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 network) 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
This commit is contained in:
Kent Wu 2019-11-18 17:34:28 -08:00
parent e9b746b8fc
commit 1ed7c1a473
10 changed files with 322 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

@ -31,6 +31,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'
@ -252,11 +253,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

@ -96,6 +96,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',
@ -316,6 +317,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):
@ -329,6 +332,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

@ -198,6 +198,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):
@ -213,6 +215,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):
@ -223,7 +227,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

@ -1702,6 +1702,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
@ -2268,8 +2275,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)
@ -2322,6 +2385,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):
@ -5269,15 +5333,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

@ -386,6 +386,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
@ -648,6 +652,24 @@ 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 = BAKERY(lambda s: s.query(
extension_db.SubnetExtensionDb.active_active_aap,
))
query += lambda q: q.filter(
extension_db.SubnetExtensionDb.subnet_id.in_(
sa.bindparam('subnet_ids', expanding=True)))
query += lambda q: q.distinct()
for active_active_aap, in query(session).params(
subnet_ids=list(subnet_ids)):
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
@ -866,6 +888,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:
@ -920,7 +943,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

@ -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
)
@ -5183,6 +5208,32 @@ class TestExtensionAttributes(ApicAimTestCase):
resp = req.get_response(self.api)
self.assertEqual(resp.status_code, 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_reader_session()
extn = extn_db.ExtensionDbMixin()
@ -8772,7 +8823,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'])
@ -8832,6 +8884,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
@ -8897,7 +8953,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)
@ -8911,22 +8967,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
@ -8978,7 +9052,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(
@ -8992,6 +9067,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

@ -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'
@ -5769,7 +5770,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)
@ -5777,6 +5778,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
@ -5788,6 +5819,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',),
@ -5811,8 +5851,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.
@ -5828,6 +5877,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']}
@ -6016,7 +6076,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