From b4070c975274f53a4a2caaabeb5af55683232d3d Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Thu, 10 Dec 2020 00:10:38 +0100 Subject: [PATCH] Fix multicast traffic with IGMP snooping enabled In the ML2/OVS when igmp_snooping is enabled but there is no external querier multicast traffic will stop working after few minutes as packets will not be flooded to tunnel/external bridges. So this patch sets "mcast-snooping-disable-flood-unregistered" option of the br-int to False (default value) even when igmp_snooping is enabled in the neutron-ovs-agent's config file. Additionally it configures "mcast-snooping-flood-reports" and "mcast-snooping-flood" on patch ports in br-int to True. That way we can provide best effort snooping: multicast isolation where IGMP queriers are available and flood everywhere else? Closes-Bug: #1884723 Change-Id: Iefa0044dba9e92592295a79448e5d57d9e14a40b --- neutron/agent/common/ovs_lib.py | 12 +++++++++- .../openvswitch/agent/ovs_neutron_agent.py | 5 ++++ .../functional/agent/common/test_ovs_lib.py | 24 +++++++++++++++++++ .../tests/functional/agent/test_ovs_lib.py | 5 ++-- .../agent/test_ovs_neutron_agent.py | 15 +++++++++++- .../openvswitch/agent/test_ovs_tunnel.py | 3 +++ 6 files changed, 60 insertions(+), 4 deletions(-) diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index ab2119b2514..d8caac31753 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -278,7 +278,7 @@ class OVSBridge(BaseOVS): def set_igmp_snooping_state(self, state): state = bool(state) other_config = { - 'mcast-snooping-disable-flood-unregistered': str(state)} + 'mcast-snooping-disable-flood-unregistered': 'false'} with self.ovsdb.transaction() as txn: txn.add( self.ovsdb.db_set('Bridge', self.br_name, @@ -287,6 +287,16 @@ class OVSBridge(BaseOVS): self.ovsdb.db_set('Bridge', self.br_name, ('other_config', other_config))) + def set_igmp_snooping_flood(self, port_name, state): + state = str(state) + other_config = { + 'mcast-snooping-flood-reports': state, + 'mcast-snooping-flood': state} + self.ovsdb.db_set( + 'Port', port_name, + ('other_config', other_config)).execute( + check_error=True, log_errors=True) + def create(self, secure_mode=False): other_config = { 'mac-table-size': str(cfg.CONF.OVS.bridge_mac_table_size)} diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 874f9d57f45..21b230949ab 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -1407,6 +1407,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, "version of OVS does not support tunnels or patch " "ports. Agent terminated!") sys.exit(1) + self.int_br.set_igmp_snooping_flood( + self.conf.OVS.int_peer_patch_port, + self.conf.OVS.igmp_snooping_enable) if self.conf.AGENT.drop_flows_on_start: self.tun_br.uninstall_flows(cookie=ovs_lib.COOKIE_ANY) @@ -1539,6 +1542,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, else: int_ofport = self.int_br.add_patch_port( int_if_name, constants.NONEXISTENT_PEER) + self.int_br.set_igmp_snooping_flood( + int_if_name, self.conf.OVS.igmp_snooping_enable) if br.port_exists(phys_if_name): phys_ofport = br.get_port_ofport(phys_if_name) else: diff --git a/neutron/tests/functional/agent/common/test_ovs_lib.py b/neutron/tests/functional/agent/common/test_ovs_lib.py index 69c396238da..575e55061bb 100644 --- a/neutron/tests/functional/agent/common/test_ovs_lib.py +++ b/neutron/tests/functional/agent/common/test_ovs_lib.py @@ -470,3 +470,27 @@ class BaseOVSTestCase(base.BaseSudoTestCase): self.assertEqual(p_const.TYPE_GRE, ipv4_port_type) self.assertEqual(ovs_lib.TYPE_GRE_IP6, ipv6_port_type) self.assertEqual('legacy', ipv6_port_options.get('packet_type')) + + def test_set_igmp_snooping_flood(self): + port_name = 'test_output_port_2' + self._create_bridge() + self._create_port(port_name) + self.ovs.set_igmp_snooping_flood(port_name, True) + ports_other_config = self.ovs.db_get_val('Port', port_name, + 'other_config') + self.assertEqual( + 'true', + ports_other_config.get('mcast-snooping-flood', '').lower()) + self.assertEqual( + 'true', + ports_other_config.get('mcast-snooping-flood-reports', '').lower()) + + self.ovs.set_igmp_snooping_flood(port_name, False) + ports_other_config = self.ovs.db_get_val('Port', port_name, + 'other_config') + self.assertEqual( + 'false', + ports_other_config.get('mcast-snooping-flood', '').lower()) + self.assertEqual( + 'false', + ports_other_config.get('mcast-snooping-flood-reports', '').lower()) diff --git a/neutron/tests/functional/agent/test_ovs_lib.py b/neutron/tests/functional/agent/test_ovs_lib.py index 4de5f03a57c..697c68487e3 100644 --- a/neutron/tests/functional/agent/test_ovs_lib.py +++ b/neutron/tests/functional/agent/test_ovs_lib.py @@ -195,8 +195,9 @@ class OVSBridgeTestCase(OVSBridgeTestBase): 'Bridge', ('name', '=', self.br.br_name), columns=['other_config'] ).execute()[0]['other_config'] self.assertEqual( - str(state), - br_other_config['mcast-snooping-disable-flood-unregistered']) + 'false', + br_other_config.get( + 'mcast-snooping-disable-flood-unregistered', '').lower()) def test_set_igmp_snooping_enabled(self): self._test_set_igmp_snooping_state(True) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 96b2b6bc0cf..d0fc2ed824f 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -1533,7 +1533,8 @@ class TestOvsNeutronAgent(object): self.assertNotIn('activated_port_id', port_info['added']) def _test_setup_physical_bridges(self, port_exists=False, - dvr_enabled=False): + dvr_enabled=False, + igmp_snooping_enabled=False): self.agent.enable_distributed_routing = dvr_enabled with mock.patch.object(ip_lib.IPDevice, "exists") as devex_fn,\ mock.patch.object(sys, "exit"),\ @@ -1579,6 +1580,8 @@ class TestOvsNeutronAgent(object): 'int-br-eth', constants.NONEXISTENT_PEER), ] expected_calls += [ + mock.call.int_br.set_igmp_snooping_flood( + 'int-br-eth', igmp_snooping_enabled), mock.call.phys_br.port_exists('phy-br-eth'), ] if port_exists: @@ -1620,6 +1623,10 @@ class TestOvsNeutronAgent(object): def test_setup_physical_bridges_dvr_enabled(self): self._test_setup_physical_bridges(dvr_enabled=True) + def test_setup_physical_bridges_igmp_snooping_enabled(self): + cfg.CONF.set_override('igmp_snooping_enable', True, 'OVS') + self._test_setup_physical_bridges(igmp_snooping_enabled=True) + def _test_setup_physical_bridges_change_from_veth_to_patch_conf( self, port_exists=False): with mock.patch.object(sys, "exit"),\ @@ -1663,6 +1670,8 @@ class TestOvsNeutronAgent(object): 'int-br-eth', constants.NONEXISTENT_PEER), ] expected_calls += [ + mock.call.int_br.set_igmp_snooping_flood( + 'int-br-eth', False), mock.call.phys_br.port_exists('phy-br-eth'), ] if port_exists: @@ -1715,6 +1724,8 @@ class TestOvsNeutronAgent(object): return_value=False),\ mock.patch.object(self.agent.int_br, 'port_exists', return_value=False),\ + mock.patch.object(self.agent.int_br, + 'set_igmp_snooping_flood'),\ mock.patch.object(sys, "exit"): self.agent.setup_tunnel_br(None) self.agent.setup_tunnel_br() @@ -1739,6 +1750,8 @@ class TestOvsNeutronAgent(object): "add_patch_port") as int_patch_port,\ mock.patch.object(self.agent.tun_br, "add_patch_port") as tun_patch_port,\ + mock.patch.object(self.agent.int_br, + 'set_igmp_snooping_flood'),\ mock.patch.object(sys, "exit"): self.agent.setup_tunnel_br(None) self.agent.setup_tunnel_br() diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index 24cc555fd46..c4a685ce3d5 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -228,6 +228,8 @@ class TunnelTest(object): mock.call.port_exists('int-%s' % self.MAP_TUN_BRIDGE), mock.call.add_patch_port('int-%s' % self.MAP_TUN_BRIDGE, constants.NONEXISTENT_PEER), + mock.call.set_igmp_snooping_flood('int-%s' % self.MAP_TUN_BRIDGE, + igmp_snooping), ] self.mock_int_bridge_expected += [ @@ -257,6 +259,7 @@ class TunnelTest(object): self.mock_int_bridge_expected += [ mock.call.port_exists('patch-tun'), mock.call.add_patch_port('patch-tun', 'patch-int'), + mock.call.set_igmp_snooping_flood('patch-tun', igmp_snooping), ] self.mock_int_bridge_expected += [ mock.call.get_vif_ports((ovs_lib.INVALID_OFPORT,