From 6423c820c40961d7bd03e4bdf081dca7dd37d762 Mon Sep 17 00:00:00 2001 From: Kent Wu Date: Mon, 18 Nov 2019 17:34:28 -0800 Subject: [PATCH] Active active AAP feature 1. Introduced a new subnet extension attribute for this. When its enabled, this subnet can't be connected to a router. And to make things simple, this new attribute's value can't be changed after creation. 2. VM port's active_active_aap mode in the EP file is dtermined by checking this port's fixded_ip subnets one by one, and only if the active_active_aap value is true for all the subnets then we will mark it accordingly in this VM port's EP file. And when active_active_aap is on, the owned address concept doesn't apply to those AAPs. 3. An AAP can't be added to a port if this port's active_active_aap mode is different than any of the other ports (in the same VRF) whose AAPs is overlapping with this AAP. 4. Also added a helper function to create a subnet with those extension attributes in the UT environment. Change-Id: Icb3af7b33442eda739f93f9e6ca16174a26f5c21 (cherry picked from commit 88d3f0ab2adf2ea690d13d1e05204a75737102f5) (cherry picked from commit 88ac1f4aa6d3322d3b79e2beebf9e4d7a0112345) (cherry picked from commit 543da14e2841783a1a7b850004a20265e9a0be8d) --- .../alembic_migrations/versions/HEAD | 2 +- .../b76dc22f2e23_active_active_aap.py | 35 ++++++ gbpservice/neutron/extensions/cisco_apic.py | 7 ++ .../ml2plus/drivers/apic_aim/exceptions.py | 11 ++ .../ml2plus/drivers/apic_aim/extension_db.py | 6 + .../drivers/apic_aim/extension_driver.py | 16 ++- .../drivers/apic_aim/mechanism_driver.py | 75 ++++++++++++- .../plugins/ml2plus/drivers/apic_aim/rpc.py | 27 ++++- .../unit/plugins/ml2plus/test_apic_aim.py | 105 ++++++++++++++++-- .../grouppolicy/test_aim_mapping_driver.py | 60 +++++++++- 10 files changed, 322 insertions(+), 22 deletions(-) create mode 100644 gbpservice/neutron/db/migration/alembic_migrations/versions/b76dc22f2e23_active_active_aap.py diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD index 42c47b81c..c34a1200f 100644 --- a/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/HEAD @@ -1 +1 @@ -24427b3a5c95 +b76dc22f2e23 diff --git a/gbpservice/neutron/db/migration/alembic_migrations/versions/b76dc22f2e23_active_active_aap.py b/gbpservice/neutron/db/migration/alembic_migrations/versions/b76dc22f2e23_active_active_aap.py new file mode 100644 index 000000000..197a93efe --- /dev/null +++ b/gbpservice/neutron/db/migration/alembic_migrations/versions/b76dc22f2e23_active_active_aap.py @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Active active AAP + +Revision ID: b76dc22f2e23 +Revises: 24427b3a5c95 +Create Date: 2019-11-21 14:18:11.909757 + +""" + +# revision identifiers, used by Alembic. +revision = 'b76dc22f2e23' +down_revision = '24427b3a5c95' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('apic_aim_subnet_extensions', + sa.Column('active_active_aap', sa.Boolean)) + + +def downgrade(): + pass diff --git a/gbpservice/neutron/extensions/cisco_apic.py b/gbpservice/neutron/extensions/cisco_apic.py index bf1836fca..686059096 100644 --- a/gbpservice/neutron/extensions/cisco_apic.py +++ b/gbpservice/neutron/extensions/cisco_apic.py @@ -30,6 +30,7 @@ DIST_NAMES = 'apic:distinguished_names' SYNC_STATE = 'apic:synchronization_state' NAT_TYPE = 'apic:nat_type' SNAT_HOST_POOL = 'apic:snat_host_pool' +ACTIVE_ACTIVE_AAP = 'apic:active_active_aap' EXTERNAL_CIDRS = 'apic:external_cidrs' SVI = 'apic:svi' BGP = 'apic:bgp_enable' @@ -256,6 +257,12 @@ EXT_SUBNET_ATTRIBUTES = { 'allow_post': True, 'allow_put': True, 'is_visible': True, 'default': False, 'convert_to': conv.convert_to_boolean, + }, + ACTIVE_ACTIVE_AAP: { + # whether a subnet will support the active active AAP or not + 'allow_post': True, 'allow_put': False, + 'is_visible': True, 'default': False, + 'convert_to': conv.convert_to_boolean, } } diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/exceptions.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/exceptions.py index f5422ebac..6edd6c654 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/exceptions.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/exceptions.py @@ -90,3 +90,14 @@ class ExternalSubnetNotAllowed(exceptions.BadRequest): class SubnetOverlapInRoutedVRF(exceptions.BadRequest): message = _("Subnets %(id1)s (%(cidr1)s) and %(id2)s (%(cidr2)s) mapped " "to %(vrf)s overlap.") + + +class ActiveActiveAAPSubnetNotConnectedToRouter(exceptions.BadRequest): + message = _("Subnet %(subnet_id)s can not be connected to a router " + "because its an active active AAP subnet.") + + +class AAPNotAllowedOnDifferentActiveActiveAAPSubnet(exceptions.BadRequest): + message = _("Allowed address pair can not be added to this port " + "because its subnets %(subnet_ids)s active active AAP mode is " + "different than other port's subnets %(other_subnet_ids)s.") diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py index 714b9280d..ef9c6d117 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_db.py @@ -92,6 +92,7 @@ class SubnetExtensionDb(model_base.BASEV2): sa.String(36), sa.ForeignKey('subnets.id', ondelete="CASCADE"), primary_key=True) snat_host_pool = sa.Column(sa.Boolean) + active_active_aap = sa.Column(sa.Boolean) subnet = orm.relationship(models_v2.Subnet, backref=orm.backref( 'aim_extension_mapping', lazy='joined', @@ -298,6 +299,8 @@ class ExtensionDbMixin(object): if db_obj: self._set_if_not_none(result, cisco_apic.SNAT_HOST_POOL, db_obj['snat_host_pool']) + self._set_if_not_none(result, cisco_apic.ACTIVE_ACTIVE_AAP, + db_obj['active_active_aap']) return result def set_subnet_extn_db(self, session, subnet_id, res_dict): @@ -311,6 +314,9 @@ class ExtensionDbMixin(object): db_obj = db_obj or SubnetExtensionDb(subnet_id=subnet_id) if cisco_apic.SNAT_HOST_POOL in res_dict: db_obj['snat_host_pool'] = res_dict[cisco_apic.SNAT_HOST_POOL] + if cisco_apic.ACTIVE_ACTIVE_AAP in res_dict: + db_obj['active_active_aap'] = res_dict[ + cisco_apic.ACTIVE_ACTIVE_AAP] session.add(db_obj) def get_router_extn_db(self, session, router_id): diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py index 828f5f0e7..7635acb24 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/extension_driver.py @@ -199,6 +199,8 @@ class ApicExtensionDriver(api_plus.ExtensionDriver, res_dict = self.get_subnet_extn_db(session, result['id']) result[cisco_apic.SNAT_HOST_POOL] = ( res_dict.get(cisco_apic.SNAT_HOST_POOL, False)) + result[cisco_apic.ACTIVE_ACTIVE_AAP] = ( + res_dict.get(cisco_apic.ACTIVE_ACTIVE_AAP, False)) except Exception as e: with excutils.save_and_reraise_exception(): if db_api.is_retriable(e): @@ -214,6 +216,8 @@ class ApicExtensionDriver(api_plus.ExtensionDriver, res_dict = self.get_subnet_extn_db(session, subnet_db['id']) result[cisco_apic.SNAT_HOST_POOL] = ( res_dict.get(cisco_apic.SNAT_HOST_POOL, False)) + result[cisco_apic.ACTIVE_ACTIVE_AAP] = ( + res_dict.get(cisco_apic.ACTIVE_ACTIVE_AAP, False)) except Exception as e: with excutils.save_and_reraise_exception(): if db_api.is_retriable(e): @@ -224,15 +228,21 @@ class ApicExtensionDriver(api_plus.ExtensionDriver, def process_create_subnet(self, plugin_context, data, result): res_dict = {cisco_apic.SNAT_HOST_POOL: - data.get(cisco_apic.SNAT_HOST_POOL, False)} + data.get(cisco_apic.SNAT_HOST_POOL, False), + cisco_apic.ACTIVE_ACTIVE_AAP: + data.get(cisco_apic.ACTIVE_ACTIVE_AAP, False)} self.set_subnet_extn_db(plugin_context.session, result['id'], res_dict) result.update(res_dict) def process_update_subnet(self, plugin_context, data, result): - if not cisco_apic.SNAT_HOST_POOL in data: + if (cisco_apic.SNAT_HOST_POOL not in data and + cisco_apic.ACTIVE_ACTIVE_AAP not in data): return - res_dict = {cisco_apic.SNAT_HOST_POOL: data[cisco_apic.SNAT_HOST_POOL]} + res_dict = {cisco_apic.SNAT_HOST_POOL: + data.get(cisco_apic.SNAT_HOST_POOL, False), + cisco_apic.ACTIVE_ACTIVE_AAP: + data.get(cisco_apic.ACTIVE_ACTIVE_AAP, False)} self.set_subnet_extn_db(plugin_context.session, result['id'], res_dict) result.update(res_dict) diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py index cdb86960c..cca450829 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/mechanism_driver.py @@ -1671,6 +1671,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver, if network_db.external: raise exceptions.ExternalSubnetNotAllowed(network_id=network_id) + # We don't allow subnets makred with ACTIVE_ACTIVE_AAP flag to be + # connected to a router. + for subnet in subnets: + if subnet[cisco_apic.ACTIVE_ACTIVE_AAP]: + raise exceptions.ActiveActiveAAPSubnetNotConnectedToRouter( + subnet_id=subnet['id']) + # Find the address_scope(s) for the new interface. # # REVISIT: If dual-stack interfaces allowed, process each @@ -2235,8 +2242,68 @@ class ApicMechanismDriver(api_plus.MechanismDriver, self.aim.update(aim_ctx, sg_rule_aim, remote_ips=aim_sg_rule.remote_ips) + def _check_active_active_aap(self, context, port): + aap_current = port.get('allowed_address_pairs', []) + aap_original = [] + if context.original: + aap_original = context.original.get('allowed_address_pairs', []) + curr_ips = [aap['ip_address'] for aap in aap_current] + orig_ips = [aap['ip_address'] for aap in aap_original] + added = list(set(curr_ips) - set(orig_ips)) + if not added: + return + + session = context._plugin_context.session + query = BAKERY(lambda s: s.query( + n_addr_pair_db.AllowedAddressPair.port_id, + n_addr_pair_db.AllowedAddressPair.ip_address, + models_v2.IPAllocation.subnet_id)) + query += lambda q: q.join( + models_v2.IPAllocation, + models_v2.IPAllocation.port_id == + n_addr_pair_db.AllowedAddressPair.port_id) + query += lambda q: q.join( + models_v2.Port, + models_v2.Port.id == + n_addr_pair_db.AllowedAddressPair.port_id) + query += lambda q: q.filter( + n_addr_pair_db.AllowedAddressPair.port_id != sa.bindparam( + 'port_id'), + models_v2.Port.project_id == sa.bindparam('project_id')) + addr_pairs = query(session).params( + project_id=port['project_id'], + port_id=port['id']).all() + if not addr_pairs: + return + + affected_ports = {} + cidr_aap = netaddr.IPSet() + for added_ip in added: + cidr_aap.add(netaddr.IPNetwork(added_ip)) + for a_pair in addr_pairs: + port_id, ip_address, subnet_id = a_pair + cidr = netaddr.IPSet(netaddr.IPNetwork(ip_address)) + if cidr & cidr_aap: + affected_ports.setdefault(port_id, []) + if subnet_id not in affected_ports[port_id]: + affected_ports[port_id].append(subnet_id) + if not affected_ports: + return + + # Make sure all these ports belong to the same + # active_active_aap mode. + subnet_ids = [x['subnet_id'] for x in port['fixed_ips']] + active_aap_mode = self._query_active_active_aap(session, subnet_ids) + for port_id, other_subnet_ids in affected_ports.items(): + other_active_aap_mode = self._query_active_active_aap( + session, other_subnet_ids) + if active_aap_mode != other_active_aap_mode: + raise exceptions.AAPNotAllowedOnDifferentActiveActiveAAPSubnet( + subnet_ids=subnet_ids, other_subnet_ids=other_subnet_ids) + def create_port_precommit(self, context): port = context.current + self._check_active_active_aap(context, port) self._really_update_sg_rule_with_remote_group_set( context, port, port['security_groups'], is_delete=False) self._insert_provisioning_block(context) @@ -2289,6 +2356,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver, def update_port_precommit(self, context): port = context.current + self._check_active_active_aap(context, port) if context.original_host and context.original_host != context.host: self.disassociate_domain(context, use_original=True) if self._use_static_path(context.original_bottom_bound_segment): @@ -5172,15 +5240,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver, # Note that this is intended primarily to handle migration to # apic_aim, where the previous plugin and/or drivers did not # populate apic_aim's extension data. After migration, the - # SNAT_HOST_POOL attribute can be changed via the REST API if - # needed. + # SNAT_HOST_POOL and ACTIVE_ACTIVE_AAP attribute can be changed + # via the REST API if needed. if not mgr.should_repair( "subnet %s missing extension data" % subnet_db.id): return res_dict = { - cisco_apic.SNAT_HOST_POOL: False + cisco_apic.SNAT_HOST_POOL: False, + cisco_apic.ACTIVE_ACTIVE_AAP: False } self.set_subnet_extn_db(mgr.actual_session, subnet_db.id, res_dict) diff --git a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/rpc.py b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/rpc.py index 241a1ffbe..e9066bf2e 100644 --- a/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/rpc.py +++ b/gbpservice/neutron/plugins/ml2plus/drivers/apic_aim/rpc.py @@ -396,6 +396,8 @@ class ApicRpcHandlerMixin(object): subnet_ids = set([ip.subnet_id for ip in info['ip_info']]) info['ext_net_info'] = self._query_endpoint_ext_net_info( session, subnet_ids) + info['active_active_aap'] = self._query_active_active_aap( + session, subnet_ids) # Query for list of floating IPs for both this # port and all the other ports on which this @@ -674,6 +676,23 @@ class ApicRpcHandlerMixin(object): port_id=port_id, network_id=network_id)] + def _query_active_active_aap(self, session, subnet_ids): + # Default is False + if not subnet_ids: + False + + query = session.query( + extension_db.SubnetExtensionDb.active_active_aap, + ) + query = query.filter( + extension_db.SubnetExtensionDb.subnet_id.in_(subnet_ids)) + query = query.distinct() + for active_active_aap, in query: + if active_active_aap is False: + return False + # Return True only if all the subnets have this flag enabled + return True + def _query_endpoint_ext_net_info(self, session, subnet_ids): # REVISIT: Consider replacing this query with additional joins # in _query_endpoint_fixed_ip_info to eliminate a round-trip @@ -888,6 +907,7 @@ class ApicRpcHandlerMixin(object): details = {} details['allowed_address_pairs'] = self._build_aaps(info) + details['active_active_aap'] = info['active_active_aap'] details['app_profile_name'] = port_info.epg_app_profile_name details['device'] = info['device'] # Redundant. if self.apic_optimized_dhcp_lease_time > 0: @@ -942,7 +962,12 @@ class ApicRpcHandlerMixin(object): return details def _build_aaps(self, info): - owned_ips = set(ip.ip_address for ip in info['owned_ip_info']) + # if active_active_app is enabled, then the concept of + # owned_ip doesn't apply. + if info['active_active_aap']: + owned_ips = set() + else: + owned_ips = set(ip.ip_address for ip in info['owned_ip_info']) aaps = {} for allowed in info['aap_info']: aaps[allowed.ip_address] = {'ip_address': allowed.ip_address, diff --git a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py index 3dba69dcf..7e1cc9f33 100644 --- a/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py +++ b/gbpservice/neutron/tests/unit/plugins/ml2plus/test_apic_aim.py @@ -118,6 +118,7 @@ CIDR = 'apic:external_cidrs' PROV = 'apic:external_provided_contracts' CONS = 'apic:external_consumed_contracts' SNAT_POOL = 'apic:snat_host_pool' +ACTIVE_ACTIVE_AAP = 'apic:active_active_aap' SVI = 'apic:svi' BGP = 'apic:bgp_enable' ASN = 'apic:bgp_asn' @@ -260,6 +261,29 @@ class ApicAimTestMixin(object): self.aim_patch.start() self.addCleanup(self.aim_patch.stop) + def _create_subnet_with_extension(self, fmt, net, gateway, + cidr, **kwargs): + data = {'subnet': {'network_id': net['network']['id'], + 'ip_version': 4, + 'enable_dhcp': True, + 'tenant_id': self._tenant_id}} + if gateway: + data['subnet']['gateway_ip'] = gateway + if cidr: + data['subnet']['cidr'] = cidr + for arg in kwargs: + # Arg must be present and not null (but can be false) + if kwargs.get(arg) is not None: + data['subnet'][arg] = kwargs[arg] + subnet_req = self.new_create_request('subnets', data, fmt) + subnet_res = subnet_req.get_response(self.api) + + # Things can go wrong - raise HTTP exc with res code only + # so it can be caught by unit tests + if subnet_res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError(code=subnet_res.status_int) + return self.deserialize(fmt, subnet_res) + class ApicAimTestCase(test_address_scope.AddressScopeTestCase, test_l3.L3NatTestCaseMixin, ApicAimTestMixin, @@ -316,6 +340,7 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase, self._app_profile_name = self.driver.ap_name self.extension_attributes = ('router:external', DN, 'apic:nat_type', SNAT_POOL, + ACTIVE_ACTIVE_AAP, CIDR, PROV, CONS, SVI, BGP, BGP_TYPE, ASN ) @@ -5173,6 +5198,32 @@ class TestExtensionAttributes(ApicAimTestCase): 400) self._update('networks', net1['id'], {'apic:nat_type': ''}, 400) + def test_active_aap_subnet(self): + net = self._make_network(self.fmt, 'net1', True) + subnet = self._make_subnet( + self.fmt, net, '10.0.0.1', '10.0.0.0/24')['subnet'] + subnet = self._show('subnets', subnet['id'])['subnet'] + self.assertFalse(subnet[ACTIVE_ACTIVE_AAP]) + + # Update is not allowed + data = {'subnet': {ACTIVE_ACTIVE_AAP: True}} + req = self.new_update_request('subnets', data, subnet['id'], self.fmt) + resp = req.get_response(self.api) + self.assertEqual(resp.status_code, 400) + + subnet1 = self._create_subnet_with_extension( + self.fmt, net, '10.1.0.1', '10.1.0.0/24', + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + self.assertTrue(subnet1[ACTIVE_ACTIVE_AAP]) + + # Active active AAP subnet can't be connected to a router + router = self._make_router( + self.fmt, self._tenant_id, 'router1')['router'] + self.assertRaises(exceptions.ActiveActiveAAPSubnetNotConnectedToRouter, + self.l3_plugin.add_router_interface, + n_context.get_admin_context(), router['id'], + {'subnet_id': subnet1['id']}) + def test_external_subnet_lifecycle(self): session = db_api.get_session() extn = extn_db.ExtensionDbMixin() @@ -8761,7 +8812,8 @@ class TestOpflexRpc(ApicAimTestCase): super(TestOpflexRpc, self).setUp(*args, **kwargs) def _check_response(self, request, response, port, net, subnets, - network_type='opflex', vm_name='someid'): + network_type='opflex', vm_name='someid', + active_active_aap=False): epg = aim_resource.EndpointGroup.from_dn( net['apic:distinguished_names']['EndpointGroup']) @@ -8821,6 +8873,10 @@ class TestOpflexRpc(ApicAimTestCase): self.assertEqual(sorted([sn['cidr'] for sn in subnets]), sorted(gbp_details['vrf_subnets'])) self.assertEqual(vrf.tenant_name, gbp_details['vrf_tenant']) + if active_active_aap: + self.assertTrue(gbp_details['active_active_aap']) + else: + self.assertFalse(gbp_details['active_active_aap']) # trunk_details tests in TestVlanAwareVM @@ -8886,7 +8942,7 @@ class TestOpflexRpc(ApicAimTestCase): self.assertNotIn('gbp_details', response) self.assertNotIn('trunk_details', response) - def test_endpoint_details_bound(self): + def _test_endpoint_details_bound(self, active_active_aap=False): self.driver.apic_optimized_dhcp_lease_time = 100 host = 'host1' self._register_agent('host1', AGENT_CONF_OPFLEX) @@ -8900,22 +8956,40 @@ class TestOpflexRpc(ApicAimTestCase): {'destination': '172.16.0.0/24', 'nexthop': '10.0.1.2'}, {'destination': '192.168.0.0/24', 'nexthop': '10.0.1.3'}, ] - subnet1 = self._make_subnet( - self.fmt, net, '10.0.1.1', '10.0.1.0/24', - dns_nameservers=dns_nameservers1, - host_routes=host_routes1)['subnet'] + if active_active_aap: + subnet1 = self._create_subnet_with_extension( + self.fmt, net, '10.0.1.1', '10.0.1.0/24', + dns_nameservers=dns_nameservers1, + host_routes=host_routes1, + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + else: + subnet1 = self._make_subnet( + self.fmt, net, '10.0.1.1', '10.0.1.0/24', + dns_nameservers=dns_nameservers1, + host_routes=host_routes1)['subnet'] subnet1_id = subnet1['id'] host_routes2 = [ {'destination': '169.254.169.254/16', 'nexthop': '10.0.1.2'}, ] - subnet2 = self._make_subnet( - self.fmt, net, '10.0.2.1', '10.0.2.0/24', - host_routes=host_routes2)['subnet'] + if active_active_aap: + subnet2 = self._create_subnet_with_extension( + self.fmt, net, '10.0.2.1', '10.0.2.0/24', + host_routes=host_routes2, + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + else: + subnet2 = self._make_subnet( + self.fmt, net, '10.0.2.1', '10.0.2.0/24', + host_routes=host_routes2)['subnet'] subnet2_id = subnet2['id'] - subnet3 = self._make_subnet( - self.fmt, net, '10.0.3.1', '10.0.3.0/24')['subnet'] + if active_active_aap: + subnet3 = self._create_subnet_with_extension( + self.fmt, net, '10.0.3.1', '10.0.3.0/24', + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + else: + subnet3 = self._make_subnet( + self.fmt, net, '10.0.3.1', '10.0.3.0/24')['subnet'] subnet3_id = subnet3['id'] # Create multiple DHCP ports and multiple subnets to exercise @@ -8967,7 +9041,8 @@ class TestOpflexRpc(ApicAimTestCase): n_context.get_admin_context(), request=request, host=host) self._check_response( - request, response, port, net['network'], subnets, vm_name='a name') + request, response, port, net['network'], subnets, + vm_name='a name', active_active_aap=active_active_aap) # Call the get_vrf_details RPC handler and check its response. vrf = aim_resource.VRF.from_dn( @@ -8981,6 +9056,12 @@ class TestOpflexRpc(ApicAimTestCase): self.assertEqual(sorted([sn['cidr'] for sn in subnets]), sorted(response['vrf_subnets'])) + def test_endpoint_details_bound_regular(self): + self._test_endpoint_details_bound() + + def test_endpoint_details_bound_active_active_aap(self): + self._test_endpoint_details_bound(active_active_aap=True) + def test_endpoint_details_unbound(self): host = 'host1' net = self._make_network(self.fmt, 'net1', True) diff --git a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py index d4cfc61f9..f01115eaa 100644 --- a/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py +++ b/gbpservice/neutron/tests/unit/services/grouppolicy/test_aim_mapping_driver.py @@ -99,6 +99,7 @@ DN = cisco_apic.DIST_NAMES EPG = cisco_apic.EPG EXTERNAL_NETWORK = cisco_apic.EXTERNAL_NETWORK SNAT_HOST_POOL = cisco_apic.SNAT_HOST_POOL +ACTIVE_ACTIVE_AAP = cisco_apic.ACTIVE_ACTIVE_AAP VRF = cisco_apic.VRF CIDR = 'apic:external_cidrs' PROV = 'apic:external_provided_contracts' @@ -5757,7 +5758,7 @@ class TestNeutronPortOperation(AIMBaseTestCase): return False def _test_gbp_details_for_allowed_address_pair(self, allow_addr, - owned_addr, update_addr, update_owned_addr): + owned_addr, update_addr, update_owned_addr, is_cidr=False): self._register_agent('h1', test_aim_md.AGENT_CONF_OPFLEX) self._register_agent('h2', test_aim_md.AGENT_CONF_OPFLEX) net = self._make_network(self.fmt, 'net1', True) @@ -5765,6 +5766,41 @@ class TestNeutronPortOperation(AIMBaseTestCase): 'subnet'] sub2 = self._make_subnet(self.fmt, net, '1.2.3.1', '1.2.3.0/24')[ 'subnet'] + sub3_active_aap = self._create_subnet_with_extension( + self.fmt, net, '20.0.0.1', '20.0.0.0/24', + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + net_active_aap = self._make_network(self.fmt, 'net2', True) + sub_active_aap = self._create_subnet_with_extension( + self.fmt, net_active_aap, '2.2.3.1', '2.2.3.0/24', + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + sub_active_aap1 = self._create_subnet_with_extension( + self.fmt, net_active_aap, '3.2.3.1', '3.2.3.0/24', + **{ACTIVE_ACTIVE_AAP: 'True'})['subnet'] + + allow_addr_active_aap = [{'ip_address': '2.2.3.250', + 'mac_address': '00:00:00:AA:AA:AA'}, + {'ip_address': '2.2.3.251', + 'mac_address': '00:00:00:BB:BB:BB'}] + if is_cidr: + allow_addr_active_aap = [{'ip_address': '2.2.3.0/24', + 'mac_address': '00:00:00:AA:AA:AA'}] + owned_addr_active_aap = ['2.2.3.250', '2.2.3.251'] + p_active_aap = self._make_port(self.fmt, + net_active_aap['network']['id'], + arg_list=('allowed_address_pairs',), + device_owner='compute:', + fixed_ips=[{'subnet_id': sub_active_aap['id']}, + {'subnet_id': sub_active_aap1['id']}], + allowed_address_pairs=allow_addr_active_aap)['port'] + # The same aap can't be assigned to a port whose subnets + # belong to a different active_acitve_aap mode. + self.assertRaises(webob.exc.HTTPClientError, + self._make_port, self.fmt, net['network']['id'], + arg_list=('allowed_address_pairs',), + device_owner='compute:', + fixed_ips=[{'subnet_id': sub1['id']}, + {'subnet_id': sub3_active_aap['id']}], + allowed_address_pairs=allow_addr_active_aap) # Create similar net and subnets for a different # tenant. Similar t2* resources, with the same addresses as @@ -5799,8 +5835,17 @@ class TestNeutronPortOperation(AIMBaseTestCase): allowed_address_pairs=allow_addr)['port'] self._bind_port_to_host(p1['id'], 'h1') self._bind_port_to_host(t2p1['id'], 'h1') + self._bind_port_to_host(p_active_aap['id'], 'h1') self._bind_port_to_host(p2['id'], 'h2') self._bind_port_to_host(t2p2['id'], 'h2') + + # The same aap can't be assigned to a port whose subnets + # belong to a different active_acitve_aap mode. + self._update('ports', p_active_aap['id'], + {'port': {'allowed_address_pairs': allow_addr}}, + neutron_context=self._neutron_admin_context, + expected_code=webob.exc.HTTPBadRequest.code) + # Call agent => plugin RPC to get the details for each port. The # results should only have the configured AAPs, with none of them # active. @@ -5816,6 +5861,17 @@ class TestNeutronPortOperation(AIMBaseTestCase): sorted(details['allowed_address_pairs'])) # Call agent => plugin RPC, requesting ownership of a /32 IP + ip_owner_info = {'port': p_active_aap['id'], + 'ip_address_v4': owned_addr_active_aap[0], + 'network_id': p_active_aap['network_id']} + self.mech_driver.update_ip_owner(ip_owner_info) + details = self.mech_driver.get_gbp_details( + self._neutron_admin_context, device='tap%s' % p_active_aap['id'], + host='h1') + # There should be no active aap here + self.assertEqual(sorted(allow_addr_active_aap), + sorted(details['allowed_address_pairs'])) + ip_owner_info = {'port': p1['id'], 'ip_address_v4': owned_addr[0], 'network_id': p1['network_id']} @@ -6004,7 +6060,7 @@ class TestNeutronPortOperation(AIMBaseTestCase): 'mac_address': '00:00:00:BB:BB:BB'}] update_owned_addr = ['2.3.4.250', '2.3.4.251'] self._test_gbp_details_for_allowed_address_pair(allow_addr, - owned_addr, update_addr, update_owned_addr) + owned_addr, update_addr, update_owned_addr, is_cidr=True) def test_port_bound_other_agent(self): # REVISIT: This test should call request_endpoint_details