From 889566aa9735cade0842af45b2e040c23a60c8a1 Mon Sep 17 00:00:00 2001
From: Ihar Hrachyshka <ihrachys@redhat.com>
Date: Thu, 22 Aug 2024 18:08:29 +0000
Subject: [PATCH] Implement nested snat validation test scenario

It's disabled for ovn because it's not, yet, implemented for the driver.

See: https://review.opendev.org/c/openstack/neutron/+/926495

(The feature also requires special configuration for the driver that we
probably won't enable in the gate. This can be discussed though.)

Related-Bug: #2051935
Change-Id: Ie2e49a53857009446e22300e2fff292355cef058
---
 neutron_tempest_plugin/config.py              |  5 ++
 .../scenario/test_floatingip.py               | 54 +++++++++++++++++++
 zuul.d/master_jobs.yaml                       |  6 +++
 3 files changed, 65 insertions(+)

diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index a6d5c090..d2592017 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -77,6 +77,11 @@ NeutronPluginOptions = [
                default='openstackgate.local',
                help='dns_domain value configured at neutron.conf, which will '
                     'be used for the DNS configuration of the instances'),
+    cfg.BoolOpt('snat_rules_apply_to_nested_networks',
+                default=False,
+                help='Whether SNAT rules apply recursively to all connected '
+                'networks. This is the default behavior for ovs and '
+                'linuxbridge drivers.'),
 
     # Multicast tests settings
     cfg.StrOpt('multicast_group_range',
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index f2223967..24e58203 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -204,6 +204,60 @@ class DefaultSnatToExternal(FloatingIpTestCasesMixin,
                                        gateway_external_ip,
                                        servers=[proxy, src_server])
 
+    @decorators.idempotent_id('b911b124-b6cb-449d-83d9-b34f3665741d')
+    @utils.requires_ext(extension='extraroute', service='network')
+    @testtools.skipUnless(
+        CONF.neutron_plugin_options.snat_rules_apply_to_nested_networks,
+        "Backend doesn't enable nested SNAT.")
+    def test_nested_snat_external_ip(self):
+        """Check connectivity to an external IP from a nested network."""
+        gateway_external_ip = self._get_external_gateway()
+
+        if not gateway_external_ip:
+            raise self.skipTest("IPv4 gateway is not configured for public "
+                                "network or public_network_id is not "
+                                "configured")
+        proxy = self._create_server()
+        proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
+                                  CONF.validation.image_ssh_user,
+                                  pkey=self.keypair['private_key'])
+
+        # Create a nested router
+        router = self.create_router(
+            router_name=data_utils.rand_name('router'),
+            admin_state_up=True)
+
+        # Attach outer subnet to it
+        outer_port = self.create_port(self.network)
+        self.client.add_router_interface_with_port_id(router['id'],
+                                                      outer_port['id'])
+
+        # Attach a nested subnet to it
+        network = self.create_network()
+        subnet = self.create_subnet(network)
+        self.create_router_interface(router['id'], subnet['id'])
+
+        # Set up static routes in both directions
+        self.client.update_extra_routes(
+            self.router['id'],
+            outer_port['fixed_ips'][0]['ip_address'], subnet['cidr'])
+        self.client.update_extra_routes(
+            router['id'], self.subnet['gateway_ip'], '0.0.0.0/0')
+
+        # Create a server inside the nested network
+        src_server = self._create_server(create_floating_ip=False,
+                                         network=network)
+
+        # Validate that it can access external gw ip (via nested snat)
+        src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
+        ssh_client = ssh.Client(src_server_ip,
+                                CONF.validation.image_ssh_user,
+                                pkey=self.keypair['private_key'],
+                                proxy_client=proxy_client)
+        self.check_remote_connectivity(ssh_client,
+                                       gateway_external_ip,
+                                       servers=[proxy, src_server])
+
 
 class FloatingIPPortDetailsTest(FloatingIpTestCasesMixin,
                                 base.BaseTempestTestCase):
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 90379a1b..36955658 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -194,6 +194,7 @@
               image_is_advanced: true
               available_type_drivers: flat,geneve,vlan,gre,local,vxlan
               provider_net_base_segm_id: 1
+              snat_rules_apply_to_nested_networks: true
     irrelevant-files:
       - ^\.pylintrc$
       - ^(test-|)requirements.txt$
@@ -285,6 +286,7 @@
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
               firewall_driver: openvswitch
+              snat_rules_apply_to_nested_networks: true
     irrelevant-files:
       - ^\.pylintrc$
       - ^(test-|)requirements.txt$
@@ -400,6 +402,7 @@
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
               firewall_driver: iptables_hybrid
+              snat_rules_apply_to_nested_networks: true
     irrelevant-files:
       - ^\.pylintrc$
       - ^(test-|)requirements.txt$
@@ -575,6 +578,7 @@
               available_type_drivers: flat,vlan,local,vxlan
               q_agent: linuxbridge
               firewall_driver: iptables
+              snat_rules_apply_to_nested_networks: true
     irrelevant-files:
       - ^\.pylintrc$
       - ^(test-|)requirements.txt$
@@ -715,6 +719,7 @@
               available_type_drivers: local,flat,vlan,geneve
               is_igmp_snooping_enabled: True
               firewall_driver: ovn
+              snat_rules_apply_to_nested_networks: false
       zuul_copy_output:
         '{{ devstack_base_dir }}/data/ovs': 'logs'
         '{{ devstack_base_dir }}/data/ovn': 'logs'
@@ -937,6 +942,7 @@
               available_type_drivers: flat,geneve,vlan,gre,local,vxlan
               l3_agent_mode: dvr_snat
               firewall_driver: openvswitch
+              snat_rules_apply_to_nested_networks: true
     group-vars:
       subnode:
         devstack_services: