Merge "Add stadium projects jobs to the gate queue"
diff --git a/.zuul.yaml b/.zuul.yaml
index 29ca617..e9d28c9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -749,6 +749,9 @@
     override-checkout: stable/queens
     vars:
       branch_override: stable/queens
+      # TODO(slaweq): remove trunks subport_connectivity test from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
+      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)"
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
@@ -866,7 +869,6 @@
         NETWORKING_BGPVPN_DRIVER: "BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.service_drivers.bagpipe.bagpipe_v2.BaGPipeBGPVPNDriver:default"
         BAGPIPE_DATAPLANE_DRIVER_IPVPN: "ovs"
         BAGPIPE_BGP_PEERS: "-"
-        USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_bgpvpn) | join(',') }}"
       devstack_plugins:
         networking-bgpvpn: https://git.openstack.org/openstack/networking-bgpvpn
diff --git a/neutron_tempest_plugin/api/admin/test_network_segment_range.py b/neutron_tempest_plugin/api/admin/test_network_segment_range.py
index e747fed..eacfbba 100644
--- a/neutron_tempest_plugin/api/admin/test_network_segment_range.py
+++ b/neutron_tempest_plugin/api/admin/test_network_segment_range.py
@@ -193,6 +193,16 @@
         for network_id in network_ids:
             self.admin_client.delete_network(network_id)
 
+    def _compare_segment_ranges(self, reference, observed):
+        self.assertEqual(reference['id'], observed['id'])
+        self.assertEqual(reference['name'], observed['name'])
+        self.assertFalse(observed['default'])
+        self.assertFalse(observed['shared'])
+        self.assertEqual(reference['project_id'], observed['project_id'])
+        self.assertEqual(reference['network_type'], observed['network_type'])
+        self.assertEqual(reference['minimum'], observed['minimum'])
+        self.assertEqual(reference['maximum'], observed['maximum'])
+
     @decorators.idempotent_id('54fa26c9-37b5-4df4-a934-a705f29920fc')
     def test_show_network_segment_range(self):
         # Creates a network segment range
@@ -201,18 +211,7 @@
         body = self.admin_client.show_network_segment_range(
             network_segment_range['id'])
         observed_range = body['network_segment_range']
-        self.assertEqual(network_segment_range['id'], observed_range['id'])
-        self.assertEqual(network_segment_range['name'], observed_range['name'])
-        self.assertFalse(observed_range['default'])
-        self.assertFalse(observed_range['shared'])
-        self.assertEqual(network_segment_range['project_id'],
-                         observed_range['project_id'])
-        self.assertEqual(network_segment_range['network_type'],
-                         observed_range['network_type'])
-        self.assertEqual(network_segment_range['minimum'],
-                         observed_range['minimum'])
-        self.assertEqual(network_segment_range['maximum'],
-                         observed_range['maximum'])
+        self._compare_segment_ranges(network_segment_range, observed_range)
 
     @decorators.idempotent_id('17139cc1-4826-4bf9-9c39-85b74894d938')
     def test_list_network_segment_ranges(self):
@@ -225,8 +224,9 @@
 
         body = self.admin_client.list_network_segment_ranges(
             id=network_segment_range['id'])
