diff --git a/gbpservice/neutron/services/sfc/aim/sfc_driver.py b/gbpservice/neutron/services/sfc/aim/sfc_driver.py index 06b0d8a02..ec331a2a4 100644 --- a/gbpservice/neutron/services/sfc/aim/sfc_driver.py +++ b/gbpservice/neutron/services/sfc/aim/sfc_driver.py @@ -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( diff --git a/gbpservice/neutron/tests/unit/services/sfc/test_aim_sfc_driver.py b/gbpservice/neutron/tests/unit/services/sfc/test_aim_sfc_driver.py index ae07c8c48..1448535fc 100644 --- a/gbpservice/neutron/tests/unit/services/sfc/test_aim_sfc_driver.py +++ b/gbpservice/neutron/tests/unit/services/sfc/test_aim_sfc_driver.py @@ -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))