From b5114e078f33bf2683f3bfc6ea59d9508155e529 Mon Sep 17 00:00:00 2001 From: Lucas Alvares Gomes Date: Tue, 4 Feb 2020 13:36:46 +0000 Subject: [PATCH] Enhance the test_multicast_between_vms_on_same_network test This patch is enhancing the test_multicast_between_vms_on_same_network test to fit the different scenarios that can be encountered when using multicast: 1) When IGMP snooping is enabled and the multicast group address *is not* in the 224.0.0.X range [0], asserts that the multicast traffic *is not* flooded. 2) When IGMP snooping is not enabled, asserts that the multicast traffic is flooded and the unregistered VM gets it. 3) When IGMP snooping is enabled and the multicast group addres *is* in the 224.0.0.X range [0], asserts that the multicast traffic *is* flooded. In order to make those assertions, a new VM is being launched as part of the test running tcpdump to verify whether the traffic is reaching it or not. A new configuration option called "is_igmp_snooping_enabled" has been added. [0] https://tools.ietf.org/html/rfc4541 (See section 2.1.2) Change-Id: I8af041925119463c7199238988f0133e8d993a8f Signed-off-by: Lucas Alvares Gomes --- neutron_tempest_plugin/config.py | 6 ++ .../scenario/test_multicast.py | 73 +++++++++++++++++-- .../notes/igmp-snooping-8d6d85608df8880a.yaml | 11 +++ 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/igmp-snooping-8d6d85608df8880a.yaml diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py index 51fbafce..4e218792 100644 --- a/neutron_tempest_plugin/config.py +++ b/neutron_tempest_plugin/config.py @@ -123,6 +123,12 @@ NeutronPluginOptions = [ default=False, help='Allow creation of shared resources.' 'The default value is false.'), + cfg.BoolOpt('is_igmp_snooping_enabled', + default=False, + help='Indicates whether IGMP snooping is enabled or not. ' + 'If True, multicast test(s) will assert that multicast ' + 'traffic is not being flooded to all ports. Defaults ' + 'to False.'), ] # TODO(amuller): Redo configuration options registration as part of the planned diff --git a/neutron_tempest_plugin/scenario/test_multicast.py b/neutron_tempest_plugin/scenario/test_multicast.py index a39a0c3d..566ac957 100644 --- a/neutron_tempest_plugin/scenario/test_multicast.py +++ b/neutron_tempest_plugin/scenario/test_multicast.py @@ -111,6 +111,14 @@ with open('%(result_file)s', 'w') as f: 'result_file': result_file} +def get_unregistered_script(group, result_file): + return """#!/bin/bash +export LC_ALL=en_US.UTF-8 +tcpdump -i any -s0 -vv host %(group)s -vvneA -s0 -l &> %(result_file)s & + """ % {'group': group, + 'result_file': result_file} + + class BaseMulticastTest(object): credentials = ['primary'] @@ -125,6 +133,7 @@ class BaseMulticastTest(object): multicast_message = "Big Bang" receiver_output_file = "/tmp/receiver_mcast_out" sender_output_file = "/tmp/sender_mcast_out" + unregistered_output_file = "/tmp/unregistered_mcast_out" @classmethod def skip_checks(cls): @@ -198,16 +207,16 @@ class BaseMulticastTest(object): server['ssh_client'] = ssh.Client(server['fip']['floating_ip_address'], self.username, pkey=self.keypair['private_key']) - self._check_python_installed_on_server(server['ssh_client'], - server['id']) + self._check_cmd_installed_on_server(server['ssh_client'], + server['id'], PYTHON3_BIN) return server - def _check_python_installed_on_server(self, ssh_client, server_id): + def _check_cmd_installed_on_server(self, ssh_client, server_id, cmd): try: - ssh_client.execute_script('which %s' % PYTHON3_BIN) + ssh_client.execute_script('which %s' % cmd) except exceptions.SSHScriptFailed: raise self.skipException( - "%s is not available on server %s" % (PYTHON3_BIN, server_id)) + "%s is not available on server %s" % (cmd, server_id)) def _prepare_sender(self, server, mcast_address): check_script = get_sender_script( @@ -226,10 +235,23 @@ class BaseMulticastTest(object): server['fip']['floating_ip_address'], self.username, pkey=self.keypair['private_key']) - self._check_python_installed_on_server(ssh_client, server['id']) + self._check_cmd_installed_on_server(ssh_client, server['id'], + PYTHON3_BIN) server['ssh_client'].execute_script( 'echo "%s" > ~/multicast_traffic_receiver.py' % check_script) + def _prepare_unregistered(self, server, mcast_address): + check_script = get_unregistered_script( + group=mcast_address, result_file=self.unregistered_output_file) + ssh_client = ssh.Client( + server['fip']['floating_ip_address'], + self.username, + pkey=self.keypair['private_key']) + self._check_cmd_installed_on_server(ssh_client, server['id'], + 'tcpdump') + server['ssh_client'].execute_script( + 'echo "%s" > ~/unregistered_traffic_receiver.sh' % check_script) + @test.unstable_test("bug 1850288") @decorators.idempotent_id('113486fc-24c9-4be4-8361-03b1c9892867') def test_multicast_between_vms_on_same_network(self): @@ -241,9 +263,26 @@ class BaseMulticastTest(object): receivers = [self._create_server() for _ in range(1)] # Sender can be also receiver of multicast traffic receivers.append(sender) - self._check_multicast_conectivity(sender=sender, receivers=receivers) + unregistered = self._create_server() + self._check_multicast_conectivity(sender=sender, receivers=receivers, + unregistered=unregistered) - def _check_multicast_conectivity(self, sender, receivers): + def _is_multicast_traffic_expected(self, mcast_address): + """Checks if multicast traffic is expected to arrive. + + Checks if multicast traffic is expected to arrive to the + unregistered VM. + + If IGMP snooping is enabled, multicast traffic should not be + flooded unless the destination IP is in the range of 224.0.0.X + [0]. + + [0] https://tools.ietf.org/html/rfc4541 (See section 2.1.2) + """ + return (mcast_address.startswith('224.0.0') or not + CONF.neutron_plugin_options.is_igmp_snooping_enabled) + + def _check_multicast_conectivity(self, sender, receivers, unregistered): """Test multi-cast messaging between two servers [Sender server] -> ... some network topology ... -> [Receiver server] @@ -257,6 +296,12 @@ class BaseMulticastTest(object): path=file_path)) return msg in result + self._prepare_unregistered(unregistered, mcast_address) + + # Run the unregistered node script + unregistered['ssh_client'].execute_script( + "bash ~/unregistered_traffic_receiver.sh", become_root=True) + self._prepare_sender(sender, mcast_address) receiver_ids = [] for receiver in receivers: @@ -295,6 +340,18 @@ class BaseMulticastTest(object): for receiver_id in receiver_ids: self.assertIn(receiver_id, replies_result) + # Kill the tcpdump command running on the unregistered node so + # tcpdump flushes its output to the output file + unregistered['ssh_client'].execute_script( + "killall tcpdump && sleep 2", become_root=True) + + unregistered_result = unregistered['ssh_client'].execute_script( + "cat {path} || echo '{path} not exists yet'".format( + path=self.unregistered_output_file)) + num_of_pckt = (1 if self._is_multicast_traffic_expected(mcast_address) + else 0) + self.assertIn('%d packets captured' % num_of_pckt, unregistered_result) + class MulticastTestIPv4(BaseMulticastTest, base.BaseTempestTestCase): diff --git a/releasenotes/notes/igmp-snooping-8d6d85608df8880a.yaml b/releasenotes/notes/igmp-snooping-8d6d85608df8880a.yaml new file mode 100644 index 00000000..032be1f6 --- /dev/null +++ b/releasenotes/notes/igmp-snooping-8d6d85608df8880a.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Enhanced the ``test_multicast_between_vms_on_same_network`` adding + IGMP test coverage to it. A new VM running tcpdump is spawned as + part of the test to verify whether the traffic is reaching it or not. +upgrade: + - | + Add a new configuration option called ``is_igmp_snooping_enabled`` + to enable/disable IGMP testing as part of the + ``test_multicast_between_vms_on_same_network`` test case.