-        list_range_ids = [r['id'] for r in body['network_segment_ranges']]
-        self.assertIn(network_segment_range['id'], list_range_ids)
+        self.assertEqual(1, len(body['network_segment_ranges']))
+        observed_range = body['network_segment_ranges'][0]
+        self._compare_segment_ranges(network_segment_range, observed_range)
 
     @decorators.idempotent_id('42959544-9956-4b0c-aec6-d56533323924')
     def test_delete_network_segment_range_failed_with_segment_referenced(
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 4b2ddcd..5a29aa1 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -26,6 +26,7 @@
 from tempest.lib import exceptions as lib_exc
 
 from neutron_tempest_plugin.api import base as base_api
+from neutron_tempest_plugin.common import shell
 from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import constants
@@ -385,7 +386,8 @@
             for server in servers:
                 kwargs = {}
                 try:
-                    kwargs['port'] = server['port_forwarding']['external_port']
+                    kwargs['port'] = (
+                        server['port_forwarding_tcp']['external_port'])
                 except KeyError:
                     pass
                 ssh_client = ssh.Client(
@@ -405,3 +407,34 @@
             if log_errors:
                 self._log_console_output(servers)
             raise
+
+    def nc_listen(self, server, ssh_client, port, protocol, echo_msg):
+        """Create nc server listening on the given TCP/UDP port.
+
+        Listener is created always on remote host.
+        """
+        udp = ''
+        if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
+            udp = '-u'
+        cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo %(msg)s &" % {
+            'udp': udp, 'port': port, 'msg': echo_msg}
+        try:
+            return ssh_client.exec_command(cmd)
+        except lib_exc.SSHTimeout as ssh_e:
+            LOG.debug(ssh_e)
+            self._log_console_output([server])
+            raise
+
+    def nc_client(self, ip_address, port, protocol):
+        """Check connectivity to TCP/UDP port at host via nc.
+
+        Client is always executed locally on host where tests are executed.
+        """
+        udp = ''
+        if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
+            udp = '-u'
+        cmd = 'echo "knock knock" | nc -w 1 %(udp)s %(host)s %(port)s' % {
+            'udp': udp, 'host': ip_address, 'port': port}
+        result = shell.execute_local_command(cmd)
+        self.assertEqual(0, result.exit_status)
+        return result.stdout
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index 3715fad..6d2239d 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -13,10 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from neutron_lib import constants
 from oslo_log import log
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
+from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
 
@@ -42,12 +44,13 @@
         cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
         cls.keypair = cls.create_keypair()
 
-    @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
-    def test_port_forwarding_to_2_servers(self):
-        internal_tcp_port = 22
+    def _prepare_resources(self, num_servers, internal_tcp_port, protocol):
         servers = []
-        for i in range(1, 3):
-            external_tcp_port = 1000 + i
+        external_port_base = 1025
+        for i in range(1, num_servers + 1):
+            internal_udp_port = internal_tcp_port + 10
+            external_tcp_port = external_port_base + i
+            external_udp_port = external_tcp_port + 10
             name = data_utils.rand_name("server-%s" % i)
             port = self.create_port(
                 self.network,
@@ -59,13 +62,55 @@
                 networks=[{'port': port['id']}])['server']
             server['name'] = name
             self.wait_for_server_active(server)
-            server['port_forwarding'] = self.create_port_forwarding(
+            server['port_forwarding_tcp'] = self.create_port_forwarding(
                 self.fip['id'],
                 internal_port_id=port['id'],
                 internal_ip_address=port['fixed_ips'][0]['ip_address'],
                 internal_port=internal_tcp_port,
                 external_port=external_tcp_port,
-                protocol="tcp")
+                protocol=constants.PROTO_NAME_TCP)
+            server['port_forwarding_udp'] = self.create_port_forwarding(
+                self.fip['id'],
+                internal_port_id=port['id'],
+                internal_ip_address=port['fixed_ips'][0]['ip_address'],
+                internal_port=internal_udp_port,
+                external_port=external_udp_port,
+                protocol=constants.PROTO_NAME_UDP)
             servers.append(server)
+        return servers
 
+    def _test_udp_port_forwarding(self, servers):
+        for server in servers:
+            msg = "%s-UDP-test" % server['name']
+            ssh_client = ssh.Client(
+                self.fip['floating_ip_address'],
+                CONF.validation.image_ssh_user,
+                pkey=self.keypair['private_key'],
+                port=server['port_forwarding_tcp']['external_port'])
+            self.nc_listen(server,
+                           ssh_client,
+                           server['port_forwarding_udp']['internal_port'],
+                           constants.PROTO_NAME_UDP,
+                           msg)
+        for server in servers:
+            expected_msg = "%s-UDP-test" % server['name']
+            self.assertIn(
+                expected_msg, self.nc_client(
+                    self.fip['floating_ip_address'],
+                    server['port_forwarding_udp']['external_port'],
+                    constants.PROTO_NAME_UDP))
+
+    @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
+    def test_port_forwarding_to_2_servers(self):
+        udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
+                       'direction': constants.INGRESS_DIRECTION,
+                       'remote_ip_prefix': '0.0.0.0/0'}
+        self.create_secgroup_rules(
+            [udp_sg_rule], secgroup_id=self.secgroup['id'])
+        servers = self._prepare_resources(
+            num_servers=2, internal_tcp_port=22,
+            protocol=constants.PROTO_NAME_TCP)
+        # Test TCP port forwarding by SSH to each server
         self.check_servers_hostnames(servers)
+        # And now test UDP port forwarding using nc
+        self._test_udp_port_forwarding(servers)