Merge "[AIM] Prevent overlapping CIDRs in routed VRF" into stable/pike

This commit is contained in:
Zuul 2019-08-02 01:41:20 +00:00 committed by Gerrit Code Review
commit 9827010bc7
4 changed files with 279 additions and 0 deletions

View File

@ -59,6 +59,15 @@ apic_opts = [
help=("How many seconds for the polling thread on each "
"controller should wait before it updates the nova vm "
"name cache again.")),
cfg.BoolOpt('allow_routed_vrf_subnet_overlap',
default=False,
help=("Set to True to turn off checking for overlapping "
"subnets within a routed VRF when adding or removing "
"router interfaces. Overlapping subnets in a routed VRF "
"will result in ACI faults and lost connectivity, so "
"this should only be used temporarily to enable "
"cleaning up overlapping routed subnets created before "
"overlap checking was implemented.")),
]

View File

@ -85,3 +85,8 @@ class ExternalSubnetNotAllowed(exceptions.BadRequest):
message = _("Connecting port or subnet which is on external network "
"%(network_id)s as a router interface is not allowed. "
"External networks can only be used as router gateways.")
class SubnetOverlapInRoutedVRF(exceptions.BadRequest):
message = _("Subnets %(id1)s (%(cidr1)s) and %(id2)s (%(cidr2)s) mapped "
"to %(vrf)s overlap.")

View File

