[apic_aim] Complete mapping to AIM Subnet

Expose each AIM Subnet's distinguished name and synchronization state
on the Neutron router, in addition to on the Neutron subnet.

Build the AIM Subnet's display name using both the Neutron router and
subnet names, handling updates to either name. If the Neutron subnet's
name is empty, use its CIDR instead in the display name.

Change-Id: I697d490b76dea0f099d62aa153fc29c12b2e3c70
This commit is contained in:
Robert Kukura 2016-10-10 20:49:06 -04:00
parent 2d5067aeb7
commit 3707a62887
2 changed files with 175 additions and 69 deletions

View File

@ -28,6 +28,7 @@ from neutron.db import l3_db
from neutron.db import models_v2
from neutron.extensions import portbindings
from neutron import manager
from neutron.plugins.common import constants as pconst
from neutron.plugins.ml2 import driver_api as api
from opflexagent import constants as ofcst
from oslo_log import log
@ -72,6 +73,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self.name_mapper = apic_mapper.APICNameMapper(self.db, log)
self.aim = aim_manager.AimManager()
self._core_plugin = None
self._l3_plugin = None
self.aim_cfg_mgr = aim_cfg.ConfigManager(
aim_context.AimContext(db_api.get_session()),
host=aim_cfg.CONF.host)
@ -334,17 +336,18 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
LOG.info(_LI("Mapped network_id %(id)s to %(aname)s"),
{'id': network_id, 'aname': network_aname})
# TODO(rkukura): Combine subnet name and name of
# interfaced router to build display name for each mapped
# Subnet.
dname = aim_utils.sanitize_display_name(context.current['name'])
aim_ctx = aim_context.AimContext(session)
prefix_len = context.current['cidr'].split('/')[1]
subnet_id = context.current['id']
for intf in self._subnet_router_interfaces(session, subnet_id):
gw_ip = intf.ip_address
for gw_ip, router_id in self._subnet_router_ips(session,
subnet_id):
router_db = self.l3_plugin._get_router(context._plugin_context,
router_id)
dname = aim_utils.sanitize_display_name(
router_db.name + " - " +
(context.current['name'] or context.current['cidr']))
gw_ip_mask = gw_ip + '/' + prefix_len
aim_subnet = aim_resource.Subnet(tenant_name=
network_tenant_aname,
@ -382,8 +385,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
{'id': network_id, 'aname': network_aname})
subnet_id = subnet_db.id
for intf in self._subnet_router_interfaces(session, subnet_id):
gw_ip = intf.ip_address
for gw_ip, router_id in self._subnet_router_ips(session, subnet_id):
gw_ip_mask = gw_ip + '/' + prefix_len
aim_subnet = aim_resource.Subnet(tenant_name=network_tenant_aname,
bd_name=network_aname,
@ -578,7 +580,49 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
name=ROUTER_SUBJECT_NAME)
subject = self.aim.update(aim_ctx, subject, display_name=dname)
# REVISIT(rkukura): Update extension attributes?
# REVISIT(rkukura): Refactor to share common code below
# with extend_router_dict. Also consider using joins to
# fetch the subnet_db and network_db as part of the
# initial query.
for intf in (session.query(models_v2.IPAllocation).
join(models_v2.Port).
join(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF)):
subnet_db = (session.query(models_v2.Subnet).
filter_by(id=intf.subnet_id).
one())
prefix_len = subnet_db.cidr.split('/')[1]
dname = aim_utils.sanitize_display_name(
name + " - " + (subnet_db.name or subnet_db.cidr))
network_id = subnet_db.network_id
network_db = (session.query(models_v2.Network).
filter_by(id=network_id).
one())
network_tenant_id = network_db.tenant_id
network_tenant_aname = self.name_mapper.tenant(
session, network_tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': network_tenant_id,
'aname': network_tenant_aname})
network_aname = self.name_mapper.network(session, network_id)
LOG.debug("Mapped network_id %(id)s to %(aname)s",
{'id': network_id, 'aname': network_aname})
gw_ip = intf.ip_address
gw_ip_mask = gw_ip + '/' + prefix_len
aim_subnet = aim_resource.Subnet(tenant_name=
network_tenant_aname,
bd_name=network_aname,
gw_ip_mask=gw_ip_mask)
self.aim.update(aim_ctx, aim_subnet, display_name=dname)
def delete_router(self, context, current):
LOG.debug("APIC AIM MD deleting router: %s", current)
@ -643,12 +687,47 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
dist_names[cisco_apic_l3.CONTRACT_SUBJECT] = aim_subject.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_subject)
# See if this router has any attached interfaces.
if (session.query(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
count()):
# REVISIT(rkukura): Consider moving the SubnetPool query below
# into this loop, although that might be less efficient when
# many subnets are from the same pool.
active = False
for intf in (session.query(models_v2.IPAllocation).
join(models_v2.Port).
join(l3_db.RouterPort).
filter(l3_db.RouterPort.router_id == id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF)):
active = True
subnet_db = (session.query(models_v2.Subnet).
filter_by(id=intf.subnet_id).
one())
prefix_len = subnet_db.cidr.split('/')[1]
network_id = subnet_db.network_id
network_db = (session.query(models_v2.Network).
filter_by(id=network_id).
one())
network_tenant_id = network_db.tenant_id
network_tenant_aname = self.name_mapper.tenant(session,
network_tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': network_tenant_id, 'aname': network_tenant_aname})
network_aname = self.name_mapper.network(session, network_id)
LOG.debug("Mapped network_id %(id)s to %(aname)s",
{'id': network_id, 'aname': network_aname})
gw_ip = intf.ip_address
gw_ip_mask = gw_ip + '/' + prefix_len
aim_subnet = aim_resource.Subnet(tenant_name=network_tenant_aname,
bd_name=network_aname,
gw_ip_mask=gw_ip_mask)
dist_names[gw_ip] = aim_subnet.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_subnet)
if active:
# Find this router's IPv4 address scope if it has one, or
# else its IPv6 address scope.
scope_id = None
@ -663,7 +742,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).
distinct()):
LOG.debug("got pool_db: %s", pool_db)
if pool_db.ip_version == 4:
scope_id = pool_db.address_scope_id
break
@ -689,11 +767,6 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
dist_names[cisco_apic_l3.VRF] = aim_vrf.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_vrf)
# TODO(rkukura): Also include interfaced Subnets. This
# probably means splitting the above SubnetPool query to first
# query for subnets, add the corresponding AIM subnet, then
# check the subnet's subnetpool's address scope.
result[cisco_apic.DIST_NAMES] = dist_names
result[cisco_apic.SYNC_STATE] = sync_state
@ -736,10 +809,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
prefix_len = subnet['cidr'].split('/')[1]
gw_ip_mask = gw_ip + '/' + prefix_len
dname = aim_utils.sanitize_display_name(
router['name'] + " - " +
(subnet['name'] or subnet['cidr']))
aim_subnet = aim_resource.Subnet(tenant_name=network_tenant_aname,
bd_name=network_aname,
gw_ip_mask=gw_ip_mask,
display_name=subnet['name'])
display_name=dname)
aim_subnet = self.aim.create(aim_ctx, aim_subnet)
# Ensure network's EPG provides/consumes router's Contract.
@ -948,6 +1024,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self._core_plugin = manager.NeutronManager.get_plugin()
return self._core_plugin
@property
def l3_plugin(self):
if not self._l3_plugin:
plugins = manager.NeutronManager.get_service_plugins()
self._l3_plugin = plugins[pconst.L3_ROUTER_NAT]
return self._l3_plugin
def _merge_status(self, aim_ctx, sync_state, resource):
status = self.aim.get_status(aim_ctx, resource)
if not status:
@ -978,10 +1061,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if fixed_ip['subnet_id'] == subnet_id:
return fixed_ip['ip_address']
def _subnet_router_interfaces(self, session, subnet_id):
return (session.query(models_v2.IPAllocation).
def _subnet_router_ips(self, session, subnet_id):
return (session.query(models_v2.IPAllocation.ip_address,
l3_db.RouterPort.router_id).
join(models_v2.Port).
join(l3_db.RouterPort).
filter(
models_v2.IPAllocation.subnet_id == subnet_id,
l3_db.RouterPort.port_type ==

View File

@ -370,7 +370,7 @@ class TestAimMapping(ApicAimTestCase):
app_profile_name=self._app_profile_name,
should_exist=False)
def _check_subnet(self, subnet, net, expected_gw_ips, unexpected_gw_ips):
def _check_subnet(self, subnet, net, expected_gws, unexpected_gw_ips):
prefix_len = subnet['cidr'].split('/')[1]
# REVISIT(rkukura): Check AIM Tenant here?
@ -378,7 +378,7 @@ class TestAimMapping(ApicAimTestCase):
net_aname = self._map_name(net)
for gw_ip in expected_gw_ips:
for gw_ip, router in expected_gws:
gw_ip_mask = gw_ip + '/' + prefix_len
aim_subnet = self._get_subnet(gw_ip_mask,
net_aname,
@ -387,7 +387,10 @@ class TestAimMapping(ApicAimTestCase):
self.assertEqual(net_aname, aim_subnet.bd_name)
self.assertEqual(gw_ip_mask, aim_subnet.gw_ip_mask)
self.assertEqual('private', aim_subnet.scope)
self.assertEqual(subnet['name'], aim_subnet.display_name)
display_name = ("%s - %s" %
(router['name'],
(subnet['name'] or subnet['cidr'])))
self.assertEqual(display_name, aim_subnet.display_name)
self._check_dn(subnet, aim_subnet, gw_ip)
for gw_ip in unexpected_gw_ips:
@ -427,8 +430,8 @@ class TestAimMapping(ApicAimTestCase):
self._tenant_name,
should_exist=False)
def _check_router(self, router, orig_router=None, active=False,
scope=None):
def _check_router(self, router, expected_gw_ips, unexpected_gw_ips,
orig_router=None, scope=None):
orig_router = orig_router or router
# REVISIT(rkukura): Check AIM Tenant here?
@ -455,9 +458,7 @@ class TestAimMapping(ApicAimTestCase):
self._check_any_filter()
# TODO(rkukura): Once AIM Subnets are exposed on router, pass
# in expected_gw_ips and use instead of this active flag.
if active:
if expected_gw_ips:
if scope:
vrf_aname = self._map_name(scope)
vrf_dname = scope['name']
@ -483,6 +484,17 @@ class TestAimMapping(ApicAimTestCase):
else:
self._check_no_dn(router, 'VRF')
# The AIM Subnets are validated in _check_subnet, so just
# check that their DNs are present and valid.
dist_names = router.get('apic:distinguished_names')
for gw_ip in expected_gw_ips:
self.assertIn(gw_ip, dist_names)
aim_subnet = self._find_by_dn(dist_names[gw_ip],
aim_resource.Subnet)
self.assertIsNotNone(aim_subnet)
for gw_ip in unexpected_gw_ips:
self.assertNotIn(gw_ip, dist_names)
def _check_router_deleted(self, router):
aname = self._map_name(router)
@ -585,16 +597,16 @@ class TestAimMapping(ApicAimTestCase):
orig_router = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']
router_id = orig_router['id']
self._check_router(orig_router)
self._check_router(orig_router, [], [])
# Test show.
router = self._show('routers', router_id)['router']
self._check_router(router)
self._check_router(router, [], [])
# Test update.
data = {'router': {'name': 'newnameforrouter'}}
router = self._update('routers', router_id, data)['router']
self._check_router(router, orig_router)
self._check_router(router, [], [], orig_router)
# Test delete.
self._delete('routers', router_id)
@ -602,10 +614,10 @@ class TestAimMapping(ApicAimTestCase):
def test_router_interface(self):
# Create router.
router = self._make_router(
orig_router = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']
router_id = router['id']
self._check_router(router)
router_id = orig_router['id']
self._check_router(orig_router, [], [])
# Create network.
net_resp = self._make_network(self.fmt, 'net1', True)
@ -634,7 +646,7 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True)
self._check_router(router, [gw1_ip], [])
# Check network.
net = self._show('networks', net_id)['network']
@ -642,7 +654,7 @@ class TestAimMapping(ApicAimTestCase):
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
@ -651,7 +663,13 @@ class TestAimMapping(ApicAimTestCase):
# Test subnet update.
data = {'subnet': {'name': 'newnameforsubnet'}}
subnet = self._update('subnets', subnet1_id, data)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Test router update.
data = {'router': {'name': 'newnameforrouter'}}
router = self._update('routers', router_id, data)['router']
self._check_router(router, [gw1_ip], [], orig_router)
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Add subnet2 to router by port.
fixed_ips = [{'subnet_id': subnet2_id, 'ip_address': gw2_ip}]
@ -663,19 +681,19 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True)
self._check_router(router, [gw1_ip, gw2_ip], [], orig_router)
# Check network.
net = self._show('networks', net_id)['network']
self._check_network(net, routers=[router])
self._check_network(net, routers=[orig_router])
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
self._check_subnet(subnet, net, [gw2_ip], [])
self._check_subnet(subnet, net, [(gw2_ip, router)], [])
# Remove subnet1 from router by subnet.
info = self.l3_plugin.remove_router_interface(
@ -684,11 +702,11 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True)
self._check_router(router, [gw2_ip], [gw1_ip], orig_router)
# Check network.
net = self._show('networks', net_id)['network']
self._check_network(net, routers=[router])
self._check_network(net, routers=[orig_router])
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
@ -696,7 +714,7 @@ class TestAimMapping(ApicAimTestCase):
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
self._check_subnet(subnet, net, [gw2_ip], [])
self._check_subnet(subnet, net, [(gw2_ip, router)], [])
# Remove subnet2 from router by port.
info = self.l3_plugin.remove_router_interface(
@ -705,7 +723,7 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router)
self._check_router(router, [], [gw1_ip, gw2_ip], orig_router)
# Check network.
net = self._show('networks', net_id)['network']
@ -738,10 +756,10 @@ class TestAimMapping(ApicAimTestCase):
pool_id = pool['id']
# Create router.
router = self._make_router(
orig_router = self._make_router(
self.fmt, 'test-tenant', 'router1')['router']
router_id = router['id']
self._check_router(router, scope=scope)
router_id = orig_router['id']
self._check_router(orig_router, [], [], scope=scope)
# Create network.
net_resp = self._make_network(self.fmt, 'net1', True)
@ -772,7 +790,7 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True, scope=scope)
self._check_router(router, [gw1_ip], [], scope=scope)
# Check network.
net = self._show('networks', net_id)['network']
@ -780,7 +798,7 @@ class TestAimMapping(ApicAimTestCase):
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
@ -789,7 +807,13 @@ class TestAimMapping(ApicAimTestCase):
# Test subnet update.
data = {'subnet': {'name': 'newnameforsubnet'}}
subnet = self._update('subnets', subnet1_id, data)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Test router update.
data = {'router': {'name': 'newnameforrouter'}}
router = self._update('routers', router_id, data)['router']
self._check_router(router, [gw1_ip], [], orig_router, scope)
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Add subnet2 to router by port.
fixed_ips = [{'subnet_id': subnet2_id, 'ip_address': gw2_ip}]
@ -801,19 +825,19 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True, scope=scope)
self._check_router(router, [gw1_ip, gw2_ip], [], orig_router, scope)
# Check network.
net = self._show('networks', net_id)['network']
self._check_network(net, routers=[router], scope=scope)
self._check_network(net, routers=[orig_router], scope=scope)
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
self._check_subnet(subnet, net, [gw1_ip], [])
self._check_subnet(subnet, net, [(gw1_ip, router)], [])
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
self._check_subnet(subnet, net, [gw2_ip], [])
self._check_subnet(subnet, net, [(gw2_ip, router)], [])
# Remove subnet1 from router by subnet.
info = self.l3_plugin.remove_router_interface(
@ -822,11 +846,11 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, active=True, scope=scope)
self._check_router(router, [gw2_ip], [gw1_ip], orig_router, scope)
# Check network.
net = self._show('networks', net_id)['network']
self._check_network(net, routers=[router], scope=scope)
self._check_network(net, routers=[orig_router], scope=scope)
# Check subnet1.
subnet = self._show('subnets', subnet1_id)['subnet']
@ -834,7 +858,7 @@ class TestAimMapping(ApicAimTestCase):
# Check subnet2.
subnet = self._show('subnets', subnet2_id)['subnet']
self._check_subnet(subnet, net, [gw2_ip], [])
self._check_subnet(subnet, net, [(gw2_ip, router)], [])
# Remove subnet2 from router by port.
info = self.l3_plugin.remove_router_interface(
@ -843,7 +867,7 @@ class TestAimMapping(ApicAimTestCase):
# Check router.
router = self._show('routers', router_id)['router']
self._check_router(router, scope=scope)
self._check_router(router, [], [gw1_ip, gw2_ip], orig_router, scope)
# Check network.
net = self._show('networks', net_id)['network']
@ -1058,10 +1082,9 @@ class TestSyncState(ApicAimTestCase):
context.get_admin_context(), router['id'],
{'subnet_id': subnet['id']})
# TODO(rkukura): Enable when exposing Subnets on router is implemented.
# router = self._show('routers', router['id'])['router']
# self.assertEqual(expected_state,
# router['apic:synchronization_state'])
router = self._show('routers', router['id'])['router']
self.assertEqual(expected_state,
router['apic:synchronization_state'])
subnet = self._show('subnets', subnet['id'])['subnet']
self.assertEqual(expected_state, subnet['apic:synchronization_state'])