Remove fdb entries for ha router interfaces when going DOWN

When HA router's interface on host is going DOWN but router
is still available on this host, L2 population
mechanism driver will now send to other hosts info to remove
fdb unicast entries to this port on host.

It will not send FLOODING_ENTRY because this port is still on
host but in standby mode and might be transformed to master
in future.

This solves issue with migration router from Legacy to HA.
In such case, port which was originally attached to legacy
router is transformed to be HA backup port before changing
its status to DOWN.
Now in such case unicast entries to this port and backup
node will be removed properly so packets to HA router will
be really send to host which is master node for router.

Closes-Bug: #1785582

Change-Id: Icc14e5f5d40fc6fbb49e0f7b18cc3b15ebec8508
(cherry picked from commit 6c300b1a9b)
This commit is contained in:
Slawek Kaplonski 2018-08-08 14:52:06 +02:00
parent 27519e8ff5
commit 5be3950965
2 changed files with 60 additions and 6 deletions

View File

@ -53,6 +53,14 @@ class L2populationMechanismDriver(api.MechanismDriver):
ip_address=ip['ip_address'])
for ip in port['fixed_ips']]
def _remove_flooding(self, fdb_entries):
for network_fdb in fdb_entries.values():
for agent_fdb in network_fdb.get('ports', {}).values():
try:
agent_fdb.remove(const.FLOODING_ENTRY)
except ValueError:
pass
def check_vlan_transparency(self, context):
"""L2population driver vlan transparency support."""
return True
@ -249,11 +257,15 @@ class L2populationMechanismDriver(api.MechanismDriver):
l3plugin, "list_router_ids_on_host", None):
admin_context = n_context.get_admin_context()
port_context = context._plugin_context
if l3plugin.list_router_ids_on_host(
admin_context, agent_host, [port['device_id']]):
return
fdb_entries = self._get_agent_fdb(
port_context, context.bottom_bound_segment, port, agent_host)
port_context, context.bottom_bound_segment, port, agent_host,
include_ha_router_ports=True)
if (fdb_entries and
l3plugin.list_router_ids_on_host(
admin_context, agent_host, [port['device_id']])):
# NOTE(slaweq): in case this is HA router, remove unicast
# entries to this port but don't remove flood entry
self._remove_flooding(fdb_entries)
self.L2populationAgentNotify.remove_fdb_entries(
self.rpc_ctx, fdb_entries)
@ -306,7 +318,8 @@ class L2populationMechanismDriver(api.MechanismDriver):
self.L2populationAgentNotify.add_fdb_entries(self.rpc_ctx,
other_fdb_entries)
def _get_agent_fdb(self, context, segment, port, agent_host):
def _get_agent_fdb(self, context, segment, port, agent_host,
include_ha_router_ports=False):
if not agent_host:
return
@ -335,9 +348,10 @@ class L2populationMechanismDriver(api.MechanismDriver):
const.FLOODING_ENTRY)
# Notify other agents to remove fdb rules for current port
if (port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE and
(include_ha_router_ports or
not l3_hamode_db.is_ha_router_port(context,
port['device_owner'],
port['device_id'])):
port['device_id']))):
fdb_entries = self._get_port_fdb_entries(port)
other_fdb_entries[network_id]['ports'][agent_ip] += fdb_entries

View File

@ -55,6 +55,7 @@ TEST_ROUTER_ID = 'router_id'
NOTIFIER = 'neutron.plugins.ml2.rpc.AgentNotifierApi'
DEVICE_OWNER_COMPUTE = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake'
DEVICE_OWNER_ROUTER_HA_INTF = constants.DEVICE_OWNER_ROUTER_HA_INTF + 'fake'
class FakeL3PluginWithAgents(common_db_mixin.CommonDbMixin,
@ -825,6 +826,45 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
self.mock_fanout.assert_called_with(
mock.ANY, 'remove_fdb_entries', expected)
def test_update_port_down_ha_router_port(self):
router = self._create_ha_router()
directory.add_plugin(plugin_constants.L3, self.plugin)
with self.subnet(network=self._network, enable_dhcp=False) as snet:
subnet = snet['subnet']
router_port = self._add_router_interface(subnet, router, HOST)
router_port_device = 'tap' + router_port['id']
host_arg = {portbindings.HOST_ID: HOST_4, 'admin_state_up': True}
with self.port(subnet=snet,
device_owner=DEVICE_OWNER_COMPUTE,
arg_list=(portbindings.HOST_ID,),
**host_arg) as port1:
p1 = port1['port']
device1 = 'tap' + p1['id']
self.callbacks.update_device_up(self.adminContext,
agent_id=HOST,
device=device1)
self.mock_fanout.reset_mock()
self.callbacks.update_device_down(self.adminContext,
agent_id=HOST,
device=router_port_device,
host=HOST)
router_port_ips = [
p['ip_address'] for p in router_port['fixed_ips']]
expected = {
router_port['network_id']: {
'ports': {
'20.0.0.1': [
l2pop_rpc.PortInfo(router_port['mac_address'],
router_port_ips[0])]},
'network_type': 'vxlan',
'segment_id': 1}}
self.mock_fanout.assert_called_with(
mock.ANY, 'remove_fdb_entries', expected)
def test_delete_port(self):
self._register_ml2_agents()