[AIM][SFC] Use single contract per provider
For scalability purposes, create only one contract per provider/graph pair. This will not affect the user intent as far as the datapath logic is concerned Change-Id: I4ca7c9d8f7a32faa7b627e8c3154bb76d76fad15
This commit is contained in:
parent
9b7b759221
commit
9d005febc4
|
@ -10,6 +10,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import hashlib
|
||||
|
||||
from aim import aim_manager
|
||||
from aim.api import resource as aim_resource
|
||||
from aim.api import service_graph as aim_sg
|
||||
|
@ -459,17 +461,18 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
# the number of nodes in the chain.
|
||||
p_tenants = set()
|
||||
for flc in flowcs:
|
||||
p_tenant = self._get_flowc_provider_group(plugin_context,
|
||||
flc).tenant_name
|
||||
p_group = self._get_flowc_provider_group(plugin_context, flc)
|
||||
p_tenant = p_group.tenant_name
|
||||
sg = self._get_pc_service_graph(p_ctx.session, pc, p_tenant)
|
||||
contract = self._get_flc_contract(p_ctx.session, flc, p_tenant)
|
||||
contract = self._get_flc_contract(p_ctx.session, p_group, sg)
|
||||
subject = aim_resource.ContractSubject(
|
||||
tenant_name=contract.tenant_name, contract_name=contract.name,
|
||||
name=sg.name, service_graph_name=sg.name,
|
||||
bi_filters=[self.aim_mech._any_filter_name])
|
||||
self.aim.create(aim_ctx, contract)
|
||||
self.aim.create(aim_ctx, subject)
|
||||
self._map_flow_classifier(p_ctx, flc, p_tenant)
|
||||
if not self.aim.get(aim_ctx, contract):
|
||||
self.aim.create(aim_ctx, contract)
|
||||
self.aim.create(aim_ctx, subject)
|
||||
self._map_flow_classifier(p_ctx, flc, pc, p_tenant)
|
||||
# Map device clusters for each flow tenant
|
||||
if p_tenant not in p_tenants:
|
||||
for ppg in ppgs:
|
||||
|
@ -520,16 +523,16 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
aim_ctx = aim_context.AimContext(session)
|
||||
deleted_ppgs = set()
|
||||
for flc in flowcs:
|
||||
tenant = self._get_flowc_provider_group(plugin_context,
|
||||
flc).tenant_name
|
||||
p_group = self._get_flowc_provider_group(plugin_context, flc)
|
||||
tenant = p_group.tenant_name
|
||||
for ppg in ppgs:
|
||||
key = (tenant, ppg['id'])
|
||||
if key not in deleted_ppgs:
|
||||
self._delete_port_pair_group_mapping(p_ctx, ppg, tenant)
|
||||
deleted_ppgs.add(key)
|
||||
self._delete_flow_classifier_mapping(p_ctx, flc, tenant)
|
||||
contract = self._get_flc_contract(p_ctx.session, flc, tenant)
|
||||
self._delete_flow_classifier_mapping(p_ctx, flc, pc, tenant)
|
||||
sg = self._get_pc_service_graph(p_ctx.session, pc, tenant)
|
||||
contract = self._get_flc_contract(p_ctx.session, p_group, sg)
|
||||
self.aim.delete(aim_ctx, contract, cascade=True)
|
||||
self.aim.delete(aim_ctx, sg, cascade=True)
|
||||
for ppg_id in pc['port_pair_groups']:
|
||||
|
@ -569,7 +572,7 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
net_id)
|
||||
self.aim.update(aim_ctx, epg, sync=True)
|
||||
|
||||
def _map_flow_classifier(self, plugin_context, flowc, tenant):
|
||||
def _map_flow_classifier(self, plugin_context, flowc, pc, tenant):
|
||||
"""Map flowclassifier to AIM model
|
||||
|
||||
If source/destination ports are plugged to external networks, create
|
||||
|
@ -580,10 +583,11 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
:return:
|
||||
"""
|
||||
aim_ctx = aim_context.AimContext(plugin_context.session)
|
||||
cons_group = self._get_flowc_consumer_group(plugin_context, flowc)
|
||||
prov_group = self._get_flowc_provider_group(plugin_context, flowc)
|
||||
contract = self._get_flc_contract(plugin_context.session, flowc,
|
||||
tenant)
|
||||
cons_group = self._map_flowc_consumer_group(plugin_context, flowc)
|
||||
prov_group = self._map_flowc_provider_group(plugin_context, flowc)
|
||||
sg = self._get_pc_service_graph(plugin_context.session, pc, tenant)
|
||||
contract = self._get_flc_contract(plugin_context.session, prov_group,
|
||||
sg)
|
||||
# TODO(ivar): if provider/consumer are in different tenants, export
|
||||
# the contract
|
||||
cons_group.consumed_contract_names.append(contract.name)
|
||||
|
@ -621,23 +625,21 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
return self.aim_mech._get_epg_by_network_id(plugin_context.session,
|
||||
net['id'])
|
||||
|
||||
def _delete_flow_classifier_mapping(self, plugin_context, flowc, tenant):
|
||||
def _delete_flow_classifier_mapping(self, plugin_context, flowc, pc,
|
||||
tenant):
|
||||
source_net = self._get_flowc_src_network(plugin_context, flowc)
|
||||
dest_net = self._get_flowc_dst_network(plugin_context, flowc)
|
||||
self._delete_flowc_network_group_mapping(
|
||||
plugin_context, source_net, flowc, tenant,
|
||||
flowc['source_ip_prefix'], FLOWC_SRC)
|
||||
plugin_context, source_net, flowc, pc, tenant, FLOWC_SRC)
|
||||
self._delete_flowc_network_group_mapping(
|
||||
plugin_context, dest_net, flowc, tenant,
|
||||
flowc['destination_ip_prefix'], FLOWC_DST)
|
||||
plugin_context, dest_net, flowc, pc, tenant, FLOWC_DST)
|
||||
|
||||
def _delete_flowc_network_group_mapping(self, plugin_context, net, flowc,
|
||||
tenant, cidr, prefix=''):
|
||||
pc, tenant, prefix=''):
|
||||
flc_aid = self._get_external_group_aim_name(plugin_context, flowc,
|
||||
prefix)
|
||||
aim_ctx = aim_context.AimContext(plugin_context.session)
|
||||
l3out = self.aim_mech._get_svi_net_l3out(net)
|
||||
cidr = netaddr.IPNetwork(cidr)
|
||||
ext_net = None
|
||||
if l3out:
|
||||
ext_net = aim_resource.ExternalNetwork(
|
||||
|
@ -648,8 +650,10 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
epg = self.aim.get(aim_ctx, self.aim_mech._get_epg_by_network_id(
|
||||
plugin_context.session, net['id']))
|
||||
if epg:
|
||||
contract = self._get_flc_contract(plugin_context.session, flowc,
|
||||
tenant)
|
||||
p_group = self._get_flowc_provider_group(plugin_context, flowc)
|
||||
sg = self._get_pc_service_graph(plugin_context.session, pc, tenant)
|
||||
contract = self._get_flc_contract(plugin_context.session, p_group,
|
||||
sg)
|
||||
try:
|
||||
if prefix == FLOWC_SRC:
|
||||
epg.consumed_contract_names.remove(contract.name)
|
||||
|
@ -658,11 +662,12 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
except ValueError:
|
||||
LOG.warning("Contract %s not present in EPG %s" %
|
||||
(contract.name, epg))
|
||||
self.aim.create(aim_ctx, epg, overwrite=True)
|
||||
else:
|
||||
epg = self.aim.create(aim_ctx, epg, overwrite=True)
|
||||
if (ext_net and not epg.consumed_contract_names and not
|
||||
epg.provided_contract_names):
|
||||
# Only remove external network if completely empty
|
||||
self.aim.delete(aim_ctx, ext_net, cascade=True)
|
||||
self.aim.delete(aim_ctx, epg, cascade=True)
|
||||
|
||||
def _get_chains_by_classifier_id(self, plugin_context, flowc_id):
|
||||
context = plugin_context
|
||||
|
@ -768,12 +773,27 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
return aim_sg.ServiceGraph(tenant_name=tenant_aid, name=pc_aid,
|
||||
display_name=pc_aname)
|
||||
|
||||
def _get_flc_contract(self, session, flc, tenant):
|
||||
tenant_id = tenant
|
||||
flc_aid = self.name_mapper.flow_classifier(session, flc['id'])
|
||||
flc_aname = aim_utils.sanitize_display_name(flc['name'])
|
||||
return aim_resource.Contract(tenant_name=tenant_id, name=flc_aid,
|
||||
display_name=flc_aname)
|
||||
def _get_flc_contract(self, session, provider, graph):
|
||||
tenant_id = provider.tenant_name
|
||||
display_name = ''
|
||||
if provider.display_name or graph.display_name:
|
||||
display_name = aim_utils.sanitize_display_name(
|
||||
provider.display_name + '_' + graph.display_name)
|
||||
# NOTE(ivar): For scalability purposes, we want to limit the number
|
||||
# of contracts by creating one per provider/graph pair.
|
||||
# In order to keep the contract name unique, hashing the provider and
|
||||
# graph names (which are already unique) together seems like the
|
||||
# safest choice, considering that truncating those names to fit 64
|
||||
# bytes would cause a huge loss of entropy. This is especially true
|
||||
# for SVI networks, where the provider group can use as much as
|
||||
# 23 bytes only for the prefix ([network cidr] + _net_)
|
||||
return aim_resource.Contract(
|
||||
tenant_name=tenant_id,
|
||||
name=self._generate_contract_name(provider.name, graph.name),
|
||||
display_name=display_name)
|
||||
|
||||
def _generate_contract_name(self, prov_name, graph_name):
|
||||
return hashlib.sha256(prov_name + graph_name).hexdigest()
|
||||
|
||||
def _get_ppg_service_redirect_policy(self, session, ppg, direction,
|
||||
tenant):
|
||||
|
@ -941,14 +961,39 @@ class SfcAIMDriver(SfcAIMDriverBase):
|
|||
break
|
||||
return flowcs, ppgs
|
||||
|
||||
def _get_flowc_network_group(self, plugin_context, flowc, net, prefix):
|
||||
flc_aid = self._get_external_group_aim_name(plugin_context, flowc,
|
||||
prefix)
|
||||
l3out = self.aim_mech._get_svi_net_l3out(net)
|
||||
if l3out:
|
||||
# Create ExternalNetwork and ExternalSubnet on the proper
|
||||
# L3Out. Return the External network
|
||||
ext_net = aim_resource.ExternalNetwork(
|
||||
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
|
||||
name=flc_aid)
|
||||
return ext_net
|
||||
else:
|
||||
return self.aim_mech._get_epg_by_network_id(plugin_context.session,
|
||||
net['id'])
|
||||
|
||||
def _get_flowc_provider_group(self, plugin_context, flowc):
|
||||
net = self._get_flowc_dst_network(plugin_context, flowc)
|
||||
return self._get_flowc_network_group(plugin_context, flowc, net,
|
||||
FLOWC_DST)
|
||||
|
||||
def _get_flowc_consumer_group(self, plugin_context, flowc):
|
||||
net = self._get_flowc_src_network(plugin_context, flowc)
|
||||
return self._get_flowc_network_group(plugin_context, flowc, net,
|
||||
FLOWC_SRC)
|
||||
|
||||
def _map_flowc_provider_group(self, plugin_context, flowc):
|
||||
aim_ctx = aim_context.AimContext(plugin_context.session)
|
||||
net = self._get_flowc_dst_network(plugin_context, flowc)
|
||||
return self.aim.get(aim_ctx, self._map_flowc_network_group(
|
||||
plugin_context, net, flowc['destination_ip_prefix'], flowc,
|
||||
FLOWC_DST))
|
||||
|
||||
def _get_flowc_consumer_group(self, plugin_context, flowc):
|
||||
def _map_flowc_consumer_group(self, plugin_context, flowc):
|
||||
aim_ctx = aim_context.AimContext(plugin_context.session)
|
||||
net = self._get_flowc_src_network(plugin_context, flowc)
|
||||
return self.aim.get(aim_ctx, self._map_flowc_network_group(
|
||||
|
|
|
@ -385,17 +385,6 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
|
|||
ppgs = [self.show_port_pair_group(x)['port_pair_group'] for x in
|
||||
pc['port_pair_groups']]
|
||||
if not multiple:
|
||||
routers_count = ctx.db_session.query(l3_db.Router).count()
|
||||
self.assertEqual(
|
||||
len(flowcs),
|
||||
len(self.aim_mgr.find(ctx, aim_res.Contract)) - routers_count)
|
||||
self.assertEqual(
|
||||
len(flowcs),
|
||||
len(self.aim_mgr.find(
|
||||
ctx, aim_res.ContractSubject)) - routers_count)
|
||||
self.assertEqual(
|
||||
len(flowcs), len(self.aim_mgr.find(
|
||||
ctx, aim_res.ContractSubject)) - routers_count)
|
||||
self.assertEqual(
|
||||
len(flowc_tenants) * len(ppgs),
|
||||
len(self.aim_mgr.find(ctx, aim_sg.DeviceClusterContext)))
|
||||
|
@ -416,42 +405,60 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
|
|||
sg = self.aim_mgr.get(ctx, aim_sg.ServiceGraph(
|
||||
tenant_name=apic_tn, name='ptc_' + pc['id']))
|
||||
self.assertIsNotNone(sg)
|
||||
# Verify Flow Classifier mapping
|
||||
contract = self.aim_mgr.get(
|
||||
ctx, aim_res.Contract(tenant_name=apic_tn,
|
||||
name='flc_' + flowc['id']))
|
||||
self.assertIsNotNone(contract)
|
||||
subject = self.aim_mgr.get(
|
||||
ctx, aim_res.ContractSubject(
|
||||
tenant_name=apic_tn, contract_name='flc_' + flowc['id'],
|
||||
name='ptc_' + pc['id']))
|
||||
self.assertIsNotNone(subject)
|
||||
self.assertEqual(['openstack_AnyFilter'], subject.bi_filters)
|
||||
src_cidr = flowc['source_ip_prefix']
|
||||
dst_cird = flowc['destination_ip_prefix']
|
||||
for net, pref, cidr in [(src_net, 'src_', src_cidr),
|
||||
(dst_net, 'dst_', dst_cird)]:
|
||||
|
||||
def get_net_group(net, cidr):
|
||||
if net['apic:svi']:
|
||||
# TODO(ivar): this will not work, there's no L3Outside
|
||||
# DN extension for external networks.
|
||||
ext = aim_res.ExternalNetwork.from_dn(
|
||||
net['apic:distinguished_names']['ExternalNetwork'])
|
||||
name_prefix = cidr.replace('/', '_')
|
||||
subnets = [cidr]
|
||||
if cidr in ['0.0.0.0/0', '::/0']:
|
||||
# use default external EPG
|
||||
name_prefix = 'default'
|
||||
subnets = ['128.0.0.0/1', '0.0.0.0/1', '8000::/1',
|
||||
'::/1']
|
||||
ext_net = self.aim_mgr.get(
|
||||
ctx, aim_res.ExternalNetwork(
|
||||
tenant_name=ext.tenant_name,
|
||||
l3out_name=ext.l3out_name,
|
||||
name=name_prefix + '_' + 'net_' + net['id']))
|
||||
return ext_net
|
||||
else:
|
||||
epg = self.aim_mgr.get(
|
||||
ctx, aim_res.EndpointGroup.from_dn(
|
||||
net['apic:distinguished_names']['EndpointGroup']))
|
||||
return epg
|
||||
|
||||
provider = get_net_group(dst_net, dst_cird)
|
||||
# Verify Flow Classifier mapping
|
||||
contract = self.aim_mgr.get(
|
||||
ctx, aim_res.Contract(
|
||||
tenant_name=apic_tn,
|
||||
name=self.sfc_driver._generate_contract_name(provider.name,
|
||||
sg.name)))
|
||||
self.assertIsNotNone(contract)
|
||||
subject = self.aim_mgr.get(
|
||||
ctx, aim_res.ContractSubject(
|
||||
tenant_name=apic_tn,
|
||||
contract_name=contract.name,
|
||||
name='ptc_' + pc['id']))
|
||||
self.assertIsNotNone(subject)
|
||||
self.assertEqual(['openstack_AnyFilter'], subject.bi_filters)
|
||||
for net, pref, cidr in [(src_net, 'src_', src_cidr),
|
||||
(dst_net, 'dst_', dst_cird)]:
|
||||
group = get_net_group(net, cidr)
|
||||
if net['apic:svi']:
|
||||
ext_net = group
|
||||
subnets = [cidr]
|
||||
if cidr in ['0.0.0.0/0', '::/0']:
|
||||
# use default external EPG
|
||||
subnets = ['128.0.0.0/1', '0.0.0.0/1', '8000::/1',
|
||||
'::/1']
|
||||
for sub in subnets:
|
||||
ext_sub = self.aim_mgr.get(ctx, aim_res.ExternalSubnet(
|
||||
tenant_name=ext.tenant_name,
|
||||
l3out_name=ext.l3out_name,
|
||||
tenant_name=ext_net.tenant_name,
|
||||
l3out_name=ext_net.l3out_name,
|
||||
external_network_name=ext_net.name, cidr=sub))
|
||||
self.assertIsNotNone(ext_sub)
|
||||
|
||||
|
@ -463,9 +470,7 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
|
|||
"%s not in ext net %s" % (contract.name,
|
||||
ext_net.__dict__))
|
||||
else:
|
||||
epg = self.aim_mgr.get(
|
||||
ctx, aim_res.EndpointGroup.from_dn(
|
||||
net['apic:distinguished_names']['EndpointGroup']))
|
||||
epg = group
|
||||
self.assertTrue(
|
||||
contract.name in (epg.consumed_contract_names if
|
||||
pref == 'src_' else
|
||||
|
@ -1236,7 +1241,7 @@ class TestPortChain(TestAIMServiceFunctionChainingBase):
|
|||
'l7_parameters']['logical_source_network'],
|
||||
'logical_destination_network': fc[
|
||||
'l7_parameters']['logical_destination_network']},
|
||||
source_ip_prefix='192.168.%s.0/24' % (i + 3),
|
||||
source_ip_prefix='192.198.%s.0/24' % (i + 3),
|
||||
destination_ip_prefix=fc['destination_ip_prefix'],
|
||||
expected_res_status=201)['flow_classifier'])
|
||||
# We have four FCs
|
||||
|
@ -1259,13 +1264,15 @@ class TestPortChain(TestAIMServiceFunctionChainingBase):
|
|||
self._verify_pc_mapping(pc1, multiple=True)
|
||||
self._verify_pc_mapping(pc2, multiple=True)
|
||||
if self.dst_svi:
|
||||
self.delete_port_chain(pc1['id'])
|
||||
dst_net_id = fc['l7_parameters']['logical_destination_network']
|
||||
ext_net = self.aim_mgr.find(
|
||||
self._aim_context, aim_res.ExternalNetwork,
|
||||
name=fc['destination_ip_prefix'].replace(
|
||||
'/', '_') + '_' + 'net_' + dst_net_id)[0]
|
||||
self.assertEqual(2, len(ext_net.provided_contract_names))
|
||||
self.delete_port_chain(pc1['id'])
|
||||
ext_net = self.aim_mgr.get(self._aim_context, ext_net)
|
||||
self.assertEqual(1, len(ext_net.provided_contract_names))
|
||||
self.delete_port_chain(pc2['id'])
|
||||
self.assertIsNone(self.aim_mgr.get(self._aim_context, ext_net))
|
||||
|
||||
|
|
Loading…
Reference in New Issue