Send VRF notifications

The neutron-opflex-agent needs to be notified when the
subnets of a VRF have changed. This happens under the
following conditions:
1) When a subnetpool referencing a VRF is created, deleted, or
   has its prefixes updated
2) Whenever subnets on networks whose BD is associated with
   the unrouted VRF or any tenant's default VRF are created or deleted
3) When unscoped subnets change VRFs (happens as a result of
   attaching and detaching interfaces on a router)

Change-Id: I241df94dd0b2d96052742d2885d185f407a2e6b6
This commit is contained in:
Thomas Bachman 2019-04-05 23:27:29 +00:00
parent f617775213
commit 71f5d5a2ae
2 changed files with 682 additions and 402 deletions

View File

@ -1023,9 +1023,29 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if subnets_size > 1:
raise exceptions.OnlyOneSubnetInSVINetwork()
if network_db.aim_mapping:
# Provide VRF notifications if creating subnets in
# unscoped networks.
# REVISIT: We may need to handle VRF notifications for
# external networks as well.
vrf = self._get_network_vrf(network_db.aim_mapping)
if (vrf and (self._is_unrouted_vrf(vrf) or
self._is_default_vrf(vrf))
and not network_db.external):
vrfs_to_notify = self._add_vrf_notification(vrf)
self._notify_vrf_update(context._plugin_context,
vrfs_to_notify)
# Neutron subnets in non-external networks are mapped to AIM
# Subnets as they are added to routers as interfaces.
def _is_unrouted_vrf(self, vrf):
return (vrf.tenant_name == COMMON_TENANT_NAME and
vrf.name == self.apic_system_id + '_' + UNROUTED_VRF_NAME)
def _is_default_vrf(self, vrf):
return vrf.name == DEFAULT_VRF_NAME
def update_subnet_precommit(self, context):
current = context.current
original = context.original
@ -1097,6 +1117,19 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ns.delete_subnet(aim_ctx, l3out,
self._subnet_to_gw_ip_mask(current))
if network_db.aim_mapping:
# Provide VRF notifications if deleting subnets from
# unscoped networks.
# REVISIT: We may need to handle VRF notifications for
# external networks as well.
vrf = self._get_network_vrf(network_db.aim_mapping)
if (vrf and (self._is_unrouted_vrf(vrf) or
self._is_default_vrf(vrf))
and not network_db.external):
vrfs_to_notify = self._add_vrf_notification(vrf)
self._notify_vrf_update(context._plugin_context,
vrfs_to_notify)
# Non-external neutron subnets are unmapped from AIM Subnets as
# they are removed from routers.
@ -1166,6 +1199,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self.extend_subnet_dict_bulk(session, [(result, subnet_db)])
def _notify_vrf_for_scope(self, context):
session = context._plugin_context.session
scope_id = context.current['address_scope_id']
mapping = self._get_address_scope_mapping(session, scope_id)
if mapping:
vrf = self._get_address_scope_vrf(mapping)
if vrf:
vrfs_to_notify = self._add_vrf_notification(vrf)
self._notify_vrf_update(context._plugin_context,
vrfs_to_notify)
def create_subnetpool_precommit(self, context):
self._notify_vrf_for_scope(context)
def update_subnetpool_precommit(self, context):
current = context.current
original = context.original
@ -1216,6 +1263,14 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# just reject the update.
raise exceptions.ScopeUpdateNotSupported()
current_prefixes = set(current['prefixes'])
original_prefixes = set(original['prefixes'])
if current_scope_id and current_prefixes != original_prefixes:
self._notify_vrf_for_scope(context)
def delete_subnetpool_precommit(self, context):
self._notify_vrf_for_scope(context)
def create_address_scope_precommit(self, context):
current = context.current
LOG.debug("APIC AIM MD creating address scope: %s", current)
@ -1637,6 +1692,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
raise exceptions.NonIsomorphicNetworkRoutingUnsupported()
nets_to_notify = set()
vrfs_to_notify = set()
ports_to_notify = set()
router_topo_moved = False
@ -1677,7 +1733,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology(
context, aim_ctx, router_topology, router_vrf, vrf,
nets_to_notify)
nets_to_notify, vrfs_to_notify)
router_topo_moved = True
self._cleanup_default_vrf(aim_ctx, router_vrf)
elif router_shared_net:
@ -1688,7 +1744,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if net_intfs:
self._move_topology(
context, aim_ctx, intf_topology, intf_vrf, vrf,
nets_to_notify)
nets_to_notify, vrfs_to_notify)
self._cleanup_default_vrf(aim_ctx, intf_vrf)
else:
# This should never happen.
@ -1708,10 +1764,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# First interface for network.
if network_db.aim_mapping.epg_name:
bd, epg = self._associate_network_with_vrf(
context, aim_ctx, network_db, vrf, nets_to_notify)
context, aim_ctx, network_db, vrf, nets_to_notify,
scope_id, vrfs_to_notify)
elif network_db.aim_mapping.l3out_name:
l3out, epg = self._associate_network_with_vrf(
context, aim_ctx, network_db, vrf, nets_to_notify)
context, aim_ctx, network_db, vrf, nets_to_notify,
scope_id, vrfs_to_notify)
else:
# Network is already routed.
#
@ -1795,6 +1853,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ports_to_notify.update(port_ids)
if ports_to_notify:
self._notify_port_update_bulk(context, ports_to_notify)
if vrfs_to_notify:
self._notify_vrf_update(context, vrfs_to_notify)
def remove_router_interface(self, context, router_id, port, subnets):
LOG.debug("APIC AIM MD removing subnets %(subnets)s from router "
@ -1867,6 +1927,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
provided_contract_names=contracts)
nets_to_notify = set()
vrfs_to_notify = set()
ports_to_notify = set()
router_topo_moved = False
@ -1888,7 +1949,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
intf_vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology(
context, aim_ctx, intf_topology, old_vrf, intf_vrf,
nets_to_notify)
nets_to_notify, vrfs_to_notify)
# See if the router's topology must be moved.
router_topology = self._router_topology(session, router_db.id)
@ -1901,14 +1962,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
router_vrf = self._ensure_default_vrf(aim_ctx, router_vrf)
self._move_topology(
context, aim_ctx, router_topology, old_vrf, router_vrf,
nets_to_notify)
nets_to_notify, vrfs_to_notify)
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(
context, aim_ctx, network_db, old_vrf, nets_to_notify)
context, aim_ctx, network_db, old_vrf, nets_to_notify,
scope_id, vrfs_to_notify)
if scope_id == NO_ADDR_SCOPE:
self._cleanup_default_vrf(aim_ctx, old_vrf)
@ -1957,6 +2019,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ports_to_notify.update(port_ids)
if ports_to_notify:
self._notify_port_update_bulk(context, ports_to_notify)
if vrfs_to_notify:
self._notify_vrf_update(context, vrfs_to_notify)
def bind_port(self, context):
port = context.current
@ -2745,7 +2809,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return rtr_dbs
def _associate_network_with_vrf(self, ctx, aim_ctx, network_db, new_vrf,
nets_to_notify):
nets_to_notify, scope_id, vrfs_to_notify):
LOG.debug("Associating previously unrouted network %(net_id)s named "
"'%(net_name)s' in project %(net_tenant)s with VRF %(vrf)s",
{'net_id': network_db.id, 'net_name': network_db.name,
@ -2817,6 +2881,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# Tenants have changed.
nets_to_notify.add(network_db.id)
# Notify VRFs not associated with address_scopes that the
# subnet CIDRs within them have changed.
old_vrf = self._get_network_vrf(network_db.aim_mapping)
if self._is_default_vrf(new_vrf):
self._add_vrf_notification(new_vrf, vrfs_to_notify)
self._add_vrf_notification(old_vrf, vrfs_to_notify)
if not self._is_svi_db(network_db):
return bd, epg
else:
@ -2824,7 +2895,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return l3out, ext_net
def _dissassociate_network_from_vrf(self, ctx, aim_ctx, network_db,
old_vrf, nets_to_notify):
old_vrf, nets_to_notify, scope_id,
vrfs_to_notify):
LOG.debug("Dissassociating network %(net_id)s named '%(net_name)s' in "
"project %(net_tenant)s from VRF %(vrf)s",
{'net_id': network_db.id, 'net_name': network_db.name,
@ -2896,8 +2968,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# Tenants have changed.
nets_to_notify.add(network_db.id)
# Notify VRFs not associated with address_scopes that the
# subnet CIDRs within them have changed
if self._is_default_vrf(old_vrf):
self._add_vrf_notification(old_vrf, vrfs_to_notify)
self._add_vrf_notification(new_vrf, vrfs_to_notify)
def _add_vrf_notification(self, vrf, vrfs_to_notify=None):
vrfs_to_notify = set() if vrfs_to_notify is None else vrfs_to_notify
vrf_to_notify = '%s %s' % (vrf.tenant_name, vrf.name)
vrfs_to_notify.add(vrf_to_notify)
return vrfs_to_notify
def _move_topology(self, ctx, aim_ctx, topology, old_vrf, new_vrf,
nets_to_notify):
nets_to_notify, vrfs_to_notify):
LOG.info("Moving routed networks %(topology)s from VRF "
"%(old_vrf)s to VRF %(new_vrf)s",
{'topology': topology.keys(),
@ -2976,6 +3060,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# EPGs' Tenants have changed.
nets_to_notify.update(topology.keys())
self._add_vrf_notification(old_vrf, vrfs_to_notify)
self._add_vrf_notification(new_vrf, vrfs_to_notify)
def _router_topology(self, session, router_id):
LOG.debug("Getting topology for router %s", router_id)
visited_networks = {}
@ -3480,6 +3567,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
portbindings.VIF_TYPE_BINDING_FAILED]
def _notify_port_update(self, plugin_context, port_id):
# REVISIT: Avoid getting the port resource, if possible.
port = self.plugin.get_port(plugin_context.elevated(), port_id)
if self._is_port_bound(port):
LOG.debug("Enqueing notify for port %s", port['id'])
@ -3524,6 +3612,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
for p_id in port_ids:
self._notify_port_update(plugin_context, p_id)
def _notify_vrf_update(self, plugin_context, vrfs_to_notify):
txn = local_api.get_outer_transaction(
plugin_context.session.transaction)
for vrf in vrfs_to_notify:
local_api.send_or_queue_notification(plugin_context.session,
txn, self.notifier,
'opflex_notify_vrf',
[plugin_context, vrf])
def get_or_allocate_snat_ip(self, plugin_context, host_or_vrf,
ext_network):
"""Fetch or allocate SNAT IP on the external network.