[apic_aim] External connectivity for multi-scope routing

Manage external connectivity for all VRFs associated with a router.

Change-Id: I6016d85b433093bee960010b57a19ceb4b78b67d
(cherry picked from commit b8e7d2afd4)
This commit is contained in:
Robert Kukura 2017-05-22 14:35:04 -04:00
parent eab6e908ef
commit 2ce34e1a26
3 changed files with 294 additions and 137 deletions

View File

@ -106,6 +106,12 @@ class DbMixin(object):
filter_by(network_id=network_id).
one_or_none())
def _get_network_mappings_for_vrf(self, session, vrf):
return (session.query(NetworkMapping).
filter_by(vrf_tenant_name=vrf.tenant_name,
vrf_name=vrf.name).
all())
def _get_network_bd(self, mapping):
return aim_resource.BridgeDomain(
tenant_name=mapping.bd_tenant_name,

View File

@ -706,8 +706,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
old_net) if old_net else None
new_net = self.plugin.get_network(context,
new_net) if new_net else None
self._manage_external_connectivity(context,
current, old_net, new_net)
vrfs = self._get_vrfs_for_router(session, current['id'])
for vrf in vrfs:
self._manage_external_connectivity(
context, current, old_net, new_net, vrf)
# Send a port update so that SNAT info may be recalculated for
# affected ports in the interfaced subnets.
@ -821,11 +823,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# stack's scope separately, or at least raise an exception.
scope_id = self._get_address_scope_id_for_subnets(context, subnets)
# Find number of existing interface ports on the router,
# excluding the one we are adding.
#
# REVISIT: Just for this scope?
router_intf_count = self._get_router_intf_count(session, router)
# Find number of existing interface ports on the router for
# this scope, excluding the one we are adding.
router_intf_count = self._get_router_intf_count(
session, router, scope_id)
# Find up to two existing router interfaces for this
# network. The interface currently being added is not
@ -1136,10 +1137,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if router_db.gw_port_id:
net = self.plugin.get_network(context,
router_db.gw_port.network_id)
# If this was the last interface-port, then we no longer know
# the VRF for this router. So update external-conectivity to
# exclude this router.
if not self._get_router_intf_count(session, router_db):
# If this was the last interface for this VRF for this
# router, update external-conectivity to exclude this
# router.
if not self._get_router_intf_count(session, router_db, scope_id):
self._manage_external_connectivity(
context, router_db, net, None, old_vrf)
@ -1484,23 +1485,61 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
'vrf': vrf})
return vrf
def _get_vrf_for_router(self, session, router):
# REVISIT: Return list of VRFs for when multi-scope routing is
# supported?
def _get_vrfs_for_router(self, session, router_id):
# REVISIT: Persist router/VRF relationship?
# Find the unique VRFs for the scoped interfaces, accounting
# for isomorphic scopes.
vrfs = {}
scope_dbs = (session.query(as_db.AddressScope).
join(models_v2.SubnetPool,
models_v2.SubnetPool.address_scope_id ==
as_db.AddressScope.id).
join(models_v2.Subnet,
models_v2.Subnet.subnetpool_id ==
models_v2.SubnetPool.id).
join(models_v2.IPAllocation).
join(models_v2.Port).
join(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == router_id).
filter(l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
distinct())
for scope_db in scope_dbs:
vrf = self._get_address_scope_vrf(scope_db.aim_mapping)
vrfs[tuple(vrf.identity)] = vrf
# Find VRF for first unscoped interface.
network_db = (session.query(models_v2.Network).
join(models_v2.Port).
join(models_v2.IPAllocation).
join(models_v2.Subnet).
outerjoin(models_v2.SubnetPool,
models_v2.SubnetPool.id ==
models_v2.Subnet.subnetpool_id).
join(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == router['id'],
filter(l3_db.RouterPort.router_id == router_id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
filter(sa.or_(models_v2.Subnet.subnetpool_id.is_(None),
models_v2.SubnetPool.address_scope_id.is_(
None))).
limit(1).
first())
if network_db:
return self._get_network_vrf(network_db.aim_mapping)
vrf = self._get_network_vrf(network_db.aim_mapping)
vrfs[tuple(vrf.identity)] = vrf
return vrfs.values()
# Used by policy driver.
def _get_address_scope_ids_for_vrf(self, session, vrf):
mappings = self._get_address_scope_mappings_for_vrf(session, vrf)
if mappings:
return [mapping.scope_id for mapping in mappings]
return [mapping.scope_id for mapping in mappings]
def _get_network_ids_for_vrf(self, session, vrf):
mappings = self._get_network_mappings_for_vrf(session, vrf)
return [mapping.network_id for mapping in mappings]
def _get_routers_for_vrf(self, session, vrf):
# REVISIT: Persist router/VRF relationship?
@ -1521,24 +1560,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
scope_ids))
.distinct())
else:
# For an unscoped VRF, first find all the routed BDs
# referencing the VRF.
aim_ctx = aim_context.AimContext(session)
bds = self.aim.find(
aim_ctx, aim_resource.BridgeDomain,
tenant_name=vrf.tenant_name,
vrf_name=DEFAULT_VRF_NAME,
enable_routing=True)
# Then find network IDs for those BDs mapped from networks.
net_ids = []
for bd in bds:
id = self.name_mapper.reverse_network(
session, bd.name, enforce=False)
if id:
net_ids.append(id)
# Then find routers interfaced to those networks.
net_ids = self._get_network_ids_for_vrf(session, vrf)
rtr_dbs = (session.query(l3_db.Router).
join(l3_db.RouterPort).
join(models_v2.Port).
@ -2008,12 +2030,48 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return aim_resource.Subnet.to_gw_ip_mask(
subnet['gateway_ip'], int(subnet['cidr'].split('/')[1]))
def _get_router_intf_count(self, session, router):
return (session.query(l3_db.RouterPort)
.filter(l3_db.RouterPort.router_id == router['id'])
.filter(l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF)
.count())
def _get_router_intf_count(self, session, router, scope_id=None):
if not scope_id:
result = (session.query(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == router['id']).
filter(l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
count())
elif scope_id == NO_ADDR_SCOPE:
result = (session.query(l3_db.RouterPort).
join(models_v2.Port).
join(models_v2.IPAllocation).
join(models_v2.Subnet).
outerjoin(models_v2.SubnetPool,
models_v2.Subnet.subnetpool_id ==
models_v2.SubnetPool.id).
filter(l3_db.RouterPort.router_id == router['id']).
filter(l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
filter(sa.or_(models_v2.Subnet.subnetpool_id.is_(None),
models_v2.SubnetPool.address_scope_id.is_(
None))).
count())
else:
# Include interfaces for isomorphic scope.
mapping = self._get_address_scope_mapping(session, scope_id)
vrf = self._get_address_scope_vrf(mapping)
mappings = self._get_address_scope_mappings_for_vrf(session, vrf)
scope_ids = [mapping.scope_id for mapping in mappings]
result = (session.query(l3_db.RouterPort).
join(models_v2.Port).
join(models_v2.IPAllocation).
join(models_v2.Subnet).
join(models_v2.SubnetPool,
models_v2.Subnet.subnetpool_id ==
models_v2.SubnetPool.id).
filter(l3_db.RouterPort.router_id == router['id']).
filter(l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
filter(models_v2.SubnetPool.address_scope_id.in_(
scope_ids)).
count())
return result
def _get_address_scope_id_for_subnets(self, context, subnets):
# Assuming that all the subnets provided are consistent w.r.t.
@ -2030,34 +2088,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
scope_id = (subnetpool_db.address_scope_id or NO_ADDR_SCOPE)
return scope_id
def _get_address_scope_id_for_router(self, session, router):
scope_id = NO_ADDR_SCOPE
for pool_db in (session.query(models_v2.SubnetPool)
.join(models_v2.Subnet,
models_v2.Subnet.subnetpool_id ==
models_v2.SubnetPool.id)
.join(models_v2.IPAllocation)
.join(models_v2.Port)
.join(l3_db.RouterPort)
.filter(l3_db.RouterPort.router_id == router['id'],
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF)
.filter(models_v2.SubnetPool
.address_scope_id.isnot(None))
.distinct()):
if pool_db.ip_version == 4:
scope_id = pool_db.address_scope_id
break
elif pool_db.ip_version == 6:
scope_id = pool_db.address_scope_id
return scope_id
def _manage_external_connectivity(self, context, router, old_network,
new_network, vrf=None):
new_network, vrf):
session = context.session
aim_ctx = aim_context.AimContext(db_session=session)
vrf = vrf or self._get_vrf_for_router(session, router)
# Keep only the identity attributes of the VRF so that calls to
# nat-library have consistent resource values. This
# is mainly required to ease unit-test verification.

View File

@ -297,9 +297,16 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
class TestAimMapping(ApicAimTestCase):
def setUp(self):
self.call_wrapper = CallRecordWrapper()
self.mock_ns = self.call_wrapper.setUp(
nat_strategy.DistributedNatStrategy)
self._actual_scopes = {}
super(TestAimMapping, self).setUp()
def tearDown(self):
self.call_wrapper.tearDown()
super(TestAimMapping, self).tearDown()
def _get_tenant(self, tenant_name):
session = db_api.get_session()
aim_ctx = aim_context.AimContext(session)
@ -1124,16 +1131,32 @@ class TestAimMapping(ApicAimTestCase):
mock.call(mock.ANY, tenant)]
self._check_call_list(exp_calls, self.driver.aim.delete.call_args_list)
def test_multi_scope_routing(self):
# REVISIT: Re-enable testing with non-isomorphic scopes once
# they are supported. Also, test with shared scopes?
def test_multi_scope_routing_with_unscoped_pools(self):
self._test_multi_scope_routing(True)
def test_multi_scope_routing_without_unscoped_pools(self):
self._test_multi_scope_routing(False)
def _test_multi_scope_routing(self, use_unscoped_pools):
# REVISIT: Re-enable testing with non-isomorphic scopes on the
# same network once they are supported. Also, test with shared
# scopes?
# Get default unscoped routed VRF DNs for main and sharing
# projects.
tenant_aname = self.name_mapper.project(None, self._tenant_id)
main_vrf = aim_resource.VRF(
tenant_name=tenant_aname, name='DefaultVRF').dn
tenant_aname = self.name_mapper.project(None, 'tenant_2')
shared_vrf = aim_resource.VRF(
tenant_name=tenant_aname, name='DefaultVRF').dn
# Create a v6 scope and pool.
scope6 = self._make_address_scope(
self.fmt, 6, name='as6')['address_scope']
scope6_id = scope6['id']
self._check_address_scope(scope6)
scope6_vrf = scope6['apic:distinguished_names']['VRF']
scope46i_vrf = scope6['apic:distinguished_names']['VRF']
pool6 = self._make_subnetpool(
self.fmt, ['2001:db8:1::0/56'], name='sp6',
tenant_id=self._tenant_id,
@ -1142,7 +1165,7 @@ class TestAimMapping(ApicAimTestCase):
# Create isomorphic v4 scope and pool.
scope4i = self._make_address_scope_for_vrf(
scope6_vrf, 4, name='as4i')['address_scope']
scope46i_vrf, 4, name='as4i')['address_scope']
scope4i_id = scope4i['id']
self._actual_scopes[scope4i_id] = scope6
self._check_address_scope(scope4i)
@ -1156,11 +1179,26 @@ class TestAimMapping(ApicAimTestCase):
self.fmt, 4, name='as4n')['address_scope']
scope4n_id = scope4n['id']
self._check_address_scope(scope4n)
scope4n_vrf = scope4n['apic:distinguished_names']['VRF']
pool4n = self._make_subnetpool(
self.fmt, ['10.2.0.0/16'], name='sp4n', tenant_id=self._tenant_id,
address_scope_id=scope4n_id, default_prefixlen=24)['subnetpool']
pool4n_id = pool4n['id']
# Create unscoped pools if required.
if use_unscoped_pools:
pool4u = self._make_subnetpool(
self.fmt, ['10.3.0.0/16', '10.4.0.0/16'], name='sp4u',
tenant_id=self._tenant_id, default_prefixlen=24)['subnetpool']
pool4u_id = pool4u['id']
pool6u = self._make_subnetpool(
self.fmt, ['2001:db8:1::0/56'], name='sp6u',
tenant_id=self._tenant_id)['subnetpool']
pool6u_id = pool6u['id']
else:
pool4u_id = None
pool6u_id = None
# Create network with subnets using first v4 scope and v6 scope.
net_resp = self._make_network(self.fmt, 'net1', True)
net1 = net_resp['network']
@ -1197,12 +1235,13 @@ class TestAimMapping(ApicAimTestCase):
self._check_network(net3)
gw43_ip = '10.3.1.1'
subnet43 = self._make_subnet(
self.fmt, net_resp, gw43_ip, '10.3.1.0/24')['subnet']
self.fmt, net_resp, gw43_ip, '10.3.1.0/24',
subnetpool_id=pool4u_id)['subnet']
self._check_subnet(subnet43, net3, [], [gw43_ip])
gw63_ip = '2001:db8:1:3::1'
subnet63 = self._make_subnet(
self.fmt, net_resp, gw63_ip, '2001:db8:1:3::0/64',
ip_version=6)['subnet']
ip_version=6, subnetpool_id=pool6u_id)['subnet']
self._check_subnet(subnet63, net3, [], [gw63_ip])
# Create shared network with unscoped subnets.
@ -1212,14 +1251,27 @@ class TestAimMapping(ApicAimTestCase):
self._check_network(net4)
gw44_ip = '10.4.1.1'
subnet44 = self._make_subnet(
self.fmt, net_resp, gw44_ip, '10.4.1.0/24')['subnet']
self.fmt, net_resp, gw44_ip, '10.4.1.0/24',
subnetpool_id=pool4u_id)['subnet']
self._check_subnet(subnet44, net4, [], [gw44_ip])
gw64_ip = '2001:db8:1:4::1'
subnet64 = self._make_subnet(
self.fmt, net_resp, gw64_ip, '2001:db8:1:4::0/64',
ip_version=6)['subnet']
ip_version=6, subnetpool_id=pool6u_id)['subnet']
self._check_subnet(subnet64, net4, [], [gw64_ip])
# Create two external networks with subnets.
ext_net1 = self._make_ext_network(
'ext-net1', dn=self.dn_t1_l1_n1)
self._make_subnet(
self.fmt, {'network': ext_net1}, '100.100.100.1',
'100.100.100.0/24')
ext_net2 = self._make_ext_network(
'ext-net2', dn=self.dn_t1_l2_n2)
self._make_subnet(
self.fmt, {'network': ext_net2}, '200.200.200.1',
'200.200.200.0/24')
def add(subnet):
# REVISIT: Adding by port would work, but adding shared
# network interface by subnet fails without admin context.
@ -1265,154 +1317,218 @@ class TestAimMapping(ApicAimTestCase):
router, expected_gw_ips, unexpected_gw_ips, scopes,
unscoped_project)
def check_ns(disconnect_vrf_dns, from_net_dn,
connect_vrf_dns, to_net_dn):
def check_calls(mock, expected_vrf_dns, expected_net_dn):
# REVISIT: We should be able to use assert_has_calls()
# since assert_called_once_with() works in
# TestExternalConnectivityBase, but args don't seem to
# match when they should.
vrf_dns = []
for args, _ in mock.call_args_list:
_, net, vrf = args
self.assertEqual(expected_net_dn, net.dn)
vrf_dns.append(vrf.dn)
self.assertEqual(sorted(expected_vrf_dns), sorted(vrf_dns))
check_calls(
self.mock_ns.disconnect_vrf, disconnect_vrf_dns, from_net_dn)
check_calls(
self.mock_ns.connect_vrf, connect_vrf_dns, to_net_dn)
self.mock_ns.reset_mock()
# Create router.
router = self._make_router(
self.fmt, self._tenant_id, 'router1')['router']
self.fmt, self._tenant_id, 'router1',
external_gateway_info={'network_id': ext_net1['id']})['router']
router_id = router['id']
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [], [subnet43, subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[], None)
check_ns([], None, [], None)
# Add first scoped v4 subnet to router.
# Add first scoped v4 subnet to router, which should connect
# the isomorphic VRF to ext_net1.
add(subnet4i1)
check([(net1, [subnet4i1], [subnet61], scope4i, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [], [subnet43, subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i], None)
check_ns([], None, [scope46i_vrf], self.dn_t1_l1_n1)
# Add first scoped v6 subnet to router.
# Add first scoped v6 subnet to router, which should not
# effect external connectivity.
add(subnet61)
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [], [subnet43, subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i, scope6], None)
check_ns([], None, [], None)
# Add first unscoped v6 subnet to router.
# Add first unscoped v6 subnet to router, which should connect
# the default VRF to ext_net1.
add(subnet63)
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [subnet63], [subnet43], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i, scope6], self._tenant_id)
# Add second scoped v6 subnet to router.
add(subnet62)
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net3, [subnet63], [subnet43], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i, scope6], self._tenant_id)
check_ns([], None, [main_vrf], self.dn_t1_l1_n1)
# REVISIT: Enable when non-isomorphic network routing is
# supported.
#
# Add second scoped v4 subnet to router.
# add(subnet4n2)
# Add second scoped v6 subnet to router, which should connect
# its VRF to ext_net1.
# add(subnet62)
# check([(net1, [subnet4i1, subnet61], [], scope4i, None),
# (net2, [subnet4n2, subnet62], [], scope6, None),
# (net2, [subnet62], [subnet4n2], scope6, None),
# (net3, [subnet63], [subnet43], None, None),
# (net4, [], [subnet44, subnet64], None, None)],
# [scope4i, scope4n, scope6], self._tenant_id)
# [scope4i, scope6], self._tenant_id)
# Add first unscoped v4 subnet to router.
# Add second scoped v4 subnet to router, which should connect
# its VRF to ext_net1.
add(subnet4n2)
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet63], [subnet43], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i, scope4n, scope6], self._tenant_id)
check_ns([], None, [scope4n_vrf], self.dn_t1_l1_n1)
# Add first unscoped v4 subnet to router, which should not
# effect external connectivity.
add(subnet43)
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope4i, scope6], self._tenant_id)
[scope4i, scope4n, scope6], self._tenant_id)
check_ns([], None, [], None)
# Add shared unscoped v4 subnet to router, which should move
# unscoped topology but not scoped topologies.
# unscoped topology but not scoped topologies, and should
# disconnect tenant's own VRF from ext_net1 and connect
# sharing tenant's VRF to ext_net1.
add(subnet44)
# REVISIT: net2 should be scope4n.
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet44], [subnet64], None, 'tenant_2')],
[scope4i, scope6], 'tenant_2')
[scope4i, scope4n, scope6], 'tenant_2')
check_ns([main_vrf], self.dn_t1_l1_n1, [shared_vrf], self.dn_t1_l1_n1)
# Add shared unscoped v6 subnet to router.
# Add shared unscoped v6 subnet to router, which should not
# effect external connectivity.
add(subnet64)
# REVISIT: net2 should be scope4n.
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet44, subnet64], [], None, 'tenant_2')],
[scope4i, scope6], 'tenant_2')
[scope4i, scope4n, scope6], 'tenant_2')
check_ns([], None, [], None)
# Remove first scoped v4 subnet from router.
# Update router with new gateway, which should disconnect all
# VRFs from ext_net1 and connect them to ext_net2.
self._update('routers', router_id,
{'router': {'external_gateway_info':
{'network_id': ext_net2['id']}}})
check([(net1, [subnet4i1, subnet61], [], scope4i, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet44, subnet64], [], None, 'tenant_2')],
[scope4i, scope4n, scope6], 'tenant_2')
check_ns([scope46i_vrf, scope4n_vrf, shared_vrf], self.dn_t1_l1_n1,
[scope46i_vrf, scope4n_vrf, shared_vrf], self.dn_t1_l2_n2)
# Remove first scoped v4 subnet from router, which should not
# effect external connectivity.
remove(subnet4i1)
# REVISIT: net1 should be scope6, net2 should be scope4n.
check([(net1, [subnet61], [subnet4i1], scope4i, None),
(net2, [subnet62], [subnet4n2], scope6, None),
check([(net1, [subnet61], [subnet4i1], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet44, subnet64], [], None, 'tenant_2')],
[scope6], 'tenant_2')
[scope4n, scope6], 'tenant_2')
check_ns([], None, [], None)
# Remove first scoped v6 subnet from router.
# Remove first scoped v6 subnet from router, which should
# disconnect isomorphic VRF from ext_net2.
remove(subnet61)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet44, subnet64], [], None, 'tenant_2')],
[scope6], 'tenant_2')
[scope4n], 'tenant_2')
check_ns([scope46i_vrf], self.dn_t1_l2_n2, [], None)
# Remove shared unscoped v4 subnet from router.
# Remove shared unscoped v4 subnet from router, which should
# not effect external connecivity.
remove(subnet44)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, 'tenant_2'),
(net4, [subnet64], [subnet44], None, 'tenant_2')],
[scope6], 'tenant_2')
[scope4n], 'tenant_2')
check_ns([], None, [], None)
# Remove shared unscoped v6 subnet from router, which should
# move remaining unscoped topology back to original tenant.
# move remaining unscoped topology back to original tenant,
# and should disconnect sharing tenant's VRF from ext_net2 and
# connect tenant's own VRF to ext_net1.
remove(subnet64)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43, subnet63], [], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope6], self._tenant_id)
[scope4n], self._tenant_id)
check_ns([shared_vrf], self.dn_t1_l2_n2, [main_vrf], self.dn_t1_l2_n2)
# Remove first unscoped v6 subnet from router.
# Remove first unscoped v6 subnet from router, which should
# not effect external connectivity.
remove(subnet63)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net2, [subnet4n2], [subnet62], scope4n, None),
(net3, [subnet43], [subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope6], self._tenant_id)
[scope4n], self._tenant_id)
check_ns([], None, [], None)
# REVISIT: Enable when non-isomorphic network routing is
# supported.
#
# Remove second scoped v4 subnet from router.
# remove(subnet4n2)
# check([(net1, [], [subnet4i1, subnet61], None, None),
# (net2, [subnet62], [subnet4n2], scope6, None),
# (net3, [subnet43], [subnet63], None, None),
# (net4, [], [subnet44, subnet64], None, None)],
# [scope6], self._tenant_id)
# Remove second unscoped v4 subnet from router.
remove(subnet43)
# Remove second scoped v4 subnet from router, which should
# disconnect its VRF from ext_net2.
remove(subnet4n2)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [subnet62], [subnet4n2], scope6, None),
(net3, [], [subnet43, subnet63], None, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [subnet43], [subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[scope6], None)
[], self._tenant_id)
check_ns([scope4n_vrf], self.dn_t1_l2_n2, [], None)
# Remove second scoped v6 subnet from router.
remove(subnet62)
# Remove second unscoped v4 subnet from router, which should
# disconnect the default VRF from ext_net2.
remove(subnet43)
check([(net1, [], [subnet4i1, subnet61], None, None),
(net2, [], [subnet4n2, subnet62], None, None),
(net3, [], [subnet43, subnet63], None, None),
(net4, [], [subnet44, subnet64], None, None)],
[], None)
check_ns([main_vrf], self.dn_t1_l2_n2, [], None)
# REVISIT: Enable when non-isomorphic network routing is
# supported.
#
# Remove second scoped v6 subnet from router.
# remove(subnet62)
# check([(net1, [], [subnet4i1, subnet61], None, None),
# (net2, [], [subnet4n2, subnet62], None, None),
# (net3, [], [subnet43, subnet63], None, None),
# (net4, [], [subnet44, subnet64], None, None)],
# [], None)
# check_ns(...)
def test_shared_address_scope(self):
# Create shared scope as tenant_1.