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 88d3f0ab2a)
(cherry picked from commit 88ac1f4aa6)
(cherry picked from commit 543da14e2841783a1a7b850004a20265e9a0be8d)
(cherry picked from commit 6423c820c4)
This commit is contained in:
Kent Wu 2019-11-18 17:34:28 -08:00
parent 6ec4313138
commit 01ee7ecedb
10 changed files with 321 additions and 22 deletions

View File

@ -1 +1 @@
24427b3a5c95
b76dc22f2e23

View File

@ -0,0 +1,36 @@
# 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,
server_default=sa.false(), nullable=False))
def downgrade():
pass

View File

@ -24,6 +24,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'
@ -103,11 +104,17 @@ EXT_NET_ATTRIBUTES = {
EXT_SUBNET_ATTRIBUTES = {
SNAT_HOST_POOL: {
# whether an external subnet should be used as a pool
# for allocating host-based SNAT addresses
# Whether an external subnet should be used as a pool
# for allocating host-based SNAT addresses.
'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,
}
}

View File

@ -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 ActiveActiveAAPSubnetConnectedToRouter(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.")

View File

@ -72,6 +72,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',
@ -236,6 +237,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):
@ -249,6 +252,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):

View File

@ -165,6 +165,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):
@ -180,6 +182,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):
@ -190,7 +194,9 @@ 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)

View File

@ -1656,6 +1656,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if network_db.external:
raise exceptions.ExternalSubnetNotAllowed(network_id=network_id)
# We don't allow subnets marked with ACTIVE_ACTIVE_AAP flag to be
# connected to a router.
for subnet in subnets:
if subnet[cisco_apic.ACTIVE_ACTIVE_AAP]:
raise exceptions.ActiveActiveAAPSubnetConnectedToRouter(
subnet_id=subnet['id'])
# Find the address_scope(s) for the new interface.
#
# REVISIT: If dual-stack interfaces allowed, process each
@ -2220,8 +2227,64 @@ 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.filter(
n_addr_pair_db.AllowedAddressPair.port_id != sa.bindparam(
'port_id'),
models_v2.IPAllocation.network_id == sa.bindparam('network_id'))
addr_pairs = query(session).params(
network_id=port['network_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)
@ -2277,6 +2340,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):
@ -5157,15 +5221,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 (but not the 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)

View File

@ -404,6 +404,10 @@ class ApicRpcHandlerMixin(object):
info['ext_net_info'] = self._query_endpoint_ext_net_info(
session, subnet_ids)
# Query for the port's active active AAP mode.
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
# port's HAIP owned addresses are actually
@ -685,6 +689,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
@ -901,6 +922,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:
@ -956,7 +978,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,

View File

@ -119,6 +119,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'
@ -261,6 +262,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,
@ -319,6 +343,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
)
@ -5112,6 +5137,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.ActiveActiveAAPSubnetConnectedToRouter,
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()
@ -8700,7 +8751,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'])
@ -8760,6 +8812,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
@ -8825,7 +8881,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)
@ -8839,22 +8895,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
@ -8906,7 +8980,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(
@ -8920,6 +8995,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)

View File

@ -103,6 +103,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'
@ -5687,7 +5688,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)
@ -5695,6 +5696,36 @@ class TestNeutronPortOperation(AIMBaseTestCase):
'subnet']
sub2 = self._make_subnet(self.fmt, net, '1.2.3.1', '1.2.3.0/24')[
'subnet']
sub_active_aap = self._create_subnet_with_extension(
self.fmt, net, '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, '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['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': sub_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
@ -5706,6 +5737,15 @@ class TestNeutronPortOperation(AIMBaseTestCase):
t2sub2 = self._make_subnet(self.fmt, t2net, '1.2.3.1', '1.2.3.0/24',
tenant_id='t2')['subnet']
# The same aap however can be assigned to a port (but from a different
# network) whose subnets belong to a different active_acitve_aap mode.
self._make_port(self.fmt, t2net['network']['id'],
arg_list=('allowed_address_pairs',),
device_owner='compute:',
fixed_ips=[{'subnet_id': t2sub1['id']},
{'subnet_id': t2sub2['id']}],
allowed_address_pairs=allow_addr_active_aap)
# create 2 ports configured with the same allowed-addresses
p1 = self._make_port(self.fmt, net['network']['id'],
arg_list=('allowed_address_pairs',),
@ -5729,8 +5769,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.
@ -5746,6 +5795,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']}
@ -5934,7 +5994,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