From 5d787a9966feb8abba086ec3ed5020170bf8af82 Mon Sep 17 00:00:00 2001 From: elajkat Date: Tue, 13 Jun 2023 14:36:25 +0200 Subject: [PATCH] Tap Mirror API and scenario tests Change-Id: I0876068eb15053853f52ba9bdab1f6cce484f417 Depends-On: https://review.opendev.org/c/openstack/tap-as-a-service/+/893509 Depends-On: https://review.opendev.org/c/openstack/neutron/+/905840 Related-Bug: #2015471 --- .../tap_as_a_service/api/test_tap_mirror.py | 218 ++++++++++++++++++ .../tap_as_a_service/base.py | 27 +++ .../tap_as_a_service/scenario/manager.py | 76 +++++- .../scenario/test_tap_mirror.py | 141 +++++++++++ .../scenario/test_traffic_impact.py | 50 +--- .../tap_as_a_service/services/taas_client.py | 24 ++ zuul.d/2023_1_jobs.yaml | 4 + zuul.d/2024_1_jobs.yaml | 4 + zuul.d/2024_2_jobs.yaml | 7 + zuul.d/2025_1_jobs.yaml | 6 + zuul.d/master_jobs.yaml | 54 ++++- zuul.d/project.yaml | 2 + zuul.d/xena_jobs.yaml | 3 + zuul.d/yoga_jobs.yaml | 3 + zuul.d/zed_jobs.yaml | 3 + 15 files changed, 573 insertions(+), 49 deletions(-) create mode 100644 neutron_tempest_plugin/tap_as_a_service/api/test_tap_mirror.py create mode 100644 neutron_tempest_plugin/tap_as_a_service/scenario/test_tap_mirror.py diff --git a/neutron_tempest_plugin/tap_as_a_service/api/test_tap_mirror.py b/neutron_tempest_plugin/tap_as_a_service/api/test_tap_mirror.py new file mode 100644 index 000000000..85c37c63e --- /dev/null +++ b/neutron_tempest_plugin/tap_as_a_service/api/test_tap_mirror.py @@ -0,0 +1,218 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_utils import uuidutils + +from tempest.common import utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from neutron_tempest_plugin.tap_as_a_service import base + + +class TapMirrorTestJSON(base.BaseTaasTest): + + @classmethod + @utils.requires_ext(extension='tap-mirror', service='network') + def skip_checks(cls): + super().skip_checks() + + @classmethod + def resource_setup(cls): + super().resource_setup() + cls.network = cls.create_network() + cls.tap_mirror_port = cls.create_port(cls.network) + cls.in_direction = {'IN': 101} + cls.out_direction = {'OUT': 102} + cls.both_direction = cls.in_direction | cls.out_direction + cls.remote_ip = '192.101.0.42' + cls.remote_ip2 = '192.101.3.43' + cls.gre = 'gre' + cls.erspan = 'erspanv1' + + @decorators.idempotent_id('628f202c-ed0a-4eb1-8547-4954f67a84b7') + def test_create_tap_mirror(self): + tap_mirror = self.create_tap_mirror( + port_id=self.tap_mirror_port['id'], + directions=self.in_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + self.assertEqual(self.tap_mirror_port['id'], tap_mirror['port_id']) + self.assertEqual('gre', tap_mirror['mirror_type']) + self.assertEqual(self.in_direction, tap_mirror['directions']) + self.tap_mirrors_client.delete_tap_mirror(tap_mirror['id']) + + tap_mirror = self.create_tap_mirror( + port_id=self.tap_mirror_port['id'], + directions=self.both_direction, + remote_ip=self.remote_ip, + mirror_type=self.erspan + ) + self.assertEqual(self.tap_mirror_port['id'], tap_mirror['port_id']) + self.assertEqual(self.erspan, tap_mirror['mirror_type']) + self.assertEqual(self.both_direction, tap_mirror['directions']) + + @decorators.idempotent_id('299c251b-e0bc-4449-98db-959a5d8038c2') + def test_list_show_tap_mirror(self): + tap_mirror = self.create_tap_mirror( + port_id=self.tap_mirror_port['id'], + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + tap_mirrors = self.tap_mirrors_client.list_tap_mirrors() + is_t_m_found = False + for t_m in tap_mirrors['tap_mirrors']: + if t_m['id'] == tap_mirror['id']: + is_t_m_found = True + break + self.assertTrue(is_t_m_found) + tap_mirror_show_res = self.tap_mirrors_client.show_tap_mirror( + tap_mirror['id'])['tap_mirror'] + self.assertEqual(tap_mirror['id'], tap_mirror_show_res['id']) + self.assertEqual(self.gre, tap_mirror_show_res['mirror_type']) + self.assertEqual(self.remote_ip, + tap_mirror_show_res['remote_ip']) + self.assertEqual(self.out_direction, + tap_mirror_show_res['directions']) + + @decorators.idempotent_id('19c40379-bda5-48c9-8873-fc990739d1b5') + def test_update_tap_mirror(self): + tap_mirror = self.create_tap_mirror( + port_id=self.tap_mirror_port['id'], + directions=self.in_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + self.tap_mirrors_client.update_tap_mirror( + tap_mirror_id=tap_mirror['id'], + name='new_name', + description='My fancy Tap Mirror' + ) + tap_mirror_show_res = self.tap_mirrors_client.show_tap_mirror( + tap_mirror['id'])['tap_mirror'] + self.assertEqual('new_name', tap_mirror_show_res['name']) + self.assertEqual('My fancy Tap Mirror', + tap_mirror_show_res['description']) + + @decorators.idempotent_id('9ed165af-7c54-43ac-b14f-077e8f9601f6') + def test_delete_mirror_port_deletes_tap_mirror(self): + port1 = self.create_port(self.network) + tap_mirror = self.create_tap_mirror( + port_id=port1['id'], + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + # Delete port will result in deteltion of the tap_mirror + self.ports_client.delete_port(port1['id']) + self.assertRaises(lib_exc.NotFound, + self.tap_mirrors_client.show_tap_mirror, + tap_mirror['id']) + + @decorators.idempotent_id('abdd4451-bd9d-4f1e-ab7f-e949b9246714') + def test_delete_tap_mirror_port_remains(self): + port1 = self.create_port(self.network) + tap_mirror = self.create_tap_mirror( + port_id=port1['id'], + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + # Delete tap_mirror will keep the port + self.tap_mirrors_client.delete_tap_mirror(tap_mirror['id']) + port_res = self.ports_client.show_port(port1['id'])['port'] + self.assertEqual(port1['name'], port_res['name']) + + @decorators.idempotent_id('1d8b68fc-a600-4b9e-bd17-9469c3a6c95b') + def test_create_tap_mirror_negative(self): + # directions keys' valid values are IN and OUT + self.assertRaises(lib_exc.BadRequest, + self.create_tap_mirror, + port_id=self.tap_mirror_port['id'], + directions={'something': 101}, + remote_ip=self.remote_ip, + mirror_type=self.gre) + # mirror_type valid values are erspanv1 and gre + self.assertRaises(lib_exc.BadRequest, + self.create_tap_mirror, + port_id=self.tap_mirror_port['id'], + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type='erspanv2') + # remote_ip must be a valid IP + self.assertRaises(lib_exc.BadRequest, + self.create_tap_mirror, + port_id=self.tap_mirror_port['id'], + directions=self.in_direction, + remote_ip='192.101.0.420', + mirror_type=self.gre) + + @decorators.idempotent_id('2b7850b3-3920-4f16-96b7-05e2efd96877') + def test_create_tap_service_tunnel_id_conflict(self): + self.create_tap_mirror( + port_id=self.tap_mirror_port['id'], + directions=self.in_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + + port2 = self.create_port(self.network) + self.addCleanup(self.ports_client.delete_port, port2['id']) + self.assertRaises(lib_exc.Conflict, + self.create_tap_mirror, + port_id=port2['id'], + directions=self.in_direction, + remote_ip='192.101.0.4', + mirror_type=self.gre) + + @decorators.idempotent_id('95ef1cc1-cd57-4193-a88e-716795e39ebf') + def test_create_tap_mirror_non_existing_port(self): + not_exists = uuidutils.generate_uuid() + self.assertRaises(lib_exc.NotFound, + self.create_tap_mirror, + port_id=not_exists, + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre) + + @decorators.idempotent_id('123202cd-d810-4c15-bae7-26d69b24a1a4') + def test_multiple_mirrors_for_port(self): + port1 = self.create_port(self.network) + tap_mirror = self.create_tap_mirror( + port_id=port1['id'], + directions=self.out_direction, + remote_ip=self.remote_ip, + mirror_type=self.gre + ) + self.addCleanup(self.tap_mirrors_client.delete_tap_mirror, + tap_mirror['id']) + + # Creation of the 2nd mirror in case the direction: tunnel_id dict + # is different. + tap_mirror2 = self.create_tap_mirror( + port_id=port1['id'], + directions={'OUT': 103}, + remote_ip=self.remote_ip2, + mirror_type=self.gre + ) + + # We have a conflict if the direction: tunnel_id dict is the + # same + self.tap_mirrors_client.delete_tap_mirror(tap_mirror2['id']) + self.assertRaises(lib_exc.Conflict, + self.create_tap_mirror, + port_id=port1['id'], + directions=self.out_direction, + remote_ip='192.101.0.4', + mirror_type=self.gre) diff --git a/neutron_tempest_plugin/tap_as_a_service/base.py b/neutron_tempest_plugin/tap_as_a_service/base.py index 3ddc79772..99bd3cd38 100644 --- a/neutron_tempest_plugin/tap_as_a_service/base.py +++ b/neutron_tempest_plugin/tap_as_a_service/base.py @@ -42,6 +42,14 @@ class BaseTaasTest(test.BaseAdminNetworkTest): build_interval=CONF.network.build_interval, build_timeout=CONF.network.build_timeout, **os_primary.default_params) + cls.tap_mirrors_client = taas_client.TapMirrorsClient( + os_primary.auth_provider, + CONF.network.catalog_type, + CONF.network.region or CONF.identity.region, + endpoint_type=CONF.network.endpoint_type, + build_interval=CONF.network.build_interval, + build_timeout=CONF.network.build_timeout, + **os_primary.default_params) def create_tap_service(self, **kwargs): body = self.tap_services_client.create_tap_service( @@ -80,3 +88,22 @@ class BaseTaasTest(test.BaseAdminNetworkTest): self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.tap_flows_client.delete_tap_flow, tap_flow['id']) + + def create_tap_mirror(self, **kwargs): + body = self.tap_mirrors_client.create_tap_mirror( + name=data_utils.rand_name("tap_mirror"), + **kwargs) + tap_mirror = body['tap_mirror'] + self.addCleanup(test_utils.call_and_ignore_notfound_exc, + self.tap_mirrors_client.delete_tap_mirror, + tap_mirror['id']) + return tap_mirror + + def update_tap_mirror(self, tap_mirror_id, **kwargs): + body = self.tap_mirrors_client.update_tap_mirror( + tap_mirror_id, + **kwargs) + tap_mirror = body['tap_mirror'] + self.addCleanup(test_utils.call_and_ignore_notfound_exc, + self.tap_mirrors_client.delete_tap_mirror, + tap_mirror['id']) diff --git a/neutron_tempest_plugin/tap_as_a_service/scenario/manager.py b/neutron_tempest_plugin/tap_as_a_service/scenario/manager.py index 80389c1f1..d3c0d8b8a 100644 --- a/neutron_tempest_plugin/tap_as_a_service/scenario/manager.py +++ b/neutron_tempest_plugin/tap_as_a_service/scenario/manager.py @@ -59,6 +59,14 @@ class BaseTaasScenarioTests(base.BaseTempestTestCase): build_interval=CONF.network.build_interval, build_timeout=CONF.network.build_timeout, **cls.os_primary.default_params) + cls.tap_mirrors_client = taas_client.TapMirrorsClient( + cls.os_primary.auth_provider, + CONF.network.catalog_type, + CONF.network.region or CONF.identity.region, + endpoint_type=CONF.network.endpoint_type, + build_interval=CONF.network.build_interval, + build_timeout=CONF.network.build_timeout, + **cls.os_primary.default_params) def _create_subnet(self, network, subnets_client=None, namestart='subnet-smoke', **kwargs): @@ -214,8 +222,10 @@ class BaseTaasScenarioTests(base.BaseTempestTestCase): return network, subnet, router def _create_server_with_floatingip(self, use_taas_cloud_image=False, - provider_net=False, **kwargs): - network = self.network + provider_net=False, network=None, + **kwargs): + if not network: + network = self.network if use_taas_cloud_image: image = CONF.neutron_plugin_options.advanced_image_ref flavor = CONF.neutron_plugin_options.advanced_image_flavor_ref @@ -226,17 +236,26 @@ class BaseTaasScenarioTests(base.BaseTempestTestCase): if provider_net: network = self.provider_network - port = self.create_port( - network=network, security_groups=[self.secgroup['id']], **kwargs) + server_params = { + 'flavor_ref': flavor, + 'image_ref': image, + 'key_name': self.keypair['name'], + } + if 'security_group' in kwargs: + server_params['security_groups'] = [ + {'name': kwargs.pop('security_group')}] + + if kwargs.get('port_security_enabled', None) is False: + port = self.create_port(network=network, **kwargs) + else: + port = self.create_port( + network=network, security_groups=[self.secgroup['id']], + **kwargs) self.addCleanup(test_utils.call_and_ignore_notfound_exc, self.client.delete_port, port['id']) - params = { - 'flavor_ref': flavor, - 'image_ref': image, - 'key_name': self.keypair['name'] - } - vm = self.create_server(networks=[{'port': port['id']}], **params) + vm = self.create_server(networks=[{'port': port['id']}], + **server_params) self.wait_for_server_active(vm['server']) self.wait_for_guest_os_ready(vm['server']) @@ -291,3 +310,40 @@ class BaseTaasScenarioTests(base.BaseTempestTestCase): test_utils.call_and_ignore_notfound_exc, self.admin_network_client.remove_router_interface_with_subnet_id, self.router['id'], subnet_id=result['subnet']['id']) + + def _check_icmp_traffic(self, monitor_client, left_client, + left_port, right_port, + tcpdump_cmd=None): + log_location = "/tmp/tcpdumplog" + + right_ip = right_port['fixed_ips'][0]['ip_address'] + left_ip = left_port['fixed_ips'][0]['ip_address'] + + # Run tcpdump in background + if tcpdump_cmd: + self._run_in_background(monitor_client, tcpdump_cmd % log_location) + else: + self._run_in_background(monitor_client, + "sudo tcpdump -n -nn > %s" % log_location) + + # Ensure tcpdump is up and running + psax = monitor_client.exec_command("ps -ax") + self.assertIn("tcpdump", psax) + + # Run traffic from left_vm to right_vm + LOG.debug('Check ICMP traffic: ping %s ', right_ip) + self.check_remote_connectivity(left_client, right_ip, + ping_count=50) + + # Collect tcpdump results + output = self.monitor_client.exec_command("cat %s" % log_location) + self.assertLess(0, len(output)) + + looking_for = ["%s > %s: ICMP echo request" % (left_ip, right_ip), + "%s > %s: ICMP echo reply" % (right_ip, left_ip)] + + results = [] + for tcpdump_line in looking_for: + results.append(tcpdump_line in output) + + return all(results), output diff --git a/neutron_tempest_plugin/tap_as_a_service/scenario/test_tap_mirror.py b/neutron_tempest_plugin/tap_as_a_service/scenario/test_tap_mirror.py new file mode 100644 index 000000000..d4a482c0a --- /dev/null +++ b/neutron_tempest_plugin/tap_as_a_service/scenario/test_tap_mirror.py @@ -0,0 +1,141 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest.common import utils +from tempest import config +from tempest.lib.common.utils import data_utils +from tempest.lib.common.utils.linux import remote_client +from tempest.lib.common.utils import test_utils +from tempest.lib import decorators + +from neutron_tempest_plugin.tap_as_a_service.scenario import manager + +CONF = config.CONF + + +class TestTapMirror(manager.BaseTaasScenarioTests): + + @classmethod + @utils.requires_ext(extension='security-group', service='network') + @utils.requires_ext(extension='tap-mirror', service='network') + def skip_checks(cls): + super().skip_checks() + + @classmethod + def resource_setup(cls): + super().resource_setup() + cls.keypair = cls.create_keypair() + cls.secgroup = cls.create_security_group( + name=data_utils.rand_name('secgroup')) + cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id']) + cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id']) + + @decorators.idempotent_id('d9cfca96-fa83-417a-b111-1c02f6fe2796') + def test_tap_mirror_connectivity(self): + """Test that traffic between 2 VMs mirrored to a FIP + + .. code-block:: HTML + + +------------+ + | Monitor VM | + | FIP | + +-----+------+ + | + | + +-----+------+ + | NetMon | + +------------+ + + +---------------+ + | Net0 | + +---+---------+-+ + | | + | | + +---+-+ +-+---+ + | VM0 | | VM1 | + +-----+ +-----+ + + This is a simplified scenario adapted to the CI machinery. + The mirroring destination should be outside of the cloud. + """ + + # Create the topology for the 2 VMs of which the traffic + # will be mirrored + self.network, self.subnet, self.router = self.create_networks() + + vm0_port, vm0_fip = self._create_server_with_floatingip( + security_group=self.secgroup['name'] + ) + vm1_port, vm1_fip = self._create_server_with_floatingip( + security_group=self.secgroup['name'] + ) + vm1_ip = vm1_port['fixed_ips'][0]['ip_address'] + + vm0_client = remote_client.RemoteClient( + vm0_fip['floating_ip_address'], + CONF.validation.image_ssh_user, + pkey=self.keypair['private_key'], + ssh_key_type=CONF.validation.ssh_key_type) + vm0_client.validate_authentication() + vm1_client = remote_client.RemoteClient( + vm1_fip['floating_ip_address'], + CONF.validation.image_ssh_user, + pkey=self.keypair['private_key'], + ssh_key_type=CONF.validation.ssh_key_type) + vm1_client.validate_authentication() + + self.check_remote_connectivity(vm0_client, vm1_ip, ping_count=5) + + # Create the VM which will be the destination of the mirror + netmon, _, _ = self.create_networks() + _, vm_mon_fip = self._create_server_with_floatingip( + use_taas_cloud_image=True, network=netmon, + security_group=self.secgroup['name'], + port_security_enabled=False, + ) + + user = CONF.neutron_plugin_options.advanced_image_ssh_user + self.monitor_client = remote_client.RemoteClient( + vm_mon_fip['floating_ip_address'], user, + pkey=self.keypair['private_key'], + ssh_key_type=CONF.validation.ssh_key_type) + self.monitor_client.validate_authentication() + + r_ip = vm_mon_fip['floating_ip_address'] + # Create GRE mirror, as tcpdump cant extract ERSPAN + # it is just visible as a type of GRE traffic. + # direction IN and that the test pings from vm0 to vm1 + # means that ICMP echo request will be in the dump. + # 101 as tunnel id means that we will see 0x65 as key + tap_mirror = self.tap_mirrors_client.create_tap_mirror( + name=data_utils.rand_name("tap_mirror"), + port_id=vm1_port['id'], + directions={'IN': '101', 'OUT': '102'}, + remote_ip=r_ip, + mirror_type='gre', + ) + self.addCleanup( + test_utils.call_and_ignore_notfound_exc, + self.tap_mirrors_client.delete_tap_mirror, + tap_mirror['tap_mirror']['id'] + ) + + res, output = self._check_icmp_traffic( + self.monitor_client, + vm0_client, vm0_port, vm1_port, + tcpdump_cmd="sudo tcpdump -vvv -n -nn proto GRE > %s") + + self.assertTrue(res) + # GRE Key for Direction IN:101 + self.assertIn('key=0x65', output) + # GRE Key for Direction OUT:102 + self.assertIn('key=0x66', output) diff --git a/neutron_tempest_plugin/tap_as_a_service/scenario/test_traffic_impact.py b/neutron_tempest_plugin/tap_as_a_service/scenario/test_traffic_impact.py index 904335de8..6eaeb6ba1 100644 --- a/neutron_tempest_plugin/tap_as_a_service/scenario/test_traffic_impact.py +++ b/neutron_tempest_plugin/tap_as_a_service/scenario/test_traffic_impact.py @@ -138,40 +138,6 @@ class TestTaaSTrafficScenarios(manager.BaseTaasScenarioTests): self.right_client.validate_authentication() yield - def _check_icmp_traffic(self): - log_location = "/tmp/tcpdumplog" - - right_ip = self.right_port['fixed_ips'][0]['ip_address'] - left_ip = self.left_port['fixed_ips'][0]['ip_address'] - - # Run tcpdump in background - self._run_in_background(self.monitor_client, - "sudo tcpdump -n -nn > %s" % log_location) - - # Ensure tcpdump is up and running - psax = self.monitor_client.exec_command("ps -ax") - self.assertIn("tcpdump", psax) - - # Run traffic from left_vm to right_vm - LOG.debug('Check ICMP traffic: ping %s ', right_ip) - # self.left_client.exec_command( - # "ping -c 50 %s" % self.right_fip['floating_ip_address']) - self.check_remote_connectivity(self.left_client, right_ip, - ping_count=50) - - # Collect tcpdump results - output = self.monitor_client.exec_command("cat %s" % log_location) - self.assertLess(0, len(output)) - - looking_for = ["IP %s > %s: ICMP echo request" % (left_ip, right_ip), - "IP %s > %s: ICMP echo reply" % (right_ip, left_ip)] - - results = [] - for tcpdump_line in looking_for: - results.append(tcpdump_line in output) - - return all(results) - def _test_taas_connectivity(self, use_provider_net=False): """Ensure TAAS doesn't break connectivity @@ -222,7 +188,9 @@ class TestTaaSTrafficScenarios(manager.BaseTaasScenarioTests): with self._setup_topology(use_taas_cloud_image=True): # Check that traffic was forwarded to TAAS service - self.assertTrue(self._check_icmp_traffic()) + self.assertTrue(self._check_icmp_traffic( + self.monitor_client, self.left_client, + self.left_port, self.right_port)[0]) @decorators.idempotent_id('6c54d9c5-075a-4a1f-bbe6-12c3c9abf1e2') @testtools.skipUnless(CONF.neutron_plugin_options.advanced_image_ref, @@ -234,7 +202,9 @@ class TestTaaSTrafficScenarios(manager.BaseTaasScenarioTests): with self._setup_topology(taas=False, use_taas_cloud_image=True): # Check that traffic was NOT forwarded to TAAS service - self.assertFalse(self._check_icmp_traffic()) + self.assertFalse(self._check_icmp_traffic( + self.monitor_client, self.left_client, + self.left_port, self.right_port)[0]) @decorators.idempotent_id('fcb15ca3-ef61-11e9-9792-f45c89c47e12') @testtools.skipUnless(CONF.neutron_plugin_options.advanced_image_ref, @@ -247,7 +217,9 @@ class TestTaaSTrafficScenarios(manager.BaseTaasScenarioTests): with self._setup_topology(use_taas_cloud_image=True, provider_net=True): # Check that traffic was forwarded to TAAS service - self.assertTrue(self._check_icmp_traffic()) + self.assertTrue(self._check_icmp_traffic( + self.monitor_client, self.left_client, + self.left_port, self.right_port)[0]) @decorators.idempotent_id('6c54d9c5-075a-4a1f-bbe6-12c3c9abf1e3') @testtools.skipUnless(CONF.neutron_plugin_options.advanced_image_ref, @@ -260,4 +232,6 @@ class TestTaaSTrafficScenarios(manager.BaseTaasScenarioTests): with self._setup_topology(taas=False, use_taas_cloud_image=True, provider_net=True): # Check that traffic was NOT forwarded to TAAS service - self.assertFalse(self._check_icmp_traffic()) + self.assertFalse(self._check_icmp_traffic( + self.monitor_client, self.left_client, + self.left_port, self.right_port)[0]) diff --git a/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py index 7230cbbe3..8998f1c54 100644 --- a/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py +++ b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py @@ -61,3 +61,27 @@ class TapFlowsClient(base.BaseNetworkClient): def list_tap_flows(self, **filters): uri = '/taas/tap_flows' return self.list_resources(uri, **filters) + + +class TapMirrorsClient(base.BaseNetworkClient): + def create_tap_mirror(self, **kwargs): + uri = '/taas/tap_mirrors' + post_data = {'tap_mirror': kwargs} + return self.create_resource(uri, post_data) + + def update_tap_mirror(self, tap_mirror_id, **kwargs): + uri = '/taas/tap_mirrors/%s' % tap_mirror_id + post_data = {'tap_mirror': kwargs} + return self.update_resource(uri, post_data) + + def show_tap_mirror(self, tap_mirror_id, **fields): + uri = '/taas/tap_mirrors/%s' % tap_mirror_id + return self.show_resource(uri, **fields) + + def delete_tap_mirror(self, tap_mirror_id): + uri = '/taas/tap_mirrors/%s' % tap_mirror_id + return self.delete_resource(uri) + + def list_tap_mirrors(self, **filters): + uri = '/taas/tap_mirrors' + return self.list_resources(uri, **filters) diff --git a/zuul.d/2023_1_jobs.yaml b/zuul.d/2023_1_jobs.yaml index d69054cd3..0bda40383 100644 --- a/zuul.d/2023_1_jobs.yaml +++ b/zuul.d/2023_1_jobs.yaml @@ -318,5 +318,9 @@ nodeset: openstack-single-node-jammy override-checkout: stable/2023.1 vars: + network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: false diff --git a/zuul.d/2024_1_jobs.yaml b/zuul.d/2024_1_jobs.yaml index 674d1ab56..65fa58584 100644 --- a/zuul.d/2024_1_jobs.yaml +++ b/zuul.d/2024_1_jobs.yaml @@ -301,5 +301,9 @@ nodeset: openstack-single-node-jammy override-checkout: stable/2024.1 vars: + network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: false diff --git a/zuul.d/2024_2_jobs.yaml b/zuul.d/2024_2_jobs.yaml index e19767ca1..d54cf358a 100644 --- a/zuul.d/2024_2_jobs.yaml +++ b/zuul.d/2024_2_jobs.yaml @@ -287,3 +287,10 @@ parent: neutron-tempest-plugin-tap-as-a-service nodeset: openstack-single-node-jammy override-checkout: stable/2024.2 + vars: + network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter + devstack_localrc: + NEUTRON_DEPLOY_MOD_WSGI: false diff --git a/zuul.d/2025_1_jobs.yaml b/zuul.d/2025_1_jobs.yaml index 40d600f1b..f1e5b2b0c 100644 --- a/zuul.d/2025_1_jobs.yaml +++ b/zuul.d/2025_1_jobs.yaml @@ -252,3 +252,9 @@ parent: neutron-tempest-plugin-tap-as-a-service nodeset: openstack-single-node-noble override-checkout: stable/2025.1 + +- job: + name: neutron-tempest-plugin-tap-as-a-service-ovn-2025-1 + parent: neutron-tempest-plugin-tap-as-a-service-ovn + nodeset: openstack-single-node-noble + override-checkout: stable/2025.1 diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml index 232f0a1a5..11e66b7f4 100644 --- a/zuul.d/master_jobs.yaml +++ b/zuul.d/master_jobs.yaml @@ -1512,12 +1512,14 @@ network_api_extensions_tempest: - taas - taas-vlan-filter + - tap-mirror devstack_localrc: NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}" BUILD_TIMEOUT: 784 Q_AGENT: openvswitch Q_ML2_TENANT_NETWORK_TYPE: vxlan,vlan Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch + OVS_BRANCH: "branch-3.3" devstack_local_conf: post-config: /$NEUTRON_CORE_PLUGIN_CONF: @@ -1569,10 +1571,11 @@ q-svc: true neutron: true taas: true + tap_mirror: true taas_openvswitch_agent: true tempest: true dstat: true - irrelevant-files: + irrelevant-files: &taas_irrelevant_files - ^\.pylintrc$ - ^(test-|)requirements.txt$ - ^lower-constraints.txt$ @@ -1604,3 +1607,52 @@ # Ignore everything except for zuul.d/project.yaml - ^zuul.d/.*_jobs\.yaml$ - ^zuul.d/base-nested-switch.yaml + +- job: + name: neutron-tempest-plugin-tap-as-a-service-ovn + parent: neutron-tempest-plugin-base + description: | + Test tap-mirrors with OVN + roles: + - zuul: openstack/devstack + required-projects: + - openstack/neutron + - openstack/neutron-tempest-plugin + - openstack/tap-as-a-service + - openstack/tempest + vars: + tempest_concurrency: 4 + tempest_test_regex: ^neutron_tempest_plugin\.tap_as_a_service + tox_envlist: all + network_api_extensions_tempest: + - taas + - tap-mirror + devstack_localrc: + Q_AGENT: ovn + NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}" + BUILD_TIMEOUT: 784 + TAAS_SERVICE_DRIVER: "TAAS:TAAS:neutron_taas.services.taas.service_drivers.ovn.taas_ovn.TaasOvnDriver:default" + # mirroring is available from OVN 22.12.0 and use OVS 3.2.1 that also have this + # feature and builds with the above OVN + OVN_BRANCH: "branch-24.03" + OVS_BRANCH: "branch-3.3" + devstack_local_conf: + test-config: + $TEMPEST_CONFIG: + neutron_plugin_options: + image_is_advanced: true + advanced_image_flavor_ref: d1 + taas: + provider_physical_network: public + provider_segmentation_id: 100 + image_feature_enabled: + api_v2: true + devstack_plugins: + neutron: git://opendev.org/openstack/neutron.git + neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git + tap-as-a-service: git://opendev.org/openstack/tap-as-a-service.git + devstack_services: + tap_mirror: true + taas: true + tempest: true + irrelevant-files: *taas_irrelevant_files diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index f9f70dd5a..1720745e5 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -235,9 +235,11 @@ - neutron-tempest-plugin-vpnaas-2024-2 - neutron-tempest-plugin-vpnaas-2025-1 - neutron-tempest-plugin-tap-as-a-service + - neutron-tempest-plugin-tap-as-a-service-ovn - neutron-tempest-plugin-tap-as-a-service-2024-1 - neutron-tempest-plugin-tap-as-a-service-2024-2 - neutron-tempest-plugin-tap-as-a-service-2025-1 + - neutron-tempest-plugin-tap-as-a-service-ovn-2025-1 gate: jobs: diff --git a/zuul.d/xena_jobs.yaml b/zuul.d/xena_jobs.yaml index 847c61158..7d58efad6 100644 --- a/zuul.d/xena_jobs.yaml +++ b/zuul.d/xena_jobs.yaml @@ -284,5 +284,8 @@ required-projects: *required-projects-xena vars: network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: false diff --git a/zuul.d/yoga_jobs.yaml b/zuul.d/yoga_jobs.yaml index 2c18450d7..72a659ee8 100644 --- a/zuul.d/yoga_jobs.yaml +++ b/zuul.d/yoga_jobs.yaml @@ -306,5 +306,8 @@ required-projects: *required-projects-yoga vars: network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: false diff --git a/zuul.d/zed_jobs.yaml b/zuul.d/zed_jobs.yaml index fe98935fd..9c40f7694 100644 --- a/zuul.d/zed_jobs.yaml +++ b/zuul.d/zed_jobs.yaml @@ -327,5 +327,8 @@ required-projects: *required-projects-zed vars: network_api_extensions_common: *api_extensions + network_api_extensions_tempest: + - taas + - taas-vlan-filter devstack_localrc: NEUTRON_DEPLOY_MOD_WSGI: false