From 6c300b1a9b3f0db82b4edd84eda74600d28b7185 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Wed, 8 Aug 2018 14:52:06 +0200 Subject: [PATCH] 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 --- .../plugins/ml2/drivers/l2pop/mech_driver.py | 26 +++++++++--- .../ml2/drivers/l2pop/test_mech_driver.py | 40 +++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py index 9bc839a453c..7e26bc93dad 100644 --- a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py +++ b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py @@ -52,6 +52,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 @@ -255,11 +263,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) @@ -310,7 +322,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 @@ -338,9 +351,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 diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py index 618b47f2180..972d6fb10ba 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py @@ -57,6 +57,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, @@ -858,6 +859,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()