@ -246,6 +246,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self.l3_domain_dn = cfg.CONF.ml2_apic_aim.l3_domain_dn
self.apic_nova_vm_name_cache_update_interval = (cfg.CONF.ml2_apic_aim.
apic_nova_vm_name_cache_update_interval)
self.allow_routed_vrf_subnet_overlap = (
cfg.CONF.ml2_apic_aim.allow_routed_vrf_subnet_overlap)
if self.allow_routed_vrf_subnet_overlap:
LOG.warning("The allow_routed_vrf_subnet_overlap config option is "
"True, disabling checking for overlapping CIDRs "
"within a routed VRF. Overlapping CIDRs result in ACI "
"faults and loss of connectivity, so please eliminate "
"any existing overlap and set this option to False "
"(the default) as soon as possible.")
self._setup_nova_vm_update()
local_api.QUEUE_OUT_OF_PROCESS_NOTIFICATIONS = True
self._ensure_static_resources()
@ -1883,6 +1892,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
epg = self._get_network_l3out_ext_net(
network_db.aim_mapping)
# If we are using an unscoped VRF, raise exception if it has
# overlapping subnets, which could be due to this new
# interface itself or to movement of either topology.
if scope_id == NO_ADDR_SCOPE:
self._check_vrf_for_overlap(session, vrf, subnets)
if network_db.aim_mapping.epg_name:
# Create AIM Subnet(s) for each added Neutron subnet.
for subnet in subnets:
@ -2049,6 +2064,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self._move_topology(
context, aim_ctx, intf_topology, old_vrf, intf_vrf,
nets_to_notify, vrfs_to_notify)
self._check_vrf_for_overlap(session, intf_vrf)
# See if the router's topology must be moved.
router_topology = self._router_topology(session, router_db.id)
@ -2062,6 +2078,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self._move_topology(
context, aim_ctx, router_topology, old_vrf, router_vrf,
nets_to_notify, vrfs_to_notify)
self._check_vrf_for_overlap(session, router_vrf)
router_topo_moved = True
# If network is no longer connected to any router, make the
@ -2121,6 +2138,48 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if vrfs_to_notify:
self._notify_vrf_update(context, vrfs_to_notify)
def _check_vrf_for_overlap(self, session, vrf, new_subnets=None):
if self.allow_routed_vrf_subnet_overlap:
return
query = BAKERY(lambda s: s.query(
models_v2.Subnet.id,
models_v2.Subnet.cidr))
query += lambda q: q.join(
models_v2.IPAllocation,
models_v2.IPAllocation.subnet_id ==
models_v2.Subnet.id)
query += lambda q: q.join(
l3_db.RouterPort,
l3_db.RouterPort.port_id ==
models_v2.IPAllocation.port_id)
query += lambda q: q.join(
db.NetworkMapping,
db.NetworkMapping.network_id ==
models_v2.Subnet.network_id)
query += lambda q: q.filter(
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF,
db.NetworkMapping.vrf_name ==
sa.bindparam('vrf_name'),
db.NetworkMapping.vrf_tenant_name ==
sa.bindparam('vrf_tenant_name'))
subnets = [(id, netaddr.IPNetwork(cidr))
for id, cidr in query(session).params(
vrf_name=vrf.name,
vrf_tenant_name=vrf.tenant_name)]
if new_subnets:
subnets.extend(
[(s['id'], netaddr.IPNetwork(s['cidr']))
for s in new_subnets])
subnets.sort(key=lambda s: s[1])
for (id1, cidr1), (id2, cidr2) in zip(subnets[:-1], subnets[1:]):
if id2 != id1 and cidr2 in cidr1:
raise exceptions.SubnetOverlapInRoutedVRF(
id1=id1, cidr1=cidr1, id2=id2, cidr2=cidr2, vrf=vrf)
def bind_port(self, context):
port = context.current
LOG.debug("Attempting to bind port %(port)s on network %(net)s",

View File

@ -4109,6 +4109,212 @@ class TestTopology(ApicAimTestCase):
{'port_id': port_id,
l3_ext.OVERRIDE_NETWORK_ROUTING_TOPOLOGY_VALIDATION: True})
def test_reject_subnet_overlap(self):
# Create two routers.
router1_id = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']['id']
router2_id = self._make_router(
self.fmt, 'test-tenant', 'router2')['router']['id']
# Create two networks.
net1_resp = self._make_network(self.fmt, 'net1', True)
net2_resp = self._make_network(self.fmt, 'net2', True)
# Create 4 subnets on 1st network and add to 1st router.
subnet1a_id = self._make_subnet(
self.fmt, net1_resp, '10.1.0.1', '10.1.0.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1a_id})
subnet1b_id = self._make_subnet(
self.fmt, net1_resp, '10.2.0.1', '10.2.0.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1b_id})
subnet1c_id = self._make_subnet(
self.fmt, net1_resp, '10.3.0.1', '10.3.0.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1c_id})
subnet1d_id = self._make_subnet(
self.fmt, net1_resp, '10.4.0.1', '10.4.0.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1d_id})
# Create non-overlapping subnet on 2nd network and add to 2nd router.
subnet2a_id = self._make_subnet(
self.fmt, net2_resp, '10.1.1.2', '10.1.1.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2a_id})
# Verify that adding larger overlapping subnet on 2nd network
# to 2nd router is rejected due to overlapping CIDR within the
# project's default VRF.
subnet2b_id = self._make_subnet(
self.fmt, net2_resp, '10.2.0.2', '10.2.0.0/16')['subnet']['id']
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.add_router_interface,
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2b_id})
# Verify that adding identical overlapping subnet on 2nd
# network to 2nd router is rejected due to overlapping CIDR
# within the project's default VRF.
subnet2c_id = self._make_subnet(
self.fmt, net2_resp, '10.3.0.2', '10.3.0.0/24')['subnet']['id']
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.add_router_interface,
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2c_id})
# Verify that adding smaller overlapping subnet on 2nd network
# to 2nd router is rejected due to overlapping CIDR within the
# project's default VRF.
subnet2d_id = self._make_subnet(
self.fmt, net2_resp, '10.4.0.2', '10.4.0.0/26')['subnet']['id']
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.add_router_interface,
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2d_id})
# Create another non-overlapping subnet on 2nd network and add
# to 2nd router, ensuring unrouted subnets are not considered
# overlapping.
subnet2e_id = self._make_subnet(
self.fmt, net2_resp, '10.5.0.2', '10.5.0.0/24')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2e_id})
def test_reject_moved_subnet_overlap(self):
# Create isolated routed topology as tenant_1.
router1_id = self._make_router(
self.fmt, 'tenant_1', 'router1')['router']['id']
net1_resp = self._make_network(
self.fmt, 'net1', True, tenant_id='tenant_1')
subnet1_id = self._make_subnet(
self.fmt, net1_resp, '10.0.1.1', '10.0.1.0/24',
tenant_id='tenant_1')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1_id})
# Create overlapping isolated routed topology as tenant_2.
router2_id = self._make_router(
self.fmt, 'tenant_2', 'router2')['router']['id']
net2_resp = self._make_network(
self.fmt, 'net2', True, tenant_id='tenant_2')
subnet2_id = self._make_subnet(
self.fmt, net2_resp, '10.0.1.1', '10.0.1.0/24',
tenant_id='tenant_2')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2_id})
# Create shared network and non-overlapping subnet as tenant_2.
net3_resp = self._make_network(
self.fmt, 'net3', True, tenant_id='tenant_2', shared=True)
subnet3_id = self._make_subnet(
self.fmt, net3_resp, '10.0.3.1', '10.0.3.0/24',
tenant_id='tenant_2')['subnet']['id']
# Verify that adding tenant_2's shared subnet to tenant_1's
# topology is rejected due to tenant_1's overlapping CIDR
# being moved to tenant_2's default VRF.
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.add_router_interface,
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet3_id})
def test_reject_moved_back_subnet_overlap(self):
# Create isolated routed topology as tenant_1.
router1_id = self._make_router(
self.fmt, 'tenant_1', 'router1')['router']['id']
net1_resp = self._make_network(
self.fmt, 'net1', True, tenant_id='tenant_1')
subnet1_id = self._make_subnet(
self.fmt, net1_resp, '10.0.1.1', '10.0.1.0/24',
tenant_id='tenant_1')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1_id})
# Create shared network and non-overlapping subnet as tenant_2.
net2_resp = self._make_network(
self.fmt, 'net', True, tenant_id='tenant_2', shared=True)
subnet2_id = self._make_subnet(
self.fmt, net2_resp, '10.0.2.1', '10.0.2.0/24',
tenant_id='tenant_2')['subnet']['id']
# Create another router as tenant_1 and add subnet shared by tenant_2.
router2_id = self._make_router(
self.fmt, 'tenant_1', 'router2')['router']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2_id})
# Create and add subnet that overlaps isolated topology. This
# is allowed because router2's topology is using tenant_2's
# routed VRF.
net3_resp = self._make_network(
self.fmt, 'net3', True, tenant_id='tenant_1')
subnet3_id = self._make_subnet(
self.fmt, net3_resp, '10.0.1.1', '10.0.1.0/24',
tenant_id='tenant_1')['subnet']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet3_id})
# Create yet another router and add the overlapping subnet to
# this router too, so the network does not become unrouted
# when the subnet is later removed from router2.
router3_id = self._make_router(
self.fmt, 'tenant_1', 'router3')['router']['id']
port_id = self._make_port(
self.fmt, net3_resp['network']['id'],
fixed_ips=[{'subnet_id': subnet3_id, 'ip_address': '10.0.1.2'}]
)['port']['id']
self.l3_plugin.add_router_interface(
n_context.get_admin_context(), router3_id,
{'port_id': port_id})
# Verify that removing shared subnet from router2 is rejected
# due to overlapping CIDR in router topology being moved back
# to tenant_1's default VRF.
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.remove_router_interface,
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2_id})
# Verify that removing overlapping subnet from router2 is
# rejected due to overlapping CIDR in interface topology being
# moved back to tenant_1's default VRF.
self.assertRaises(
exceptions.SubnetOverlapInRoutedVRF,
self.l3_plugin.remove_router_interface,
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet3_id})
# Remove the overlapping subnet from router1's isolated
# topology.
self.l3_plugin.remove_router_interface(
n_context.get_admin_context(), router1_id,
{'subnet_id': subnet1_id})
# Remove the shared subnet from router2, which is no longer
# should result in overlapping CIDRs in tenant_1's default
# VRF.
self.l3_plugin.remove_router_interface(
n_context.get_admin_context(), router2_id,
{'subnet_id': subnet2_id})
def test_reject_routing_shared_networks_from_different_projects(self):
# Create router as tenant_1.
router_id = self._make_router(