[apic_aim] Map neutron resources to AIM, part 5

Complete basic east/west routing. Enables routing on BDs of routed
networks and associates those BDs with the address scope's VRF if
applicable, or else with a per-tenant default routed VRF. The selected
VRF is exposed via the extended attributes of the Neutron network and
router resources.

Validation of routing topologies will be implemented in a follow-on
patch.

Change-Id: Ic7396e5ebbc466ea5be0028931b31bdbab9833e6
This commit is contained in:
Robert Kukura
2016-09-20 22:51:35 -04:00
parent d5a7488e5f
commit 18a0972197
3 changed files with 477 additions and 119 deletions

View File

@@ -23,6 +23,7 @@ from neutron._i18n import _LW
from neutron.agent.linux import dhcp
from neutron.common import constants as n_constants
from neutron.common import rpc as n_rpc
from neutron.db import address_scope_db
from neutron.db import l3_db
from neutron.db import models_v2
from neutron.extensions import portbindings
@@ -41,12 +42,18 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
LOG = log.getLogger(__name__)
# REVISIT(rkukura): Consider making these APIC name constants
# configurable, although changing them would break an existing
# deployment.
AP_NAME = 'NeutronAP'
ANY_FILTER_NAME = 'AnyFilter'
ANY_FILTER_ENTRY_NAME = 'AnyFilterEntry'
DEFAULT_VRF_NAME = 'DefaultVRF'
UNROUTED_VRF_NAME = 'UnroutedVRF'
COMMON_TENANT_NAME = 'common'
ROUTER_SUBJECT_NAME = 'route'
AGENT_TYPE_DVS = 'DVS agent'
VIF_TYPE_DVS = 'dvs'
PROMISCUOUS_TYPES = [n_constants.DEVICE_OWNER_DHCP,
@@ -54,6 +61,8 @@ PROMISCUOUS_TYPES = [n_constants.DEVICE_OWNER_DHCP,
class ApicMechanismDriver(api_plus.MechanismDriver):
# TODO(rkukura): Derivations of tenant_aname throughout need to
# take sharing into account.
def __init__(self):
LOG.info(_LI("APIC AIM MD __init__"))
@@ -221,33 +230,89 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self.name_mapper.delete_apic_name(session, id)
def extend_network_dict(self, session, base_model, result):
def extend_network_dict(self, session, network_db, result):
LOG.debug("APIC AIM MD extending dict for network: %s", result)
tenant_id = result['tenant_id']
# REVISIT(rkukura): Consider optimizing this method by
# persisting the network->VRF relationship.
sync_state = cisco_apic.SYNC_SYNCED
dist_names = {}
aim_ctx = aim_context.AimContext(session)
tenant_id = network_db.tenant_id
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = result['id']
name = result['name']
id = network_db.id
name = network_db.name
aname = self.name_mapper.network(session, id, name)
LOG.debug("Mapped network_id %(id)s with name %(name)s to %(aname)s",
{'id': id, 'name': name, 'aname': aname})
bd = aim_resource.BridgeDomain(tenant_name=tenant_aname,
name=aname)
aim_bd = aim_resource.BridgeDomain(tenant_name=tenant_aname,
name=aname)
dist_names[cisco_apic.BD] = aim_bd.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_bd)
epg = aim_resource.EndpointGroup(tenant_name=tenant_aname,
app_profile_name=AP_NAME,
name=aname)
aim_epg = aim_resource.EndpointGroup(tenant_name=tenant_aname,
app_profile_name=AP_NAME,
name=aname)
dist_names[cisco_apic.EPG] = aim_epg.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_epg)
aim_ctx = aim_context.AimContext(session)
sync_state = cisco_apic.SYNC_SYNCED
sync_state = self._merge_status(aim_ctx, sync_state, bd)
sync_state = self._merge_status(aim_ctx, sync_state, epg)
result[cisco_apic.DIST_NAMES] = {cisco_apic.BD: bd.dn,
cisco_apic.EPG: epg.dn}
# See if this network is interfaced to any routers.
rp = (session.query(l3_db.RouterPort).
join(models_v2.Port).
filter(models_v2.Port.network_id == network_db.id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).first())
if rp:
# A network is constrained to only one subnetpool per
# address family. To support both single and dual stack,
# use the IPv4 address scope's VRF if it exists, and
# otherwise use the IPv6 address scope's VRF. For dual
# stack, the plan is for identity NAT to move IPv6 traffic
# from the IPv4 address scope's VRF to the IPv6 address
# scope's VRF.
#
# REVISIT(rkukura): Ignore subnets that are not attached
# to any router, or maybe just do a query joining
# RouterPorts, Ports, Subnets, SubnetPools and
# AddressScopes.
pool_dbs = {subnet.subnetpool for subnet in network_db.subnets
if subnet.subnetpool}
scope_id = None
for pool_db in pool_dbs:
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
if scope_id:
scope_db = self._scope_by_id(session, scope_id)
scope_tenant_id = scope_db.tenant_id
vrf_tenant_aname = self.name_mapper.tenant(session,
scope_tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': scope_tenant_id, 'aname': vrf_tenant_aname})
vrf_aname = self.name_mapper.address_scope(session, scope_id)
LOG.debug("Mapped address_scope_id %(id)s to %(aname)s",
{'id': scope_id, 'aname': vrf_aname})
else:
vrf_tenant_aname = tenant_aname # REVISIT(rkukura)
vrf_aname = DEFAULT_VRF_NAME
else:
vrf_tenant_aname = COMMON_TENANT_NAME
vrf_aname = UNROUTED_VRF_NAME
aim_vrf = aim_resource.VRF(tenant_name=vrf_tenant_aname,
name=vrf_aname)
dist_names[cisco_apic.VRF] = aim_vrf.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_vrf)
result[cisco_apic.DIST_NAMES] = dist_names
result[cisco_apic.SYNC_STATE] = sync_state
def create_subnet_precommit(self, context):
@@ -262,10 +327,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
session = context._plugin_context.session
network_id = context.current['network_id']
network = self.plugin.get_network(context._plugin_context,
network_id)
network_db = self.plugin._get_network(context._plugin_context,
network_id)
network_tenant_id = network['tenant_id']
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",
@@ -298,15 +363,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
# Neutron subnets are unmapped from AIM Subnets as they are
# removed from routers.
def extend_subnet_dict(self, session, base_model, result):
def extend_subnet_dict(self, session, subnet_db, result):
LOG.debug("APIC AIM MD extending dict for subnet: %s", result)
sync_state = cisco_apic.SYNC_SYNCED
dist_names = {}
prefix_len = result['cidr'].split('/')[1]
aim_ctx = aim_context.AimContext(session)
network_id = result['network_id']
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())
@@ -321,7 +387,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
LOG.debug("Mapped network_id %(id)s to %(aname)s",
{'id': network_id, 'aname': network_aname})
subnet_id = result['id']
subnet_id = subnet_db.id
for intf in self._subnet_router_interfaces(session, subnet_id):
gw_ip = intf.ip_address
gw_ip_mask = gw_ip + '/' + prefix_len
@@ -334,6 +400,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
result[cisco_apic.DIST_NAMES] = dist_names
result[cisco_apic.SYNC_STATE] = sync_state
# TODO(rkukura): Implement update_subnetpool_precommit to handle
# changing subnetpool's address_scope_id.
def create_address_scope_precommit(self, context):
LOG.debug("APIC AIM MD creating address scope: %s", context.current)
@@ -416,28 +485,31 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self.name_mapper.delete_apic_name(session, id)
def extend_address_scope_dict(self, session, base_model, result):
def extend_address_scope_dict(self, session, scope_db, result):
LOG.debug("APIC AIM MD extending dict for address scope: %s", result)
tenant_id = result['tenant_id']
sync_state = cisco_apic.SYNC_SYNCED
dist_names = {}
aim_ctx = aim_context.AimContext(session)
tenant_id = scope_db.tenant_id
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = result['id']
name = result['name']
id = scope_db.id
name = scope_db.name
aname = self.name_mapper.address_scope(session, id, name)
LOG.debug("Mapped address_scope_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
vrf = aim_resource.VRF(tenant_name=tenant_aname,
name=aname)
aim_vrf = aim_resource.VRF(tenant_name=tenant_aname,
name=aname)
dist_names[cisco_apic.VRF] = aim_vrf.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_vrf)
aim_ctx = aim_context.AimContext(session)
sync_state = cisco_apic.SYNC_SYNCED
sync_state = self._merge_status(aim_ctx, sync_state, vrf)
result[cisco_apic.DIST_NAMES] = {cisco_apic.VRF: vrf.dn}
result[cisco_apic.DIST_NAMES] = dist_names
result[cisco_apic.SYNC_STATE] = sync_state
def create_router(self, context, current):
@@ -544,37 +616,91 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
self.name_mapper.delete_apic_name(session, id)
def extend_router_dict(self, session, base_model, result):
def extend_router_dict(self, session, router_db, result):
LOG.debug("APIC AIM MD extending dict for router: %s", result)
tenant_id = result['tenant_id']
# REVISIT(rkukura): Consider optimizing this method by
# persisting the router->VRF relationship.
sync_state = cisco_apic.SYNC_SYNCED
dist_names = {}
aim_ctx = aim_context.AimContext(session)
tenant_id = router_db.tenant_id
tenant_aname = self.name_mapper.tenant(session, tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': tenant_id, 'aname': tenant_aname})
id = result['id']
name = result['name']
id = router_db.id
name = router_db.name
aname = self.name_mapper.router(session, id, name)
LOG.debug("Mapped router_id %(id)s with name %(name)s to "
"%(aname)s",
{'id': id, 'name': name, 'aname': aname})
contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname)
aim_contract = aim_resource.Contract(tenant_name=tenant_aname,
name=aname)
dist_names[cisco_apic_l3.CONTRACT] = aim_contract.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_contract)
subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME)
aim_subject = aim_resource.ContractSubject(tenant_name=tenant_aname,
contract_name=aname,
name=ROUTER_SUBJECT_NAME)
dist_names[cisco_apic_l3.CONTRACT_SUBJECT] = aim_subject.dn
sync_state = self._merge_status(aim_ctx, sync_state, aim_subject)
# TODO(rkukura): Also include VRF and interfaced Subnets.
# 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()):
# Find this router's IPv4 address scope if it has one, or
# else its IPv6 address scope.
scope_id = None
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 == id,
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
elif pool_db.ip_version == 6:
scope_id = pool_db.address_scope_id
if scope_id:
scope_db = self._scope_by_id(session, scope_id)
scope_tenant_id = scope_db.tenant_id
vrf_tenant_aname = self.name_mapper.tenant(session,
scope_tenant_id)
LOG.debug("Mapped tenant_id %(id)s to %(aname)s",
{'id': scope_tenant_id, 'aname': vrf_tenant_aname})
aim_ctx = aim_context.AimContext(session)
sync_state = cisco_apic.SYNC_SYNCED
sync_state = self._merge_status(aim_ctx, sync_state, contract)
sync_state = self._merge_status(aim_ctx, sync_state, subject)
result[cisco_apic.DIST_NAMES] = {cisco_apic_l3.CONTRACT: contract.dn,
cisco_apic_l3.CONTRACT_SUBJECT:
subject.dn}
vrf_aname = self.name_mapper.address_scope(session, scope_id)
LOG.debug("Mapped address_scope_id %(id)s to %(aname)s",
{'id': scope_id, 'aname': vrf_aname})
else:
vrf_tenant_aname = tenant_aname # REVISIT(rkukura)
vrf_aname = DEFAULT_VRF_NAME
aim_vrf = aim_resource.VRF(tenant_name=vrf_tenant_aname,
name=vrf_aname)
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
def add_router_interface(self, context, router, port, subnets):
@@ -585,9 +711,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
session = context.session
network_id = port['network_id']
network = self.plugin.get_network(context, network_id)
network_db = self.plugin._get_network(context, network_id)
network_tenant_id = network['tenant_id']
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",
@@ -641,8 +767,42 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
aim_epg = self.aim.update(aim_ctx, aim_epg,
provided_contract_names=contracts)
# TODO(rkukura): Implement selecting/setting VRF and
# validating topology.
# Find routers with interfaces to this network. The current
# interface is not included, because the RouterPort has not
# yet been added to the DB session.
router_ids = [r[0] for r in
session.query(l3_db.RouterPort.router_id).
join(models_v2.Port).
filter(models_v2.Port.network_id == network_id,
l3_db.RouterPort.port_type ==
n_constants.DEVICE_OWNER_ROUTER_INTF).distinct()]
# TODO(rkukura): Validate topology.
if not router_ids:
# Enable routing for BD and set VRF.
subnetpool_id = subnets[0]['subnetpool_id']
if subnetpool_id:
subnetpool_db = self.plugin._get_subnetpool(context,
subnetpool_id)
address_scope_id = subnetpool_db.address_scope_id
if address_scope_id:
vrf_aname = self.name_mapper.address_scope(
session, address_scope_id)
LOG.debug("Mapped address_scope_id %(id)s to %(aname)s",
{'id': id, 'aname': vrf_aname})
else:
vrf_aname = self._get_default_vrf(
aim_ctx, router_tenant_aname).name
else:
vrf_aname = self._get_default_vrf(
aim_ctx, router_tenant_aname).name
aim_bd = aim_resource.BridgeDomain(
tenant_name=network_tenant_aname, name=network_aname)
aim_bd = self.aim.update(aim_ctx, aim_bd, enable_routing=True,
vrf_name=vrf_aname)
def remove_router_interface(self, context, router_id, port_db, subnets):
LOG.debug("APIC AIM MD removing subnets %(subnets)s from router "
@@ -652,9 +812,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
session = context.session
network_id = port_db.network_id
network = self.plugin.get_network(context, network_id)
network_db = self.plugin._get_network(context, network_id)
network_tenant_id = network['tenant_id']
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",
@@ -718,8 +878,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
aim_epg = self.aim.update(aim_ctx, aim_epg,
provided_contract_names=contracts)
# TODO(rkukura): If network no longer connected to any router,
# make the network's BD unrouted.
# If network is no longer connected to any router, make the
# network's BD unrouted.
if not router_ids:
vrf = self._get_unrouted_vrf(aim_ctx)
aim_bd = aim_resource.BridgeDomain(
tenant_name=network_tenant_aname, name=network_aname)
aim_bd = self.aim.update(aim_ctx, aim_bd, enable_routing=False,
vrf_name=vrf.name)
def bind_port(self, context):
LOG.debug("Attempting to bind port %(port)s on network %(net)s",
@@ -974,6 +1141,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
n_constants.DEVICE_OWNER_ROUTER_INTF
))
def _scope_by_id(self, session, scope_id):
return (session.query(address_scope_db.AddressScope).
filter_by(id=scope_id).
one())
def _get_common_tenant(self, aim_ctx):
attrs = aim_resource.Tenant(name=COMMON_TENANT_NAME,
display_name='Common Tenant')
@@ -987,9 +1159,19 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
tenant = self._get_common_tenant(aim_ctx)
attrs = aim_resource.VRF(tenant_name=tenant.name,
name=UNROUTED_VRF_NAME,
display_name='Common Unrouted Context')
display_name='Common Unrouted VRF')
vrf = self.aim.get(aim_ctx, attrs)
if not vrf:
LOG.info(_LI("Creating common unrouted VRF"))
vrf = self.aim.create(aim_ctx, attrs)
return vrf
def _get_default_vrf(self, aim_ctx, tenant_aname):
attrs = aim_resource.VRF(tenant_name=tenant_aname,
name=DEFAULT_VRF_NAME,
display_name='Default Routed VRF')
vrf = self.aim.get(aim_ctx, attrs)
if not vrf:
LOG.info(_LI("Creating default VRF for %s"), tenant_aname)
vrf = self.aim.create(aim_ctx, attrs)
return vrf