[apic_aim] Notify ports when modifying BDs

Enqueue notifications for all non-router ports on a network when the
association between the network's BD and VRF changes (which may
involve moving to a different Tenant), either individually or as part
of a topology.

Change-Id: I495d7be4c9512bf16f10baa30fa8134a2c81da78
This commit is contained in:
Robert Kukura
2017-01-26 09:40:12 -05:00
parent f3ee485d4e
commit e866ce58af
2 changed files with 185 additions and 26 deletions

View File

@@ -827,6 +827,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if different_router and different_subnet:
raise exceptions.UnsupportedRoutingTopology()
nets_to_notify = set()
ports_to_notify = set()
router_topo_moved = False
# Ensure that all the BDs and EPGs in the resulting topology
@@ -857,10 +859,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
router_shared_net = self._topology_shared(router_topology)
intf_vrf = self._map_default_vrf(
aim_ctx, intf_shared_net or network_db)
session, intf_shared_net or network_db)
router_vrf = (
self._map_default_vrf(
aim_ctx,
session,
router_shared_net or router_topology.itervalues().next())
if router_topology else None)
@@ -877,7 +879,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
# router topology.
vrf = self._ensure_default_vrf(aim_ctx, intf_vrf)
self._move_topology(
aim_ctx, router_topology, router_vrf, vrf)
aim_ctx, router_topology, router_vrf, vrf,
nets_to_notify)
router_topo_moved = True
# REVISIT: Delete router_vrf if no longer used?
elif router_shared_net:
@@ -887,7 +890,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
vrf = router_vrf
if net_intfs:
self._move_topology(
aim_ctx, intf_topology, intf_vrf, vrf)
aim_ctx, intf_topology, intf_vrf, vrf,
nets_to_notify)
# REVISIT: Delete intf_vrf if no longer used?
else:
# This should never happen.
@@ -905,10 +909,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if not net_intfs:
# First interface for network.
bd, epg = self._associate_network_with_vrf(
aim_ctx, network_db, vrf)
aim_ctx, network_db, vrf, nets_to_notify)
else:
# Network is already routed.
bd, epg = self._map_network(aim_ctx, network_db, vrf)
bd, epg = self._map_network(session, network_db, vrf)
# Create AIM Subnet(s) for each added Neutron subnet.
for subnet in subnets:
@@ -964,7 +968,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
port_ids = self._get_non_router_ports_in_subnets(
session,
[subnet['id'] for subnet in subnets])
self._notify_port_update_bulk(context, port_ids)
ports_to_notify.update(port_ids)
# Enqueue notifications for all affected ports.
if nets_to_notify:
port_ids = self._get_non_router_ports_in_networks(
session, nets_to_notify)
ports_to_notify.update(port_ids)
if ports_to_notify:
self._notify_port_update_bulk(context, ports_to_notify)
def remove_router_interface(self, context, router_id, port_db, subnets):
LOG.debug("APIC AIM MD removing subnets %(subnets)s from router "
@@ -1021,7 +1033,10 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
epg = self.aim.update(aim_ctx, epg,
provided_contract_names=contracts)
nets_to_notify = set()
ports_to_notify = set()
router_topo_moved = False
# If unscoped topologies have split, move VRFs as needed.
if scope_id == NO_ADDR_SCOPE:
# If the interface's network has not become unrouted, see
@@ -1030,27 +1045,30 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
intf_topology = self._network_topology(session, network_db)
intf_shared_net = self._topology_shared(intf_topology)
intf_vrf = self._map_default_vrf(
aim_ctx, intf_shared_net or network_db)
session, intf_shared_net or network_db)
if old_vrf.identity != intf_vrf.identity:
self._move_topology(
aim_ctx, intf_topology, old_vrf, intf_vrf)
aim_ctx, intf_topology, old_vrf, intf_vrf,
nets_to_notify)
# See if the router's topology must be moved.
router_topology = self._router_topology(session, router_db.id)
if router_topology:
router_shared_net = self._topology_shared(router_topology)
router_vrf = self._map_default_vrf(
aim_ctx,
session,
router_shared_net or router_topology.itervalues().next())
if old_vrf.identity != router_vrf.identity:
self._move_topology(
aim_ctx, router_topology, old_vrf, router_vrf)
aim_ctx, router_topology, old_vrf, router_vrf,
nets_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(aim_ctx, network_db, old_vrf)
self._dissassociate_network_from_vrf(
aim_ctx, network_db, old_vrf, nets_to_notify)
# If external-gateway is set, handle external-connectivity changes.
if router_db.gw_port_id:
@@ -1081,7 +1099,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
port_ids = self._get_non_router_ports_in_subnets(
session,
[subnet['id'] for subnet in subnets])
self._notify_port_update_bulk(context, port_ids)
ports_to_notify.update(port_ids)
# Enqueue notifications for all affected ports.
if nets_to_notify:
port_ids = self._get_non_router_ports_in_networks(
session, nets_to_notify)
ports_to_notify.update(port_ids)
if ports_to_notify:
self._notify_port_update_bulk(context, ports_to_notify)
def bind_port(self, context):
current = context.current
@@ -1422,7 +1448,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
distinct())
return rtr_dbs
def _associate_network_with_vrf(self, aim_ctx, network_db, new_vrf):
def _associate_network_with_vrf(self, aim_ctx, network_db, new_vrf,
nets_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,
@@ -1467,9 +1494,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
bd = self.aim.update(aim_ctx, bd, enable_routing=True,
vrf_name=new_vrf.name)
# All non-router ports on this network need to be notified
# since their BD's VRF and possibly their BD's and EPG's
# Tenants have changed.
nets_to_notify.add(network_db.id)
return bd, epg
def _dissassociate_network_from_vrf(self, aim_ctx, network_db, old_vrf):
def _dissassociate_network_from_vrf(self, aim_ctx, network_db, old_vrf,
nets_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,
@@ -1507,7 +1540,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
bd = self.aim.update(aim_ctx, bd, enable_routing=False,
vrf_name=new_vrf.name)
def _move_topology(self, aim_ctx, topology, old_vrf, new_vrf):
# All non-router ports on this network need to be notified
# since their BD's VRF and possibly their BD's and EPG's
# Tenants have changed.
nets_to_notify.add(network_db.id)
def _move_topology(self, aim_ctx, topology, old_vrf, new_vrf,
nets_to_notify):
LOG.info(_LI("Moving routed networks %(topology)s from VRF "
"%(old_vrf)s to VRF %(new_vrf)s"),
{'topology': topology.keys(),
@@ -1523,7 +1562,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
if old_vrf.tenant_name != new_vrf.tenant_name:
# New VRF is in different Tenant, so move BD, EPG, and
# all Subnets to new VRF's Tenant and set BD's VRF.
LOG.debug("Moving network %(net) from tenant %(old)s to "
LOG.debug("Moving network %(net)s from tenant %(old)s to "
"tenant %(new)s",
{'net': network_db.id,
'old': old_vrf.tenant_name,
@@ -1553,6 +1592,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
bd, _ = self._map_network(session, network_db, new_vrf)
bd = self.aim.update(aim_ctx, bd, vrf_name=new_vrf.name)
# All non-router ports on all networks in topology need to be
# notified since their BDs' VRFs and possibly their BDs' and
# EPGs' Tenants have changed.
nets_to_notify.update(topology.keys())
def _router_topology(self, session, router_id):
LOG.debug("Getting topology for router %s", router_id)
visited_networks = {}
@@ -1953,7 +1997,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
portbindings.VIF_TYPE_BINDING_FAILED]
def _notify_port_update(self, plugin_context, port_id):
port = self.plugin.get_port(plugin_context, port_id)
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'])
txn = local_api.get_outer_transaction(
@@ -1964,6 +2008,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
[plugin_context, port])
def _notify_port_update_bulk(self, plugin_context, port_ids):
# REVISIT: Is a single query for all ports possible?
for p_id in port_ids:
self._notify_port_update(plugin_context, p_id)
@@ -2227,3 +2272,13 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
n_constants.DEVICE_OWNER_ROUTER_INTF)
.all())
return [p[0] for p in port_ids]
def _get_non_router_ports_in_networks(self, session, network_ids):
if not network_ids:
return []
port_ids = (session.query(models_v2.Port.id).
filter(models_v2.Port.network_id.in_(network_ids)).
filter(models_v2.Port.device_owner !=
n_constants.DEVICE_OWNER_ROUTER_INTF).
all())
return [p[0] for p in port_ids]