From add8cf18be382c3a55ab6c29cc53e62771354930 Mon Sep 17 00:00:00 2001 From: Ivar Lazzaro Date: Mon, 17 Nov 2014 14:41:23 -0800 Subject: [PATCH] Support shared resource on APIC driver Closes-bug: 1402428 Change-Id: Ic3bd7da911eaaafe112bf769fab390248b1f219f --- .../drivers/cisco/apic/apic_mapping.py | 163 +++-- .../services/grouppolicy/test_apic_mapping.py | 651 +++++++++++++----- 2 files changed, 586 insertions(+), 228 deletions(-) diff --git a/gbp/neutron/services/grouppolicy/drivers/cisco/apic/apic_mapping.py b/gbp/neutron/services/grouppolicy/drivers/cisco/apic/apic_mapping.py index 1f38a9f3c..3fad2ad12 100644 --- a/gbp/neutron/services/grouppolicy/drivers/cisco/apic/apic_mapping.py +++ b/gbp/neutron/services/grouppolicy/drivers/cisco/apic/apic_mapping.py @@ -71,6 +71,11 @@ class PATNotSupportedByApicDriver(gpexc.GroupPolicyBadRequest): message = _("Port address translation is not supported by APIC driver.") +class SharedAttributeUpdateNotSupportedOnApic(gpexc.GroupPolicyBadRequest): + message = _("Resource shared attribute update not supported on APIC " + "GBP driver for resource of type %(type)s") + + class ApicMappingDriver(api.ResourceMappingDriver): """Apic Mapping driver for Group Policy plugin. @@ -145,7 +150,9 @@ class ApicMappingDriver(api.ResourceMappingDriver): 'network_type': network[pn.NETWORK_TYPE], 'l2_policy_id': ptg['l2_policy_id'], 'tenant_id': port['tenant_id'], - 'host': port['binding:host_id'] + 'host': port['binding:host_id'], + 'ptg_apic_tentant': (ptg['tenant_id'] if not ptg['shared'] else + apic_manager.TENANT_COMMON) } def create_dhcp_policy_target_if_needed(self, plugin_context, port): @@ -200,8 +207,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): if port_min and port_max: attrs['dToPort'] = port_max attrs['dFromPort'] = port_min - tenant = self.name_mapper.tenant(context, - context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) policy_rule = self.name_mapper.policy_rule(context, context.current['id']) self.apic_manager.create_tenant_filter(policy_rule, owner=tenant, @@ -211,8 +217,8 @@ class ApicMappingDriver(api.ResourceMappingDriver): pass def create_policy_rule_set_postcommit(self, context): - # Create APIC contract - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + # Create APIC policy_rule_set + tenant = self._tenant_by_sharing_policy(context.current) contract = self.name_mapper.policy_rule_set(context, context.current['id']) with self.apic_manager.apic.transaction(None) as trs: @@ -235,14 +241,17 @@ class ApicMappingDriver(api.ResourceMappingDriver): def create_policy_target_group_postcommit(self, context): super(ApicMappingDriver, self).create_policy_target_group_postcommit( context) - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) l2_policy = self.name_mapper.l2_policy(context, context.current['l2_policy_id']) - ptg = self.name_mapper.policy_target_group(context, + epg = self.name_mapper.policy_target_group(context, context.current['id']) - + l2_policy_object = context._plugin.get_l2_policy( + context._plugin_context, context.current['l2_policy_id']) + bd_owner = self._tenant_by_sharing_policy(l2_policy_object) with self.apic_manager.apic.transaction(None) as trs: - self.apic_manager.ensure_epg_created(tenant, ptg, + self.apic_manager.ensure_epg_created(tenant, epg, + bd_owner=bd_owner, bd_name=l2_policy) subnets = self._subnet_ids_to_objects(context._plugin_context, context.current['subnets']) @@ -262,25 +271,27 @@ class ApicMappingDriver(api.ResourceMappingDriver): def update_l2_policy_precommit(self, context): self._reject_non_shared_net_on_shared_l2p(context) + self._reject_shared_update(context, 'l2_policy') def create_l2_policy_postcommit(self, context): super(ApicMappingDriver, self).create_l2_policy_postcommit(context) - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) l3_policy = self.name_mapper.l3_policy(context, context.current['l3_policy_id']) l2_policy = self.name_mapper.l2_policy(context, context.current['id']) - + l3_policy_object = context._plugin.get_l3_policy( + context._plugin_context, context.current['l3_policy_id']) + ctx_owner = self._tenant_by_sharing_policy(l3_policy_object) self.apic_manager.ensure_bd_created_on_apic(tenant, l2_policy, - ctx_owner=tenant, + ctx_owner=ctx_owner, ctx_name=l3_policy) def create_l3_policy_precommit(self, context): self._check_l3p_es(context) def create_l3_policy_postcommit(self, context): - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) l3_policy = self.name_mapper.l3_policy(context, context.current['id']) - self.apic_manager.ensure_context_enforced(tenant, l3_policy) external_segments = context.current['external_segments'] if external_segments: @@ -293,7 +304,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): def delete_policy_rule_postcommit(self, context): # TODO(ivar): delete Contract subject entries to avoid reference leak - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) policy_rule = self.name_mapper.policy_rule(context, context.current['id']) self.apic_manager.delete_tenant_filter(policy_rule, owner=tenant) @@ -303,11 +314,11 @@ class ApicMappingDriver(api.ResourceMappingDriver): pass def delete_policy_rule_set_postcommit(self, context): - # TODO(ivar): disassociate EPGs to avoid reference leak - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) - policy_rule_set = self.name_mapper.policy_rule_set( - context, context.current['id']) - self.apic_manager.delete_contract(policy_rule_set, owner=tenant) + # TODO(ivar): disassociate PTGs to avoid reference leak + tenant = self._tenant_by_sharing_policy(context.current) + contract = self.name_mapper.policy_rule_set(context, + context.current['id']) + self.apic_manager.delete_contract(contract, owner=tenant) def delete_policy_target_postcommit(self, context): port = self._core_plugin.get_port(context._plugin_context, @@ -326,7 +337,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): [], subnets) for subnet_id in context.current['subnets']: self._cleanup_subnet(context._plugin_context, subnet_id, None) - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) ptg = self.name_mapper.policy_target_group(context, context.current['id']) @@ -334,13 +345,13 @@ class ApicMappingDriver(api.ResourceMappingDriver): def delete_l2_policy_postcommit(self, context): super(ApicMappingDriver, self).delete_l2_policy_postcommit(context) - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) l2_policy = self.name_mapper.l2_policy(context, context.current['id']) self.apic_manager.delete_bd_on_apic(tenant, l2_policy) def delete_l3_policy_postcommit(self, context): - tenant = self.name_mapper.tenant(context, context.current['tenant_id']) + tenant = self._tenant_by_sharing_policy(context.current) l3_policy = self.name_mapper.l3_policy(context, context.current['id']) self.apic_manager.ensure_context_deleted(tenant, l3_policy) @@ -353,11 +364,11 @@ class ApicMappingDriver(api.ResourceMappingDriver): for es in ess: self._unplug_l3p_from_es(context, es) - def update_policy_target_precommit(self, context): - pass + def update_policy_rule_set_precommit(self, context): + self._reject_shared_update(context, 'policy_rule_set') def update_policy_target_postcommit(self, context): - # TODO(ivar): redo binding procedure if the EPG is modified, + # TODO(ivar): redo binding procedure if the PTG is modified, # not doable unless driver extension framework is in place pass @@ -368,6 +379,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): def update_policy_target_group_precommit(self, context): if set(context.original['subnets']) - set(context.current['subnets']): raise gpexc.PolicyTargetGroupSubnetRemovalNotSupported() + self._reject_shared_update(context, 'policy_target_group') def update_policy_target_group_postcommit(self, context): # TODO(ivar): refactor parent to avoid code duplication @@ -417,6 +429,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): context.current['tenant_id'], subnets=new_subnets) def update_l3_policy_precommit(self, context): + self._reject_shared_update(context, 'l3_policy') self._check_l3p_es(context) def update_l3_policy_postcommit(self, context): @@ -495,8 +508,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): default_gateway = ext_info['gateway_ip'] es_name = self.name_mapper.external_segment( context, context.current['id']) - es_tenant = self.name_mapper.tenant( - context, context.current['tenant_id']) + es_tenant = self._tenant_by_sharing_policy(context.current) ep_names = [self.name_mapper.external_policy(context, x) for x in context.current['external_policies']] @@ -638,30 +650,31 @@ class ApicMappingDriver(api.ResourceMappingDriver): transaction=None): # REVISIT(ivar): figure out what should be moved in apicapi instead if policy_rules: - tenant = self.name_mapper.tenant(context, - context.current['tenant_id']) - policy_rule_set = self.name_mapper.policy_rule_set( - context, context.current['id']) + tenant = self._tenant_by_sharing_policy(policy_rule_set) + contract = self.name_mapper.policy_rule_set(context, + context.current['id']) in_dir = [g_const.GP_DIRECTION_BI, g_const.GP_DIRECTION_IN] out_dir = [g_const.GP_DIRECTION_BI, g_const.GP_DIRECTION_OUT] filters = {'id': policy_rules} for rule in context._plugin.get_policy_rules( context._plugin_context, filters=filters): policy_rule = self.name_mapper.policy_rule(context, rule['id']) + rule_owner = self._tenant_by_sharing_policy(rule) classifier = context._plugin.get_policy_classifier( context._plugin_context, rule['policy_classifier_id']) with self.apic_manager.apic.transaction(transaction) as trs: - mgr = self.apic_manager if classifier['direction'] in in_dir: - # Contract and subject are the same thing in this case - mgr.manage_contract_subject_in_filter( - policy_rule_set, policy_rule_set, policy_rule, - owner=tenant, transaction=trs, unset=unset) + # PRS and subject are the same thing in this case + self.apic_manager.manage_contract_subject_in_filter( + contract, contract, policy_rule, owner=tenant, + transaction=trs, unset=unset, + rule_owner=rule_owner) if classifier['direction'] in out_dir: - # Contract and subject are the same thing in this case - mgr.manage_contract_subject_out_filter( - policy_rule_set, policy_rule_set, policy_rule, - owner=tenant, transaction=trs, unset=unset) + # PRS and subject are the same thing in this case + self.apic_manager.manage_contract_subject_out_filter( + contract, contract, policy_rule, owner=tenant, + transaction=trs, unset=unset, + rule_owner=rule_owner) @lockutils.synchronized('apic-portlock') def _manage_policy_target_port(self, plugin_context, pt): @@ -674,9 +687,10 @@ class ApicMappingDriver(api.ResourceMappingDriver): # TODO(ivar): change APICAPI to not expect a resource context plugin_context._plugin = self.gbp_plugin plugin_context._plugin_context = plugin_context - tenant_id = self.name_mapper.tenant(plugin_context, - port['tenant_id']) - ptg = self.name_mapper.policy_target_group( + ptg_object = self.gbp_plugin.get_policy_target_group( + plugin_context, port_details['ptg_id']) + tenant_id = self._tenant_by_sharing_policy(ptg_object) + epg = self.name_mapper.policy_target_group( plugin_context, port_details['ptg_id']) bd = self.name_mapper.l2_policy( plugin_context, port_details['l2_policy_id']) @@ -684,7 +698,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): # Create a static path attachment for the host/epg/switchport with self.apic_manager.apic.transaction() as trs: self.apic_manager.ensure_path_created_for_port( - tenant_id, ptg, port['binding:host_id'], seg, + tenant_id, epg, port['binding:host_id'], seg, bd_name=bd, transaction=trs) @@ -694,33 +708,37 @@ class ApicMappingDriver(api.ResourceMappingDriver): # TODO(ivar): change APICAPI to not expect a resource context plugin_context._plugin = self.gbp_plugin plugin_context._plugin_context = plugin_context - mapped_tenant = self.name_mapper.tenant(plugin_context, - ptg['tenant_id']) + mapped_tenant = self._tenant_by_sharing_policy(ptg) mapped_ptg = self.name_mapper.policy_target_group(plugin_context, - ptg['id']) + ptg['id']) provided = [added_provided, removed_provided] consumed = [added_consumed, removed_consumed] methods = [self.apic_manager.set_contract_for_epg, self.apic_manager.unset_contract_for_epg] with self.apic_manager.apic.transaction(transaction) as trs: for x in xrange(len(provided)): - for c in provided[x]: - c = self.name_mapper.policy_rule_set(plugin_context, c) + for c in self.gbp_plugin.get_policy_rule_sets( + plugin_context, filters={'id': provided[x]}): + c_owner = self._tenant_by_sharing_policy(c) + c = self.name_mapper.policy_rule_set(plugin_context, + c['id']) methods[x](mapped_tenant, mapped_ptg, c, provider=True, - transaction=trs) + contract_owner=c_owner, transaction=trs) for x in xrange(len(consumed)): - for c in consumed[x]: - c = self.name_mapper.policy_rule_set(plugin_context, c) + for c in self.gbp_plugin.get_policy_rule_sets( + plugin_context, filters={'id': consumed[x]}): + c_owner = self._tenant_by_sharing_policy(c) + c = self.name_mapper.policy_rule_set(plugin_context, + c['id']) methods[x](mapped_tenant, mapped_ptg, c, provider=False, - transaction=trs) + contract_owner=c_owner, transaction=trs) def _manage_ep_policy_rule_sets( self, plugin_context, es, ep, added_provided, added_consumed, removed_provided, removed_consumed, transaction=None): plugin_context._plugin = self.gbp_plugin plugin_context._plugin_context = plugin_context - mapped_tenant = self.name_mapper.tenant(plugin_context, - es['tenant_id']) + mapped_tenant = self._tenant_by_sharing_policy(es) mapped_es = self.name_mapper.external_segment(plugin_context, es['id']) mapped_ep = self.name_mapper.external_policy(plugin_context, @@ -748,8 +766,9 @@ class ApicMappingDriver(api.ResourceMappingDriver): # TODO(ivar): change APICAPI to not expect a resource context plugin_context._plugin = self.gbp_plugin plugin_context._plugin_context = plugin_context - mapped_tenant = self.name_mapper.tenant(plugin_context, - ptg['tenant_id']) + l2_policy_object = self.gbp_plugin.get_l2_policy( + plugin_context, ptg['l2_policy_id']) + mapped_tenant = self._tenant_by_sharing_policy(l2_policy_object) mapped_l2p = self.name_mapper.l2_policy(plugin_context, ptg['l2_policy_id']) subnets = [added_subnets, removed_subnets] @@ -779,11 +798,12 @@ class ApicMappingDriver(api.ResourceMappingDriver): # TODO(ivar): change APICAPI to not expect a resource context context._plugin = self.gbp_plugin context._plugin_context = context - atenant_id = self.name_mapper.tenant(context, - port_info['tenant_id']) - ptg = self.name_mapper.policy_target_group(context, + ptg_object = self.gbp_plugin.get_policy_target_group( + context, port_info['ptg_id']) + atenant_id = self._tenant_by_sharing_policy(ptg_object) + epg = self.name_mapper.policy_target_group(context, port_info['ptg_id']) - self._delete_port_path(context, atenant_id, ptg, port_info) + self._delete_port_path(context, atenant_id, epg, port_info) def _get_default_security_group(self, context, ptg_id, tenant_id): # Default SG in APIC mapping is per tenant, and allows all the traffic @@ -886,8 +906,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): default_gateway = ext_info['gateway_ip'] es_name = self.name_mapper.external_segment( context, es['id']) - es_tenant = self.name_mapper.tenant( - context, es['tenant_id']) + es_tenant = self._tenant_by_sharing_policy(es) with self.apic_manager.apic.transaction() as trs: # Create External Routed Network connected to the proper # L3 Context @@ -906,7 +925,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): def _unplug_l3p_from_es(self, context, es): es_name = self.name_mapper.external_segment(context, es['id']) - es_tenant = self.name_mapper.tenant(context, es['tenant_id']) + es_tenant = self._tenant_by_sharing_policy(es) self.apic_manager.delete_external_routed_network( es_name, owner=es_tenant) @@ -932,7 +951,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): "mapping driver.") % es['id']) continue es_name = self.name_mapper.external_segment(context, es['id']) - es_tenant = self.name_mapper.tenant(context, es['tenant_id']) + es_tenant = self._tenant_by_sharing_policy(es) with self.apic_manager.apic.transaction() as trs: # Create External EPG subnets = set(x['destination'] for @@ -959,7 +978,7 @@ class ApicMappingDriver(api.ResourceMappingDriver): "mapping driver.") % es['id']) continue es_name = self.name_mapper.external_segment(context, es['id']) - es_tenant = self.name_mapper.tenant(context, es['tenant_id']) + es_tenant = self._tenant_by_sharing_policy(es) self.apic_manager.ensure_external_epg_deleted( es_name, external_epg=ep_name, owner=es_tenant) @@ -983,3 +1002,13 @@ class ApicMappingDriver(api.ResourceMappingDriver): if ptgass: return self.gbp_plugin.get_policy_target_group( plugin_context, ptgass['policy_target_group_id']) + + def _reject_shared_update(self, context, type): + if context.original.get('shared') != context.current.get('shared'): + raise SharedAttributeUpdateNotSupportedOnApic(type=type) + + def _tenant_by_sharing_policy(self, object): + if not object.get('shared'): + return self.name_mapper.tenant(None, object['tenant_id']) + else: + return apic_manager.TENANT_COMMON diff --git a/gbp/neutron/tests/unit/services/grouppolicy/test_apic_mapping.py b/gbp/neutron/tests/unit/services/grouppolicy/test_apic_mapping.py index 1c86b88b4..d679e77af 100644 --- a/gbp/neutron/tests/unit/services/grouppolicy/test_apic_mapping.py +++ b/gbp/neutron/tests/unit/services/grouppolicy/test_apic_mapping.py @@ -32,8 +32,8 @@ from gbp.neutron.tests.unit.services.grouppolicy import test_grouppolicy_plugin APIC_L2_POLICY = 'l2_policy' APIC_L3_POLICY = 'l3_policy' -APIC_CONTRACT = 'contract' -APIC_ENDPOINT_GROUP = 'policy_target_group' +APIC_POLICY_RULE_SET = 'policy_rule_set' +APIC_POLICY_TARGET_GROUP = 'policy_target_group' APIC_POLICY_RULE = 'policy_rule' APIC_EXTERNAL_RID = '1.0.0.1' @@ -47,11 +47,14 @@ class MockCallRecorder(mock.Mock): recorded_call_set = set() def __call__(self, *args, **kwargs): - self.recorded_call_set.add((args, tuple(kwargs.items()))) + self.recorded_call_set.add(self.generate_entry(*args, **kwargs)) return mock.Mock() def call_happened_with(self, *args, **kwargs): - return (args, tuple(kwargs.items())) in self.recorded_call_set + return self.generate_entry(*args, **kwargs) in self.recorded_call_set + + def generate_entry(self, *args, **kwargs): + return args, tuple((x, kwargs[x]) for x in sorted(kwargs.keys())) class ApicMappingTestCase( @@ -88,6 +91,8 @@ class ApicMappingTestCase( self.driver.apic_manager = mock.Mock(name_mapper=mock.Mock(), ext_net_dict={}) self.driver.apic_manager.apic.transaction = self.fake_transaction + apic_mapping.apic_manager.TENANT_COMMON = 'common' + self.common_tenant = apic_mapping.apic_manager.TENANT_COMMON def _get_object(self, type, id, api): req = self.new_show_request(type, id, self.fmt) @@ -175,7 +180,7 @@ class TestPolicyTarget(ApicMappingTestCase): self.assertEqual(mgr.ensure_path_deleted_for_port.call_count, 2) def test_policy_target_port_not_deleted(self): - # Create 2 EP same EPG same host bound + # Create 2 EP same PTG same host bound ptg = self.create_policy_target_group( name="ptg1")['policy_target_group'] pt1 = self.create_policy_target( @@ -202,16 +207,24 @@ class TestPolicyTarget(ApicMappingTestCase): class TestPolicyTargetGroup(ApicMappingTestCase): - def test_policy_target_group_created_on_apic(self): + def _test_policy_target_group_created_on_apic(self, shared=False): ptg = self.create_policy_target_group( - name="ptg1")['policy_target_group'] - + name="ptg1", shared=shared)['policy_target_group'] + tenant = self.common_tenant if shared else ptg['tenant_id'] mgr = self.driver.apic_manager mgr.ensure_epg_created.assert_called_once_with( - ptg['tenant_id'], ptg['id'], bd_name=ptg['l2_policy_id']) + tenant, ptg['id'], bd_name=ptg['l2_policy_id'], + bd_owner=tenant) - def _test_ptg_policy_rule_set_created(self, provider=True): - cntr = self.create_policy_rule_set(name='c')['policy_rule_set'] + def test_policy_target_group_created_on_apic(self): + self._test_policy_target_group_created_on_apic() + + def test_policy_target_group_created_on_apic_shared(self): + self._test_policy_target_group_created_on_apic(shared=True) + + def _test_ptg_policy_rule_set_created(self, provider=True, shared=False): + cntr = self.create_policy_rule_set(name='c', + shared=shared)['policy_rule_set'] if provider: ptg = self.create_policy_target_group( @@ -223,16 +236,19 @@ class TestPolicyTargetGroup(ApicMappingTestCase): 'policy_target_group'] # Verify that the apic call is issued + ct_owner = self.common_tenant if shared else cntr['tenant_id'] mgr = self.driver.apic_manager mgr.set_contract_for_epg.assert_called_with( ptg['tenant_id'], ptg['id'], cntr['id'], transaction='transaction', - provider=provider) + contract_owner=ct_owner, provider=provider) - def _test_ptg_policy_rule_set_updated(self, provider=True): + def _test_ptg_policy_rule_set_updated(self, provider=True, shared=False): p_or_c = {True: 'provided_policy_rule_sets', False: 'consumed_policy_rule_sets'} - cntr = self.create_policy_rule_set(name='c1')['policy_rule_set'] - new_cntr = self.create_policy_rule_set(name='c2')['policy_rule_set'] + cntr = self.create_policy_rule_set( + name='c1', shared=shared)['policy_rule_set'] + new_cntr = self.create_policy_rule_set( + name='c2', shared=shared)['policy_rule_set'] if provider: ptg = self.create_policy_target_group( @@ -249,11 +265,14 @@ class TestPolicyTargetGroup(ApicMappingTestCase): ptg = self.deserialize(self.fmt, req.get_response(self.ext_api)) ptg = ptg['policy_target_group'] mgr = self.driver.apic_manager + ct_owner = self.common_tenant if shared else cntr['tenant_id'] mgr.set_contract_for_epg.assert_called_with( ptg['tenant_id'], ptg['id'], new_cntr['id'], - transaction='transaction', provider=provider) + contract_owner=ct_owner, transaction='transaction', + provider=provider) mgr.unset_contract_for_epg.assert_called_with( ptg['tenant_id'], ptg['id'], cntr['id'], + contract_owner=ct_owner, transaction='transaction', provider=provider) def test_ptg_policy_rule_set_provider_created(self): @@ -268,27 +287,53 @@ class TestPolicyTargetGroup(ApicMappingTestCase): def test_ptg_policy_rule_set_consumer_updated(self): self._test_ptg_policy_rule_set_updated(False) - def test_policy_target_group_deleted_on_apic(self): - ptg = self.create_policy_target_group( - name="ptg1")['policy_target_group'] - req = self.new_delete_request( - 'policy_target_groups', ptg['id'], self.fmt) + def test_ptg_policy_rule_set_provider_created_shared(self): + self._test_ptg_policy_rule_set_created(shared=True) + + def test_ptg_policy_rule_set_provider_updated_shared(self): + self._test_ptg_policy_rule_set_updated(shared=True) + + def test_ptg_policy_rule_set_consumer_created_shared(self): + self._test_ptg_policy_rule_set_created(False, shared=True) + + def test_ptg_policy_rule_set_consumer_updated_shared(self): + self._test_ptg_policy_rule_set_updated(False, shared=True) + + def _test_policy_target_group_deleted_on_apic(self, shared=False): + ptg = self.create_policy_target_group(name="ptg1", + shared=shared)['policy_target_group'] + req = self.new_delete_request('policy_target_groups', + ptg['id'], self.fmt) req.get_response(self.ext_api) mgr = self.driver.apic_manager + tenant = self.common_tenant if shared else ptg['tenant_id'] mgr.delete_epg_for_network.assert_called_once_with( - ptg['tenant_id'], ptg['id']) + tenant, ptg['id']) - def test_policy_target_group_subnet_created_on_apic(self): + def test_policy_target_group_deleted_on_apic(self): + self._test_policy_target_group_deleted_on_apic() - ptg = self._create_explicit_subnet_ptg('10.0.0.0/24') + def test_policy_target_group_deleted_on_apic_shared(self): + self._test_policy_target_group_deleted_on_apic(shared=True) + + def _test_policy_target_group_subnet_created_on_apic(self, shared=False): + + ptg = self._create_explicit_subnet_ptg('10.0.0.0/24', shared=shared) mgr = self.driver.apic_manager + tenant = self.common_tenant if shared else ptg['tenant_id'] mgr.ensure_subnet_created_on_apic.assert_called_once_with( - ptg['tenant_id'], ptg['l2_policy_id'], '10.0.0.1/24', + tenant, ptg['l2_policy_id'], '10.0.0.1/24', transaction='transaction') - def test_policy_target_group_subnet_added(self): - ptg = self._create_explicit_subnet_ptg('10.0.0.0/24') + def test_policy_target_group_subnet_created_on_apic(self): + self._test_policy_target_group_subnet_created_on_apic() + + def test_policy_target_group_subnet_created_on_apic_shared(self): + self._test_policy_target_group_subnet_created_on_apic(shared=True) + + def _test_policy_target_group_subnet_added(self, shared=False): + ptg = self._create_explicit_subnet_ptg('10.0.0.0/24', shared=shared) l2p = self._get_object('l2_policies', ptg['l2_policy_id'], self.ext_api) network = self._get_object('networks', l2p['l2_policy']['network_id'], @@ -300,12 +345,19 @@ class TestPolicyTargetGroup(ApicMappingTestCase): mgr = self.driver.apic_manager self.new_update_request('policy_target_groups', data, ptg['id'], self.fmt).get_response(self.ext_api) + tenant = self.common_tenant if shared else ptg['tenant_id'] mgr.ensure_subnet_created_on_apic.assert_called_with( - ptg['tenant_id'], ptg['l2_policy_id'], '10.0.1.1/24', + tenant, ptg['l2_policy_id'], '10.0.1.1/24', transaction='transaction') - def test_process_subnet_update(self): - ptg = self._create_explicit_subnet_ptg('10.0.0.0/24') + def test_policy_target_group_subnet_added(self): + self._test_policy_target_group_subnet_added() + + def test_policy_target_group_subnet_added_shared(self): + self._test_policy_target_group_subnet_added(shared=True) + + def _test_process_subnet_update(self, shared=False): + ptg = self._create_explicit_subnet_ptg('10.0.0.0/24', shared=shared) subnet = self._get_object('subnets', ptg['subnets'][0], self.api) subnet2 = copy.deepcopy(subnet) subnet2['subnet']['gateway_ip'] = '10.0.0.254' @@ -313,15 +365,23 @@ class TestPolicyTargetGroup(ApicMappingTestCase): mgr.reset_mock() self.driver.process_subnet_changed(context.get_admin_context(), subnet['subnet'], subnet2['subnet']) + + tenant = self.common_tenant if shared else ptg['tenant_id'] mgr.ensure_subnet_created_on_apic.assert_called_once_with( - ptg['tenant_id'], ptg['l2_policy_id'], '10.0.0.254/24', + tenant, ptg['l2_policy_id'], '10.0.0.254/24', transaction='transaction') mgr.ensure_subnet_deleted_on_apic.assert_called_with( - ptg['tenant_id'], ptg['l2_policy_id'], '10.0.0.1/24', + tenant, ptg['l2_policy_id'], '10.0.0.1/24', transaction='transaction') - def _create_explicit_subnet_ptg(self, cidr): - l2p = self.create_l2_policy(name="l2p") + def test_process_subnet_update(self): + self._test_process_subnet_update() + + def test_process_subnet_update_shared(self): + self._test_process_subnet_update(shared=True) + + def _create_explicit_subnet_ptg(self, cidr, shared=False): + l2p = self.create_l2_policy(name="l2p", shared=shared) l2p_id = l2p['l2_policy']['id'] network_id = l2p['l2_policy']['network_id'] network = self._get_object('networks', network_id, self.api) @@ -329,49 +389,77 @@ class TestPolicyTargetGroup(ApicMappingTestCase): subnet_id = subnet['subnet']['id'] return self.create_policy_target_group( name="ptg1", l2_policy_id=l2p_id, - subnets=[subnet_id])['policy_target_group'] + subnets=[subnet_id], shared=shared)['policy_target_group'] class TestL2Policy(ApicMappingTestCase): - def test_l2_policy_created_on_apic(self): - l2p = self.create_l2_policy(name="l2p")['l2_policy'] + def _test_l2_policy_created_on_apic(self, shared=False): + l2p = self.create_l2_policy(name="l2p", shared=shared)['l2_policy'] + tenant = self.common_tenant if shared else l2p['tenant_id'] mgr = self.driver.apic_manager mgr.ensure_bd_created_on_apic.assert_called_once_with( - l2p['tenant_id'], l2p['id'], ctx_owner=l2p['tenant_id'], - ctx_name=l2p['l3_policy_id']) + tenant, l2p['id'], ctx_owner=tenant, ctx_name=l2p['l3_policy_id']) - def test_l2_policy_deleted_on_apic(self): - l2p = self.create_l2_policy(name="l2p")['l2_policy'] + def test_l2_policy_created_on_apic(self): + self._test_l2_policy_created_on_apic() + + def test_l2_policy_created_on_apic_shared(self): + self._test_l2_policy_created_on_apic(shared=True) + + def _test_l2_policy_deleted_on_apic(self, shared=False): + l2p = self.create_l2_policy(name="l2p", shared=shared)['l2_policy'] req = self.new_delete_request('l2_policies', l2p['id'], self.fmt) req.get_response(self.ext_api) + tenant = self.common_tenant if shared else l2p['tenant_id'] mgr = self.driver.apic_manager mgr.delete_bd_on_apic.assert_called_once_with( - l2p['tenant_id'], l2p['id']) + tenant, l2p['id']) + + def test_l2_policy_deleted_on_apic(self): + self._test_l2_policy_deleted_on_apic() + + def test_l2_policy_deleted_on_apic_shared(self): + self._test_l2_policy_deleted_on_apic(shared=True) class TestL3Policy(ApicMappingTestCase): - def test_l3_policy_created_on_apic(self): - l3p = self.create_l3_policy(name="l3p")['l3_policy'] + def _test_l3_policy_created_on_apic(self, shared=False): + l3p = self.create_l3_policy(name="l3p", shared=shared)['l3_policy'] + tenant = self.common_tenant if shared else l3p['tenant_id'] mgr = self.driver.apic_manager mgr.ensure_context_enforced.assert_called_once_with( - l3p['tenant_id'], l3p['id']) + tenant, l3p['id']) - def test_l3_policy_deleted_on_apic(self): - l3p = self.create_l3_policy(name="l3p")['l3_policy'] + def test_l3_policy_created_on_apic(self): + self._test_l3_policy_created_on_apic() + + def test_l3_policy_created_on_apic_shared(self): + self._test_l3_policy_created_on_apic(shared=True) + + def _test_l3_policy_deleted_on_apic(self, shared=False): + l3p = self.create_l3_policy(name="l3p", shared=shared)['l3_policy'] req = self.new_delete_request('l3_policies', l3p['id'], self.fmt) req.get_response(self.ext_api) + + tenant = self.common_tenant if shared else l3p['tenant_id'] mgr = self.driver.apic_manager mgr.ensure_context_deleted.assert_called_once_with( - l3p['tenant_id'], l3p['id']) + tenant, l3p['id']) - def test_one_l3_policy_per_es(self): + def test_l3_policy_deleted_on_apic(self): + self._test_l3_policy_deleted_on_apic() + + def test_l3_policy_deleted_on_apic_shared(self): + self._test_l3_policy_deleted_on_apic(shared=True) + + def _test_one_l3_policy_per_es(self, shared_es=False): # Verify 2 L3P created on same ES fails es = self.create_external_segment( - cidr='192.168.0.0/24')['external_segment'] + cidr='192.168.0.0/24', shared=shared_es)['external_segment'] self.create_l3_policy(external_segments={es['id']: ['192.168.0.1']}, expected_res_status=201) res = self.create_l3_policy( @@ -388,6 +476,12 @@ class TestL3Policy(ApicMappingTestCase): self.assertEqual('OnlyOneL3PolicyIsAllowedPerExternalSegment', res['NeutronError']['type']) + def test_one_l3_policy_per_es(self): + self._test_one_l3_policy_per_es(shared_es=False) + + def test_one_l3_policy_per_es_shared(self): + self._test_one_l3_policy_per_es(shared_es=True) + def test_one_l3_policy_ip_on_es(self): # Verify L3P created with more than 1 IP on ES fails es = self.create_external_segment( @@ -408,11 +502,12 @@ class TestL3Policy(ApicMappingTestCase): self.assertEqual('OnlyOneAddressIsAllowedPerExternalSegment', res['NeutronError']['type']) - def test_l3p_plugged_to_es_at_creation(self): + def _test_l3p_plugged_to_es_at_creation(self, shared_es, shared_l3p): # Verify L3P is correctly plugged to ES on APIC during create self._mock_external_dict([('supported', '192.168.0.2/24')]) es = self.create_external_segment( name='supported', cidr='192.168.0.0/24', + shared=shared_es, external_routes=[{'destination': '0.0.0.0/0', 'nexthop': '192.168.0.254'}, {'destination': '128.0.0.0/16', @@ -420,90 +515,132 @@ class TestL3Policy(ApicMappingTestCase): # Create with explicit address l3p = self.create_l3_policy( + shared=shared_l3p, + tenant_id=es['tenant_id'] if not shared_es else 'another_tenant', external_segments={es['id']: ['192.168.0.3']}, expected_res_status=201)['l3_policy'] + owner = self.common_tenant if shared_es else es['tenant_id'] mgr = self.driver.apic_manager mgr.ensure_external_routed_network_created.assert_called_once_with( - es['id'], owner=es['tenant_id'], context=l3p['id'], + es['id'], owner=owner, context=l3p['id'], transaction=mock.ANY) mgr.ensure_logical_node_profile_created.assert_called_once_with( es['id'], mocked.APIC_EXT_SWITCH, mocked.APIC_EXT_MODULE, mocked.APIC_EXT_PORT, mocked.APIC_EXT_ENCAP, '192.168.0.3', - owner=es['tenant_id'], router_id=APIC_EXTERNAL_RID, + owner=owner, router_id=APIC_EXTERNAL_RID, transaction=mock.ANY) expected_route_calls = [ mock.call(es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.254', - owner=es['tenant_id'], subnet='0.0.0.0/0', + owner=owner, subnet='0.0.0.0/0', transaction=mock.ANY), mock.call(es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.1', - owner=es['tenant_id'], subnet='128.0.0.0/16', + owner=owner, subnet='128.0.0.0/16', transaction=mock.ANY)] self._check_call_list(expected_route_calls, mgr.ensure_static_route_created.call_args_list) - def test_l3p_plugged_to_es_at_update(self): + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_l3p_plugged_to_es_at_creation_1(self): + self._test_l3p_plugged_to_es_at_creation(shared_es=True, + shared_l3p=False) + + def test_l3p_plugged_to_es_at_creation_2(self): + self._test_l3p_plugged_to_es_at_creation(shared_es=True, + shared_l3p=True) + + def test_l3p_plugged_to_es_at_creation_3(self): + self._test_l3p_plugged_to_es_at_creation(shared_es=False, + shared_l3p=False) + + def _test_l3p_plugged_to_es_at_update(self, shared_es, shared_l3p): # Verify L3P is correctly plugged to ES on APIC during update self._mock_external_dict([('supported', '192.168.0.2/24')]) es = self.create_external_segment( name='supported', cidr='192.168.0.0/24', + shared=shared_es, external_routes=[{'destination': '0.0.0.0/0', 'nexthop': '192.168.0.254'}, {'destination': '128.0.0.0/16', 'nexthop': None}])['external_segment'] # Create with explicit address - l3p = self.create_l3_policy(expected_res_status=201)['l3_policy'] + l3p = self.create_l3_policy( + expected_res_status=201, + tenant_id=es['tenant_id'] if not shared_es else 'another_tenant', + shared=shared_l3p)['l3_policy'] l3p = self._update_gbp_resource( l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=200, external_segments={es['id']: ['192.168.0.3']}) mgr = self.driver.apic_manager + owner = self.common_tenant if shared_es else es['tenant_id'] mgr.ensure_external_routed_network_created.assert_called_once_with( - es['id'], owner=es['tenant_id'], context=l3p['id'], + es['id'], owner=owner, context=l3p['id'], transaction=mock.ANY) mgr.ensure_logical_node_profile_created.assert_called_once_with( es['id'], mocked.APIC_EXT_SWITCH, mocked.APIC_EXT_MODULE, mocked.APIC_EXT_PORT, mocked.APIC_EXT_ENCAP, '192.168.0.3', - owner=es['tenant_id'], router_id=APIC_EXTERNAL_RID, + owner=owner, router_id=APIC_EXTERNAL_RID, transaction=mock.ANY) expected_route_calls = [ mock.call(es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.254', - owner=es['tenant_id'], subnet='0.0.0.0/0', + owner=owner, subnet='0.0.0.0/0', transaction=mock.ANY), mock.call(es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.1', - owner=es['tenant_id'], subnet='128.0.0.0/16', + owner=owner, subnet='128.0.0.0/16', transaction=mock.ANY)] self._check_call_list(expected_route_calls, mgr.ensure_static_route_created.call_args_list) - def test_l3p_unplugged_from_es_on_delete(self): + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_l3p_plugged_to_es_at_update_1(self): + self._test_l3p_plugged_to_es_at_update(shared_es=True, + shared_l3p=False) + + def test_l3p_plugged_to_es_at_update_2(self): + self._test_l3p_plugged_to_es_at_update(shared_es=True, + shared_l3p=True) + + def test_l3p_plugged_to_es_at_update_3(self): + self._test_l3p_plugged_to_es_at_update(shared_es=False, + shared_l3p=False) + + def _test_l3p_unplugged_from_es_on_delete(self, shared_es, shared_l3p): self._mock_external_dict([('supported1', '192.168.0.2/24'), ('supported2', '192.168.1.2/24')]) es1 = self.create_external_segment( - name='supported1', cidr='192.168.0.0/24', + name='supported1', cidr='192.168.0.0/24', shared=shared_es, external_routes=[{'destination': '0.0.0.0/0', 'nexthop': '192.168.0.254'}, {'destination': '128.0.0.0/16', 'nexthop': None}])['external_segment'] es2 = self.create_external_segment( - name='supported2', cidr='192.168.1.0/24')['external_segment'] + shared=shared_es, name='supported2', + cidr='192.168.1.0/24')['external_segment'] l3p = self.create_l3_policy( - external_segments={es1['id']: ['192.168.0.3']}, + external_segments={es1['id']: ['192.168.0.3']}, shared=shared_l3p, + tenant_id=es1['tenant_id'] if not shared_es else 'another_tenant', expected_res_status=201)['l3_policy'] req = self.new_delete_request('l3_policies', l3p['id'], self.fmt) res = req.get_response(self.ext_api) self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) + mgr = self.driver.apic_manager + owner = self.common_tenant if shared_es else es1['tenant_id'] mgr.delete_external_routed_network.assert_called_once_with( - es1['id'], owner=es1['tenant_id']) + es1['id'], owner=owner) mgr.delete_external_routed_network.reset_mock() # Verify correct deletion for 2 ESs l3p = self.create_l3_policy( + shared=shared_l3p, + tenant_id=es1['tenant_id'] if not shared_es else 'another_tenant', external_segments={es1['id']: ['192.168.0.3'], es2['id']: ['192.168.1.3']}, expected_res_status=201)['l3_policy'] @@ -512,27 +649,46 @@ class TestL3Policy(ApicMappingTestCase): self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) expected_delete_calls = [ - mock.call(es1['id'], owner=es1['tenant_id']), - mock.call(es2['id'], owner=es2['tenant_id'])] + mock.call(es1['id'], owner=owner), + mock.call(es2['id'], owner=owner)] self._check_call_list( expected_delete_calls, mgr.delete_external_routed_network.call_args_list) - def test_l3p_unplugged_from_es_on_update(self): + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_l3p_unplugged_from_es_on_delete_1(self): + self._test_l3p_unplugged_from_es_on_delete(shared_es=True, + shared_l3p=False) + + def test_l3p_unplugged_from_es_on_delete_2(self): + self._test_l3p_unplugged_from_es_on_delete(shared_es=True, + shared_l3p=True) + + def test_l3p_unplugged_from_es_on_delete_3(self): + self._test_l3p_unplugged_from_es_on_delete(shared_es=False, + shared_l3p=False) + + def _test_l3p_unplugged_from_es_on_update(self, shared_es, shared_l3p): self._mock_external_dict([('supported1', '192.168.0.2/24'), ('supported2', '192.168.1.2/24')]) es1 = self.create_external_segment( - name='supported1', cidr='192.168.0.0/24', + name='supported1', cidr='192.168.0.0/24', shared=shared_es, external_routes=[{'destination': '0.0.0.0/0', 'nexthop': '192.168.0.254'}, {'destination': '128.0.0.0/16', 'nexthop': None}])['external_segment'] es2 = self.create_external_segment( + shared=shared_es, name='supported2', cidr='192.168.1.0/24')['external_segment'] l3p = self.create_l3_policy( + tenant_id=es1['tenant_id'] if not shared_es else 'another_tenant', + shared=shared_l3p, external_segments={es1['id']: ['192.168.0.3']}, expected_res_status=201)['l3_policy'] + mgr = self.driver.apic_manager + owner = self.common_tenant if shared_es else es1['tenant_id'] mgr.ensure_external_routed_network_created.reset_mock() mgr.ensure_logical_node_profile_created.reset_mock() mgr.ensure_static_route_created.reset_mock() @@ -542,14 +698,14 @@ class TestL3Policy(ApicMappingTestCase): external_segments={es2['id']: ['192.168.1.3']}) mgr.delete_external_routed_network.assert_called_once_with( - es1['id'], owner=es1['tenant_id']) + es1['id'], owner=owner) mgr.ensure_external_routed_network_created.assert_called_once_with( - es2['id'], owner=es2['tenant_id'], context=l3p['id'], + es2['id'], owner=owner, context=l3p['id'], transaction=mock.ANY) mgr.ensure_logical_node_profile_created.assert_called_once_with( es2['id'], mocked.APIC_EXT_SWITCH, mocked.APIC_EXT_MODULE, mocked.APIC_EXT_PORT, mocked.APIC_EXT_ENCAP, '192.168.1.3', - owner=es2['tenant_id'], router_id=APIC_EXTERNAL_RID, + owner=owner, router_id=APIC_EXTERNAL_RID, transaction=mock.ANY) self.assertFalse(mgr.ensure_static_route_created.called) @@ -562,12 +718,26 @@ class TestL3Policy(ApicMappingTestCase): l3p['id'], 'l3_policy', 'l3_policies', expected_res_status=200, external_segments={}) expected_delete_calls = [ - mock.call(es1['id'], owner=es1['tenant_id']), - mock.call(es2['id'], owner=es2['tenant_id'])] + mock.call(es1['id'], owner=owner), + mock.call(es2['id'], owner=owner)] self._check_call_list( expected_delete_calls, mgr.delete_external_routed_network.call_args_list) + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_l3p_unplugged_from_es_on_update_1(self): + self._test_l3p_unplugged_from_es_on_update(shared_es=True, + shared_l3p=False) + + def test_l3p_unplugged_from_es_on_update_2(self): + self._test_l3p_unplugged_from_es_on_update(shared_es=True, + shared_l3p=True) + + def test_l3p_unplugged_from_es_on_update_3(self): + self._test_l3p_unplugged_from_es_on_update(shared_es=False, + shared_l3p=False) + def test_verify_unsupported_es_noop(self): # Verify L3P is correctly plugged to ES on APIC during update self._mock_external_dict([('supported', '192.168.0.2/24')]) @@ -613,30 +783,42 @@ class TestL3Policy(ApicMappingTestCase): class TestPolicyRuleSet(ApicMappingTestCase): - # TODO(ivar): verify rule intersection with hierarchical policy_rule_sets - # happens on APIC - def test_policy_rule_set_created_on_apic(self): - self.create_policy_rule_set(name="ctr") + # TODO(ivar): verify rule intersection with hierarchical PRS happens + # on APIC + def _test_policy_rule_set_created_on_apic(self, shared=False): + ct = self.create_policy_rule_set(name="ctr", + shared=shared)['policy_rule_set'] + tenant = self.common_tenant if shared else ct['tenant_id'] mgr = self.driver.apic_manager - self.assertEqual(1, mgr.create_contract.call_count) + mgr.create_contract.assert_called_once_with( + ct['id'], owner=tenant, transaction='transaction') - def test_policy_rule_set_created_with_rules(self): + def test_policy_rule_set_created_on_apic(self): + self._test_policy_rule_set_created_on_apic() + + def test_policy_rule_set_created_on_apic_shared(self): + self._test_policy_rule_set_created_on_apic(shared=True) + + def _test_policy_rule_set_created_with_rules(self, shared=False): bi, in_d, out = range(3) - rules = self._create_3_direction_rules() + rules = self._create_3_direction_rules(shared=shared) # exclude BI rule for now ctr = self.create_policy_rule_set( name="ctr", policy_rules=[x['id'] for x in rules[1:]])[ 'policy_rule_set'] + rule_owner = self.common_tenant if shared else rules[0]['tenant_id'] # Verify that the in-out rules are correctly enforced on the APIC mgr = self.driver.apic_manager mgr.manage_contract_subject_in_filter.assert_called_once_with( ctr['id'], ctr['id'], rules[in_d]['id'], owner=ctr['tenant_id'], - transaction='transaction', unset=False) + transaction='transaction', unset=False, + rule_owner=rule_owner) mgr.manage_contract_subject_out_filter.assert_called_once_with( ctr['id'], ctr['id'], rules[out]['id'], owner=ctr['tenant_id'], - transaction='transaction', unset=False) + transaction='transaction', unset=False, + rule_owner=rule_owner) # Create policy_rule_set with BI rule ctr = self.create_policy_rule_set( @@ -644,21 +826,31 @@ class TestPolicyRuleSet(ApicMappingTestCase): mgr.manage_contract_subject_in_filter.assert_called_with( ctr['id'], ctr['id'], rules[bi]['id'], owner=ctr['tenant_id'], - transaction='transaction', unset=False) + transaction='transaction', unset=False, + rule_owner=rule_owner) mgr.manage_contract_subject_out_filter.assert_called_with( ctr['id'], ctr['id'], rules[bi]['id'], owner=ctr['tenant_id'], - transaction='transaction', unset=False) + transaction='transaction', unset=False, + rule_owner=rule_owner) - def test_policy_rule_set_updated_with_new_rules(self): + def test_policy_rule_set_created_with_rules(self): + self._test_policy_rule_set_created_with_rules() + + def test_policy_rule_set_created_with_rules_shared(self): + self._test_policy_rule_set_created_with_rules(shared=True) + + def _test_policy_rule_set_updated_with_new_rules(self, shared=False): bi, in_d, out = range(3) - old_rules = self._create_3_direction_rules() - new_rules = self._create_3_direction_rules() + old_rules = self._create_3_direction_rules(shared=shared) + new_rules = self._create_3_direction_rules(shared=shared) # exclude BI rule for now ctr = self.create_policy_rule_set( name="ctr", policy_rules=[x['id'] for x in old_rules[1:]])['policy_rule_set'] - data = {'policy_rule_set': - {'policy_rules': [x['id'] for x in new_rules[1:]]}} + data = {'policy_rule_set': { + 'policy_rules': [x['id'] for x in new_rules[1:]]}} + rule_owner = (self.common_tenant if shared else + old_rules[in_d]['tenant_id']) mgr = self.driver.apic_manager mgr.manage_contract_subject_in_filter = MockCallRecorder() mgr.manage_contract_subject_out_filter = MockCallRecorder() @@ -669,21 +861,23 @@ class TestPolicyRuleSet(ApicMappingTestCase): self.assertTrue( mgr.manage_contract_subject_in_filter.call_happened_with( ctr['id'], ctr['id'], old_rules[in_d]['id'], + rule_owner=rule_owner, owner=ctr['tenant_id'], transaction='transaction', unset=True)) self.assertTrue( mgr.manage_contract_subject_in_filter.call_happened_with( ctr['id'], ctr['id'], new_rules[in_d]['id'], owner=ctr['tenant_id'], transaction='transaction', - unset=False)) + unset=False, rule_owner=rule_owner)) self.assertTrue( mgr.manage_contract_subject_out_filter.call_happened_with( ctr['id'], ctr['id'], old_rules[out]['id'], - owner=ctr['tenant_id'], transaction='transaction', unset=True)) + owner=ctr['tenant_id'], transaction='transaction', unset=True, + rule_owner=rule_owner)) self.assertTrue( mgr.manage_contract_subject_out_filter.call_happened_with( ctr['id'], ctr['id'], new_rules[out]['id'], owner=ctr['tenant_id'], transaction='transaction', - unset=False)) + unset=False, rule_owner=rule_owner)) ctr = self.create_policy_rule_set( name="ctr", @@ -696,47 +890,65 @@ class TestPolicyRuleSet(ApicMappingTestCase): self.assertTrue( mgr.manage_contract_subject_in_filter.call_happened_with( ctr['id'], ctr['id'], old_rules[bi]['id'], - owner=ctr['tenant_id'], transaction='transaction', unset=True)) + owner=ctr['tenant_id'], transaction='transaction', unset=True, + rule_owner=rule_owner)) self.assertTrue( mgr.manage_contract_subject_out_filter.call_happened_with( ctr['id'], ctr['id'], old_rules[bi]['id'], - owner=ctr['tenant_id'], transaction='transaction', unset=True)) + owner=ctr['tenant_id'], transaction='transaction', unset=True, + rule_owner=rule_owner)) self.assertTrue( mgr.manage_contract_subject_in_filter.call_happened_with( ctr['id'], ctr['id'], new_rules[bi]['id'], owner=ctr['tenant_id'], transaction='transaction', - unset=False)) + unset=False, rule_owner=rule_owner)) self.assertTrue( mgr.manage_contract_subject_out_filter.call_happened_with( ctr['id'], ctr['id'], new_rules[bi]['id'], owner=ctr['tenant_id'], transaction='transaction', - unset=False)) + unset=False, rule_owner=rule_owner)) - def _create_3_direction_rules(self): + def test_policy_rule_set_updated_with_new_rules(self): + self._test_policy_rule_set_updated_with_new_rules() + + def test_policy_rule_set_updated_with_new_rules_shared(self): + self._test_policy_rule_set_updated_with_new_rules(shared=True) + + def _create_3_direction_rules(self, shared=False): a1 = self.create_policy_action(name='a1', - action_type='allow')['policy_action'] + action_type='allow', + shared=shared)['policy_action'] cl_attr = {'protocol': 'tcp', 'port_range': 80} cls = [] for direction in ['bi', 'in', 'out']: cls.append(self.create_policy_classifier( - direction=direction, **cl_attr)['policy_classifier']) + direction=direction, shared=shared, + **cl_attr)['policy_classifier']) rules = [] for classifier in cls: rules.append(self.create_policy_rule( - classifier['id'], policy_actions=[a1['id']])['policy_rule']) + classifier['id'], policy_actions=[a1['id']], + shared=shared)['policy_rule']) return rules class TestPolicyRule(ApicMappingTestCase): - def test_policy_rule_created_on_apic(self): - pr = self._create_simple_policy_rule('in', 'udp', 88) + def _test_policy_rule_created_on_apic(self, shared=False): + pr = self._create_simple_policy_rule('in', 'udp', 88, shared=shared) + tenant = self.common_tenant if shared else pr['tenant_id'] mgr = self.driver.apic_manager mgr.create_tenant_filter.assert_called_once_with( - pr['id'], owner=pr['tenant_id'], etherT='ip', prot='udp', + pr['id'], owner=tenant, etherT='ip', prot='udp', dToPort=88, dFromPort=88) + def test_policy_rule_created_on_apic(self): + self._test_policy_rule_created_on_apic() + + def test_policy_rule_created_on_apic_shared(self): + self._test_policy_rule_created_on_apic(shared=True) + def test_policy_rule_many_actions_rejected(self): actions = [self.create_policy_action( action_type='allow')['policy_action']['id'] for x in range(2)] @@ -746,24 +958,33 @@ class TestPolicyRule(ApicMappingTestCase): self.create_policy_rule(cls['id'], expected_res_status=400, policy_actions=actions) - def test_policy_rule_on_apic(self): - pr = self._create_simple_policy_rule() + def _test_policy_rule_deleted_on_apic(self, shared=False): + pr = self._create_simple_policy_rule(shared=shared) req = self.new_delete_request('policy_rules', pr['id'], self.fmt) req.get_response(self.ext_api) + + tenant = self.common_tenant if shared else pr['tenant_id'] mgr = self.driver.apic_manager mgr.delete_tenant_filter.assert_called_once_with( - pr['id'], owner=pr['tenant_id']) + pr['id'], owner=tenant) + + def test_policy_rule_deleted_on_apic(self): + self._test_policy_rule_deleted_on_apic() + + def test_policy_rule_deleted_on_apic_shared(self): + self._test_policy_rule_deleted_on_apic(shared=True) def _create_simple_policy_rule(self, direction='bi', protocol='tcp', - port_range=80): + port_range=80, shared=False): cls = self.create_policy_classifier( direction=direction, protocol=protocol, - port_range=port_range)['policy_classifier'] + port_range=port_range, shared=shared)['policy_classifier'] action = self.create_policy_action( - action_type='allow')['policy_action'] + action_type='allow', shared=shared)['policy_action'] return self.create_policy_rule( - cls['id'], policy_actions=[action['id']])['policy_rule'] + cls['id'], policy_actions=[action['id']], + shared=shared)['policy_rule'] class TestExternalSegment(ApicMappingTestCase): @@ -787,10 +1008,16 @@ class TestExternalSegment(ApicMappingTestCase): self.assertEqual('PATNotSupportedByApicDriver', res['NeutronError']['type']) - def test_create(self): + def _test_create(self, shared=False): self._mock_external_dict([('supported', '192.168.0.2/24')]) - self.create_external_segment(name='supported', expected_res_status=201) - self.create_external_segment(name='unsupport', expected_res_status=201) + self.create_external_segment(name='supported', expected_res_status=201, + shared=shared) + self.create_external_segment(name='unsupport', expected_res_status=201, + shared=shared) + + def test_create(self): + self._test_create(False) + self._test_create(True) def test_update_unsupported_noop(self): self._mock_external_dict([('supported', '192.168.0.2/24')]) @@ -813,11 +1040,11 @@ class TestExternalSegment(ApicMappingTestCase): self.assertFalse(mgr.ensure_external_epg_created.called) self.assertFalse(mgr.ensure_next_hop_deleted.called) - def test_route_update_remove(self): + def _test_route_update_remove(self, shared_es, shared_ep): # Verify routes are updated correctly self._mock_external_dict([('supported', '192.168.0.2/24')]) es = self.create_external_segment( - name='supported', cidr = '192.168.0.0/24', + name='supported', cidr = '192.168.0.0/24', shared=shared_es, external_routes=[{'destination': '0.0.0.0/0', 'nexthop': '192.168.0.254'}, {'destination': '128.0.0.0/16', @@ -826,10 +1053,12 @@ class TestExternalSegment(ApicMappingTestCase): # Attach 3 external policies f = self.create_external_policy - eps = [f(external_segments=[es['id']], + eps = [f(external_segments=[es['id']], shared=shared_ep, + tenant_id=es['tenant_id'] if not shared_es else 'another', expected_res_status=201)['external_policy'] for x in xrange(3)] mgr = self.driver.apic_manager + owner = es['tenant_id'] if not shared_es else self.common_tenant mgr.ensure_external_epg_created.reset_mock() # Remove route completely self._update_gbp_resource(es['id'], 'external_segment', @@ -840,12 +1069,12 @@ class TestExternalSegment(ApicMappingTestCase): mgr = self.driver.apic_manager mgr.ensure_static_route_deleted.assert_called_with( es['id'], mocked.APIC_EXT_SWITCH, '128.0.0.0/16', - owner=es['tenant_id'], transaction=mock.ANY) + owner=owner, transaction=mock.ANY) expected_delete_calls = [] for ep in eps: expected_delete_calls.append( mock.call(es['id'], subnets=['128.0.0.0/16'], - external_epg=ep['id'], owner=es['tenant_id'], + external_epg=ep['id'], owner=owner, transaction=mock.ANY)) self._check_call_list( expected_delete_calls, @@ -865,37 +1094,50 @@ class TestExternalSegment(ApicMappingTestCase): 'nexthop': None}]) mgr.ensure_next_hop_deleted.assert_called_with( es['id'], mocked.APIC_EXT_SWITCH, '0.0.0.0/0', '192.168.0.254', - owner=es['tenant_id'], transaction=mock.ANY) + owner=owner, transaction=mock.ANY) # Being the new nexthop 'None', the default one is used mgr.ensure_static_route_created.assert_called_with( es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.1', - subnet='0.0.0.0/0', owner=es['tenant_id'], transaction=mock.ANY) + subnet='0.0.0.0/0', owner=owner, transaction=mock.ANY) expected_delete_calls = [] for ep in eps: expected_delete_calls.append( mock.call(es['id'], subnet='0.0.0.0/0', external_epg=ep['id'], - owner=es['tenant_id'], transaction=mock.ANY)) + owner=owner, transaction=mock.ANY)) self._check_call_list(expected_delete_calls, mgr.ensure_external_epg_created.call_args_list) self.assertFalse(mgr.ensure_static_route_deleted.called) self.assertFalse(mgr.ensure_external_epg_routes_deleted.called) - def test_route_update_add(self): + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_route_update_remove_1(self): + self._test_route_update_remove(shared_ep=True, shared_es=True) + + def test_route_update_remove_2(self): + self._test_route_update_remove(shared_ep=False, shared_es=True) + + def test_route_update_remove_3(self): + self._test_route_update_remove(shared_ep=False, shared_es=False) + + def _test_route_update_add(self, shared_es, shared_ep): # Verify routes are updated correctly self._mock_external_dict([('supported', '192.168.0.2/24')]) es = self.create_external_segment( - name='supported', cidr='192.168.0.0/24', + name='supported', cidr='192.168.0.0/24', shared=shared_es, external_routes=[], expected_res_status=201)['external_segment'] # Attach 3 external policies f = self.create_external_policy - eps = [f(external_segments=[es['id']], + eps = [f(external_segments=[es['id']], shared=shared_ep, + tenant_id=es['tenant_id'] if not shared_es else 'another', expected_res_status=201)['external_policy'] for x in xrange(3)] mgr = self.driver.apic_manager mgr.ensure_external_epg_created.reset_mock() + owner = es['tenant_id'] if not shared_es else self.common_tenant self._update_gbp_resource(es['id'], 'external_segment', 'external_segments', expected_res_status=200, external_routes=[ @@ -904,13 +1146,13 @@ class TestExternalSegment(ApicMappingTestCase): mgr.ensure_static_route_created.assert_called_with( es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.254', - subnet='128.0.0.0/16', owner=es['tenant_id'], transaction=mock.ANY) + subnet='128.0.0.0/16', owner=owner, transaction=mock.ANY) expected_create_calls = [] for ep in eps: expected_create_calls.append( mock.call(es['id'], subnet='128.0.0.0/16', - external_epg=ep['id'], owner=es['tenant_id'], + external_epg=ep['id'], owner=owner, transaction=mock.ANY)) self._check_call_list(expected_create_calls, mgr.ensure_external_epg_created.call_args_list) @@ -932,12 +1174,12 @@ class TestExternalSegment(ApicMappingTestCase): mgr.ensure_static_route_created.assert_called_with( es['id'], mocked.APIC_EXT_SWITCH, '192.168.0.1', - subnet='0.0.0.0/0', owner=es['tenant_id'], transaction=mock.ANY) + subnet='0.0.0.0/0', owner=owner, transaction=mock.ANY) expected_create_calls = [] for ep in eps: expected_create_calls.append( mock.call(es['id'], subnet='0.0.0.0/0', - external_epg=ep['id'], owner=es['tenant_id'], + external_epg=ep['id'], owner=owner, transaction=mock.ANY)) self._check_call_list(expected_create_calls, mgr.ensure_external_epg_created.call_args_list) @@ -945,6 +1187,17 @@ class TestExternalSegment(ApicMappingTestCase): self.assertFalse(mgr.ensure_external_epg_routes_deleted.called) self.assertFalse(mgr.ensure_next_hop_deleted.called) + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_route_update_add_1(self): + self._test_route_update_add(shared_ep=True, shared_es=True) + + def test_route_update_add_2(self): + self._test_route_update_add(shared_ep=False, shared_es=True) + + def test_route_update_add_3(self): + self._test_route_update_add(shared_ep=False, shared_es=False) + class TestExternalPolicy(ApicMappingTestCase): @@ -973,11 +1226,11 @@ class TestExternalPolicy(ApicMappingTestCase): # Verify noop on unsupported self.assertFalse(mgr.ensure_external_epg_created.called) - def test_creation_no_prs(self): + def _test_creation_no_prs(self, shared_es, shared_ep): self._mock_external_dict([('supported', '192.168.0.2/24')]) es_list = [ self.create_external_segment( - name='supported', cidr='192.168.0.0/24', + name='supported', cidr='192.168.0.0/24', shared=shared_es, expected_res_status=201, external_routes=[{ 'destination': '128.0.0.0/16', @@ -985,42 +1238,58 @@ class TestExternalPolicy(ApicMappingTestCase): for x in range(3)] ep = self.create_external_policy( - external_segments=[x['id'] for x in es_list], + external_segments=[x['id'] for x in es_list], shared=shared_ep, + tenant_id=es_list[0]['tenant_id'] if not shared_es else 'another', expected_res_status=201)['external_policy'] mgr = self.driver.apic_manager + owner = (es_list[0]['tenant_id'] if not shared_es + else self.common_tenant) expected_create_calls = [] for es in es_list: expected_create_calls.append( mock.call(es['id'], subnet='128.0.0.0/16', - external_epg=ep['id'], owner=es['tenant_id'], + external_epg=ep['id'], owner=owner, transaction=mock.ANY)) self._check_call_list(expected_create_calls, mgr.ensure_external_epg_created.call_args_list) - def test_update_no_prs(self): + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_creation_no_prs_1(self): + self._test_creation_no_prs(shared_ep=True, shared_es=True) + + def test_creation_no_prs_2(self): + self._test_creation_no_prs(shared_ep=False, shared_es=True) + + def test_creation_no_prs_3(self): + self._test_creation_no_prs(shared_ep=False, shared_es=False) + + def _test_update_no_prs(self, shared_es, shared_ep): self._mock_external_dict([('supported', '192.168.0.2/24')]) es_list = [ self.create_external_segment( - name='supported', cidr='192.168.0.0/24', + name='supported', cidr='192.168.0.0/24', shared=shared_es, expected_res_status=201, external_routes=[{ 'destination': '128.0.0.0/16', 'nexthop': '192.168.0.254'}])['external_segment'] for x in range(3)] ep = self.create_external_policy( - expected_res_status=201)['external_policy'] + tenant_id=es_list[0]['tenant_id'] if not shared_es else 'another', + shared=shared_ep, expected_res_status=201)['external_policy'] ep = self._update_gbp_resource( ep['id'], 'external_policy', 'external_policies', expected_res_status=200, external_segments=[x['id'] for x in es_list]) mgr = self.driver.apic_manager + owner = (es_list[0]['tenant_id'] if not shared_es + else self.common_tenant) expected_create_calls = [] for es in es_list: expected_create_calls.append( mock.call(es['id'], subnet='128.0.0.0/16', - external_epg=ep['id'], owner=es['tenant_id'], - transaction=mock.ANY)) + external_epg=ep['id'], owner=owner, transaction=mock.ANY)) self._check_call_list(expected_create_calls, mgr.ensure_external_epg_created.call_args_list) @@ -1031,18 +1300,30 @@ class TestExternalPolicy(ApicMappingTestCase): expected_create_calls = [] for es in es_list: expected_create_calls.append( - mock.call(es['id'], owner=es['tenant_id'], - external_epg=ep['id'])) + mock.call(es['id'], owner=owner, external_epg=ep['id'])) self._check_call_list(expected_create_calls, mgr.ensure_external_epg_deleted.call_args_list) - def test_create_with_prs(self): - prov = self.create_policy_rule_set()['policy_rule_set'] - cons = self.create_policy_rule_set()['policy_rule_set'] + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_update_no_prs_1(self): + self._test_update_no_prs(shared_ep=True, shared_es=True) + + def test_update_no_prs_2(self): + self._test_update_no_prs(shared_ep=False, shared_es=True) + + def test_update_no_prs_3(self): + self._test_update_no_prs(shared_ep=False, shared_es=False) + + def _test_create_with_prs(self, shared_es, shared_ep, shared_prs): + prov = self._create_policy_rule_set_on_shared( + shared=shared_prs) + cons = self._create_policy_rule_set_on_shared( + shared=shared_prs) self._mock_external_dict([('supported', '192.168.0.2/24')]) es_list = [ self.create_external_segment( - name='supported', cidr='192.168.0.0/24', + name='supported', cidr='192.168.0.0/24', shared=shared_es, expected_res_status=201, external_routes=[{ 'destination': '128.0.0.0/16', @@ -1050,37 +1331,65 @@ class TestExternalPolicy(ApicMappingTestCase): for x in range(3)] ep = self.create_external_policy( provided_policy_rule_sets={prov['id']: ''}, - consumed_policy_rule_sets={cons['id']: ''}, + consumed_policy_rule_sets={cons['id']: ''}, shared=shared_ep, + tenant_id=es_list[0]['tenant_id'] if not shared_es else 'another', external_segments=[x['id'] for x in es_list], expected_res_status=201)['external_policy'] mgr = self.driver.apic_manager + owner = (es_list[0]['tenant_id'] if not shared_es + else self.common_tenant) expected_calls = [] for es in es_list: expected_calls.append( mock.call(es['id'], prov['id'], external_epg=ep['id'], - provided=True, owner=es['tenant_id'], + provided=True, owner=owner, transaction=mock.ANY)) expected_calls.append( mock.call(es['id'], cons['id'], external_epg=ep['id'], - provided=False, owner=es['tenant_id'], + provided=False, owner=owner, transaction=mock.ANY)) self._check_call_list(expected_calls, mgr.set_contract_for_external_epg.call_args_list) - def test_update_add_prs(self): - prov = self.create_policy_rule_set()['policy_rule_set'] - cons = self.create_policy_rule_set()['policy_rule_set'] + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_create_with_prs_1(self): + self._test_create_with_prs(shared_es=True, shared_ep=True, + shared_prs=True) + + def test_create_with_prs_2(self): + self._test_create_with_prs(shared_es=True, shared_ep=False, + shared_prs=True) + + def test_create_with_prs_3(self): + self._test_create_with_prs(shared_es=True, shared_ep=False, + shared_prs=False) + + def test_create_with_prs_4(self): + self._test_create_with_prs(shared_es=False, shared_ep=False, + shared_prs=False) + + def test_create_with_prs_5(self): + self._test_create_with_prs(shared_es=False, shared_ep=False, + shared_prs=True) + + def _test_update_add_prs(self, shared_es, shared_ep, shared_prs): + prov = self._create_policy_rule_set_on_shared( + shared=shared_prs) + cons = self._create_policy_rule_set_on_shared( + shared=shared_prs) self._mock_external_dict([('supported', '192.168.0.2/24')]) es_list = [ self.create_external_segment( - name='supported', cidr='192.168.0.0/24', + name='supported', cidr='192.168.0.0/24', shared=shared_es, expected_res_status=201, external_routes=[{ 'destination': '128.0.0.0/16', 'nexthop': '192.168.0.254'}])['external_segment'] for x in range(3)] ep = self.create_external_policy( - external_segments=[x['id'] for x in es_list], + external_segments=[x['id'] for x in es_list], shared=shared_ep, + tenant_id=es_list[0]['tenant_id'] if not shared_es else 'another', expected_res_status=201)['external_policy'] ep = self._update_gbp_resource( ep['id'], 'external_policy', 'external_policies', @@ -1088,16 +1397,16 @@ class TestExternalPolicy(ApicMappingTestCase): provided_policy_rule_sets={prov['id']: ''}, consumed_policy_rule_sets={cons['id']: ''}) mgr = self.driver.apic_manager + owner = (es_list[0]['tenant_id'] if not shared_es + else self.common_tenant) expected_calls = [] for es in es_list: expected_calls.append( mock.call(es['id'], prov['id'], external_epg=ep['id'], - provided=True, owner=es['tenant_id'], - transaction=mock.ANY)) + provided=True, owner=owner, transaction=mock.ANY)) expected_calls.append( mock.call(es['id'], cons['id'], external_epg=ep['id'], - provided=False, owner=es['tenant_id'], - transaction=mock.ANY)) + provided=False, owner=owner, transaction=mock.ANY)) self._check_call_list(expected_calls, mgr.set_contract_for_external_epg.call_args_list) @@ -1109,11 +1418,31 @@ class TestExternalPolicy(ApicMappingTestCase): for es in es_list: expected_calls.append( mock.call(es['id'], prov['id'], external_epg=ep['id'], - provided=True, owner=es['tenant_id'], - transaction=mock.ANY)) + provided=True, owner=owner, transaction=mock.ANY)) expected_calls.append( mock.call(es['id'], cons['id'], external_epg=ep['id'], - provided=False, owner=es['tenant_id'], - transaction=mock.ANY)) + provided=False, owner=owner, transaction=mock.ANY)) self._check_call_list( expected_calls, mgr.unset_contract_for_external_epg.call_args_list) + + # Although the naming convention used here has been chosen poorly, + # I'm separating the tests in order to get the mock re-set. + def test_update_add_prs_1(self): + self._test_update_add_prs(shared_es=True, shared_ep=True, + shared_prs=True) + + def test_update_add_prs_2(self): + self._test_update_add_prs(shared_es=True, shared_ep=False, + shared_prs=True) + + def test_update_add_prs_3(self): + self._test_update_add_prs(shared_es=True, shared_ep=False, + shared_prs=False) + + def test_update_add_prs_4(self): + self._test_update_add_prs(shared_es=False, shared_ep=False, + shared_prs=False) + + def test_update_add_prs_5(self): + self._test_update_add_prs(shared_es=False, shared_ep=False, + shared_prs=True)