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 commit88d3f0ab2a
) (cherry picked from commit88ac1f4aa6
) (cherry picked from commit 543da14e2841783a1a7b850004a20265e9a0be8d)
This commit is contained in:
parent
21240693fe
commit
6423c820c4
|
@ -1 +1 @@
|
|||
24427b3a5c95
|
||||
b76dc22f2e23
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue