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()