[AIM] External-connecitvity for unscoped shared networks

Networks that don't use an address-scope can
move between VRFs as they are connected/disconnected
via routers to other networks. Accordingly, the
external-connectivity of the VRFs needs to be
updated. This is required only for the case
where the "router topology" changes VRF - the old
VRF loses external connectivity available through
the router whereas the new VRF gains the same.

Change-Id: I7b6d22405a8c7b0a93a88e7bc186ab7c158757da
Signed-off-by: Amit Bose <amitbose@gmail.com>
(cherry picked from commit d1044e846d)
This commit is contained in:
Amit Bose
2017-01-18 17:26:35 -08:00
parent 607029610a
commit 46a2cde8d7
2 changed files with 126 additions and 8 deletions

View File

@@ -871,6 +871,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if different_router and different_subnet:
raise UnsupportedRoutingTopology()
router_topo_moved = False
# Ensure that all the BDs and EPGs in the resulting topology
# are mapped under the same Tenant so that the BDs can all
# reference the topology's VRF and the EPGs can all provide
@@ -920,6 +922,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology(
aim_ctx, router_topology, router_vrf, vrf)
router_topo_moved = True
# REVISIT: Delete router_vrf if no longer used?
elif router_shared_net:
# Router topology has shared network, so move
@@ -980,15 +983,25 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
epg = self.aim.update(aim_ctx, epg,
provided_contract_names=contracts)
# If external-gateway is set, handle external-connectivity changes.
if router.gw_port_id:
net = self.plugin.get_network(context,
router.gw_port.network_id)
# If this is first interface-port, then that will determine
# the VRF for this router. Setup exteral-connectivity for VRF
# if external-gateway is set.
# the VRF for this router. Setup external-connectivity for VRF.
if not router_intf_count:
net = self.plugin.get_network(context,
router.gw_port.network_id)
self._manage_external_connectivity(context, router, None, net,
vrf)
elif router_topo_moved:
# Router moved from router_vrf to vrf, so
# 1. Update router_vrf's external connectivity to exclude
# router
# 2. Update vrf's external connectivity to include router
self._manage_external_connectivity(context, router, net, None,
router_vrf)
self._manage_external_connectivity(context, router, None, net,
vrf)
# SNAT information of ports on the subnet will change because
# of router interface addition. Send a port update so that it may
# be recalculated.
@@ -1052,6 +1065,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
epg = self.aim.update(aim_ctx, epg,
provided_contract_names=contracts)
router_topo_moved = False
# If unscoped topologies have split, move VRFs as needed.
if scope_id == NO_ADDR_SCOPE:
# If the interface's network has not become unrouted, see
@@ -1075,24 +1089,36 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if old_vrf.identity != router_vrf.identity:
self._move_topology(
aim_ctx, router_topology, old_vrf, router_vrf)
router_topo_moved = True
# If network is no longer connected to any router, make the
# network's BD unrouted.
if not router_ids:
self._dissassociate_network_from_vrf(aim_ctx, network_db, old_vrf)
# If external-gateway is set, handle external-connectivity changes.
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):
net = self.plugin.get_network(context,
router_db.gw_port.network_id)
self._manage_external_connectivity(
context, router_db, net, None, old_vrf)
self._delete_snat_ip_ports_if_reqd(context, net['id'],
router_id)
elif router_topo_moved:
# Router moved from old_vrf to router_vrf, so
# 1. Update old_vrf's external connectivity to exclude router
# 2. Update router_vrf's external connectivity to include
# router
self._manage_external_connectivity(context, router_db, net,
None, old_vrf)
self._manage_external_connectivity(context, router_db, None,
net, router_vrf)
# SNAT information of ports on the subnet will change because
# of router interface removal. Send a port update so that it may
# be recalculated.
@@ -1925,6 +1951,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
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.
vrf = aim_resource.VRF(tenant_name=vrf.tenant_name, name=vrf.name)
rtr_dbs = self._get_routers_for_vrf(session, vrf)
ext_db = extension_db.ExtensionDbMixin()

View File

