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: if subnets_size > 1:
raise exceptions.OnlyOneSubnetInSVINetwork() 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 # Neutron subnets in non-external networks are mapped to AIM
# Subnets as they are added to routers as interfaces. # 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): def update_subnet_precommit(self, context):
current = context.current current = context.current
original = context.original original = context.original
@ -1097,6 +1117,19 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ns.delete_subnet(aim_ctx, l3out, ns.delete_subnet(aim_ctx, l3out,
self._subnet_to_gw_ip_mask(current)) 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 # Non-external neutron subnets are unmapped from AIM Subnets as
# they are removed from routers. # they are removed from routers.
@ -1166,6 +1199,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
self.extend_subnet_dict_bulk(session, [(result, subnet_db)]) 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): def update_subnetpool_precommit(self, context):
current = context.current current = context.current
original = context.original original = context.original
@ -1216,6 +1263,14 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# just reject the update. # just reject the update.
raise exceptions.ScopeUpdateNotSupported() 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): def create_address_scope_precommit(self, context):
current = context.current current = context.current
LOG.debug("APIC AIM MD creating address scope: %s", current) LOG.debug("APIC AIM MD creating address scope: %s", current)
@ -1637,6 +1692,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
raise exceptions.NonIsomorphicNetworkRoutingUnsupported() raise exceptions.NonIsomorphicNetworkRoutingUnsupported()
nets_to_notify = set() nets_to_notify = set()
vrfs_to_notify = set()
ports_to_notify = set() ports_to_notify = set()
router_topo_moved = False router_topo_moved = False
@ -1677,7 +1733,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
vrf = self._ensure_default_vrf(aim_ctx, intf_vrf) vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology( self._move_topology(
context, aim_ctx, router_topology, router_vrf, vrf, context, aim_ctx, router_topology, router_vrf, vrf,
nets_to_notify) nets_to_notify, vrfs_to_notify)
router_topo_moved = True router_topo_moved = True
self._cleanup_default_vrf(aim_ctx, router_vrf) self._cleanup_default_vrf(aim_ctx, router_vrf)
elif router_shared_net: elif router_shared_net:
@ -1688,7 +1744,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
if net_intfs: if net_intfs:
self._move_topology( self._move_topology(
context, aim_ctx, intf_topology, intf_vrf, vrf, 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) self._cleanup_default_vrf(aim_ctx, intf_vrf)
else: else:
# This should never happen. # This should never happen.
@ -1708,10 +1764,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# First interface for network. # First interface for network.
if network_db.aim_mapping.epg_name: if network_db.aim_mapping.epg_name:
bd, epg = self._associate_network_with_vrf( 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: elif network_db.aim_mapping.l3out_name:
l3out, epg = self._associate_network_with_vrf( 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: else:
# Network is already routed. # Network is already routed.
# #
@ -1795,6 +1853,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ports_to_notify.update(port_ids) ports_to_notify.update(port_ids)
if ports_to_notify: if ports_to_notify:
self._notify_port_update_bulk(context, 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): def remove_router_interface(self, context, router_id, port, subnets):
LOG.debug("APIC AIM MD removing subnets %(subnets)s from router " LOG.debug("APIC AIM MD removing subnets %(subnets)s from router "
@ -1867,6 +1927,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
provided_contract_names=contracts) provided_contract_names=contracts)
nets_to_notify = set() nets_to_notify = set()
vrfs_to_notify = set()
ports_to_notify = set() ports_to_notify = set()
router_topo_moved = False router_topo_moved = False
@ -1888,7 +1949,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
intf_vrf = self._ensure_default_vrf(aim_ctx, intf_vrf) intf_vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology( self._move_topology(
context, aim_ctx, intf_topology, old_vrf, intf_vrf, 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. # See if the router's topology must be moved.
router_topology = self._router_topology(session, router_db.id) 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) router_vrf = self._ensure_default_vrf(aim_ctx, router_vrf)
self._move_topology( self._move_topology(
context, aim_ctx, router_topology, old_vrf, router_vrf, context, aim_ctx, router_topology, old_vrf, router_vrf,
nets_to_notify) nets_to_notify, vrfs_to_notify)
router_topo_moved = True router_topo_moved = True
# If network is no longer connected to any router, make the # If network is no longer connected to any router, make the
# network's BD unrouted. # network's BD unrouted.
if not router_ids: if not router_ids:
self._dissassociate_network_from_vrf( 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: if scope_id == NO_ADDR_SCOPE:
self._cleanup_default_vrf(aim_ctx, old_vrf) self._cleanup_default_vrf(aim_ctx, old_vrf)
@ -1957,6 +2019,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
ports_to_notify.update(port_ids) ports_to_notify.update(port_ids)
if ports_to_notify: if ports_to_notify:
self._notify_port_update_bulk(context, 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): def bind_port(self, context):
port = context.current port = context.current
@ -2745,7 +2809,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return rtr_dbs return rtr_dbs
def _associate_network_with_vrf(self, ctx, aim_ctx, network_db, new_vrf, 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 " LOG.debug("Associating previously unrouted network %(net_id)s named "
"'%(net_name)s' in project %(net_tenant)s with VRF %(vrf)s", "'%(net_name)s' in project %(net_tenant)s with VRF %(vrf)s",
{'net_id': network_db.id, 'net_name': network_db.name, {'net_id': network_db.id, 'net_name': network_db.name,
@ -2817,6 +2881,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# Tenants have changed. # Tenants have changed.
nets_to_notify.add(network_db.id) 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): if not self._is_svi_db(network_db):
return bd, epg return bd, epg
else: else:
@ -2824,7 +2895,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
return l3out, ext_net return l3out, ext_net
def _dissassociate_network_from_vrf(self, ctx, aim_ctx, network_db, 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 " LOG.debug("Dissassociating network %(net_id)s named '%(net_name)s' in "
"project %(net_tenant)s from VRF %(vrf)s", "project %(net_tenant)s from VRF %(vrf)s",
{'net_id': network_db.id, 'net_name': network_db.name, {'net_id': network_db.id, 'net_name': network_db.name,
@ -2896,8 +2968,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# Tenants have changed. # Tenants have changed.
nets_to_notify.add(network_db.id) 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, 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 " LOG.info("Moving routed networks %(topology)s from VRF "
"%(old_vrf)s to VRF %(new_vrf)s", "%(old_vrf)s to VRF %(new_vrf)s",
{'topology': topology.keys(), {'topology': topology.keys(),
@ -2976,6 +3060,9 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# EPGs' Tenants have changed. # EPGs' Tenants have changed.
nets_to_notify.update(topology.keys()) 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): def _router_topology(self, session, router_id):
LOG.debug("Getting topology for router %s", router_id) LOG.debug("Getting topology for router %s", router_id)
visited_networks = {} visited_networks = {}
@ -3480,6 +3567,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
portbindings.VIF_TYPE_BINDING_FAILED] portbindings.VIF_TYPE_BINDING_FAILED]
def _notify_port_update(self, plugin_context, port_id): 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) port = self.plugin.get_port(plugin_context.elevated(), port_id)
if self._is_port_bound(port): if self._is_port_bound(port):
LOG.debug("Enqueing notify for port %s", port['id']) LOG.debug("Enqueing notify for port %s", port['id'])
@ -3524,6 +3612,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
for p_id in port_ids: for p_id in port_ids:
self._notify_port_update(plugin_context, p_id) 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, def get_or_allocate_snat_ip(self, plugin_context, host_or_vrf,
ext_network): ext_network):
"""Fetch or allocate SNAT IP on the external network. """Fetch or allocate SNAT IP on the external network.