@@ -2828,8 +2828,6 @@ class TestExternalConnectivityBase(object):
for router, subnets, addr_scope in router_list:
if addr_scope:
a_vrf.name = addr_scope['id']
else:
a_vrf.display_name = 'DefaultRoutedVRF'
contract = router['id']
a_ext_net.provided_contract_names.append(contract)
a_ext_net.provided_contract_names.extend(
@@ -3240,6 +3238,92 @@ class TestExternalConnectivityBase(object):
{'router': {'external_gateway_info': {}}})
mock_notif.assert_has_calls(port_calls, any_order=True)
def test_shared_unscoped_network(self):
# 0. Initial state: Router r1 is connected to external-network
# 1. Create unshared network net1 in tenant tenant_1, then connect
# it to router r1
# 2. Create shared network net2 in tenant tenant_2, then connect
# it to router r1
# 3. Create unshared network net3 in tenant test-tenant, then connect
# it to router r1
# 4. Disconnect net3 from r1
# 5. Disconnect net2 from r1
# 6. Disconnect net1 from r1
cv = self.mock_ns.connect_vrf
dv = self.mock_ns.disconnect_vrf
ext_net1 = self._make_ext_network('ext-net1',
dn='uni/tn-t1/out-l1/instP-n1')
self._make_subnet(
self.fmt, {'network': ext_net1}, '100.100.100.1',
'100.100.100.0/24')
router = self._make_router(
self.fmt, ext_net1['tenant_id'], 'router1',
external_gateway_info={'network_id': ext_net1['id']})['router']
cv.assert_not_called()
dv.assert_not_called()
contract = router['id']
a_ext_net1 = aim_resource.ExternalNetwork(
tenant_name='t1', l3out_name='l1', name='n1',
provided_contract_names=[contract],
consumed_contract_names=[contract])
a_ext_net1_no_contracts = aim_resource.ExternalNetwork(
tenant_name='t1', l3out_name='l1', name='n1')
# 1. Create unshared network net1 in tenant tenant_1, then connect
# it to router r1
net1 = self._make_network(self.fmt, 'net1', True,
tenant_id='tenant_1')['network']
sub1 = self._make_subnet(self.fmt, {'network': net1},
'10.10.10.1', '10.10.10.0/24')['subnet']
self._router_interface_action('add', router['id'], sub1['id'], None)
a_vrf1 = aim_resource.VRF(tenant_name='tenant_1', name='DefaultVRF')
cv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf1)
dv.assert_not_called()
# 2. Create shared network net2 in tenant tenant_2, then connect
# it to router r1
self.mock_ns.reset_mock()
net2 = self._make_network(self.fmt, 'net2', True,
tenant_id='tenant_2', shared=True)['network']
sub2 = self._make_subnet(self.fmt, {'network': net2},
'20.20.20.1', '20.20.20.0/24')['subnet']
self._router_interface_action('add', router['id'], sub2['id'], None)
a_vrf2 = aim_resource.VRF(tenant_name='tenant_2', name='DefaultVRF')
cv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf2)
dv.assert_called_once_with(mock.ANY, a_ext_net1_no_contracts, a_vrf1)
# 3. Create unshared network net3 in tenant test-tenant, then connect
# it to router r1
self.mock_ns.reset_mock()
net3 = self._make_network(self.fmt, 'net3', True,
tenant_id='test-tenant')['network']
sub3 = self._make_subnet(self.fmt, {'network': net3},
'30.30.30.1', '30.30.30.0/24')['subnet']
self._router_interface_action('add', router['id'], sub3['id'], None)
cv.assert_not_called()
dv.assert_not_called()
# 4. Disconnect net3 from r1
self.mock_ns.reset_mock()
self._router_interface_action('remove', router['id'], sub3['id'], None)
cv.assert_not_called()
dv.assert_not_called()
# 5. Disconnect net2 from r1
self.mock_ns.reset_mock()
self._router_interface_action('remove', router['id'], sub2['id'], None)
cv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf1)
dv.assert_called_once_with(mock.ANY, a_ext_net1_no_contracts, a_vrf2)
# 6. Disconnect net1 from r1
self.mock_ns.reset_mock()
self._router_interface_action('remove', router['id'], sub1['id'], None)
cv.assert_not_called()
dv.assert_called_once_with(mock.ANY, a_ext_net1_no_contracts, a_vrf1)
class TestExternalDistributedNat(TestExternalConnectivityBase,
ApicAimTestCase):
@@ -3257,6 +3341,10 @@ class TestExternalNoNat(TestExternalConnectivityBase,
tenant_1 = 't1'
tenant_2 = 'common'
def test_shared_unscoped_network(self):
# Skip test since the topology tested is not valid with no-NAT
pass
class TestSnatIpAllocation(ApicAimTestCase):