Merge "Create security groups with the same name"
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..6fe7c34
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,4 @@
+reno>=3.1.0 # Apache-2.0
+sphinx>=2.0.0,!=2.1.0 # BSD
+openstackdocstheme>=2.2.1 # Apache-2.0
+
diff --git a/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py b/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py
index d0adcb8..9dc4438 100644
--- a/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py
+++ b/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py
@@ -13,6 +13,7 @@
 #    under the License.
 
 from neutron_lib import constants
+from neutron_lib.utils import test
 from tempest.lib import decorators
 
 from neutron_tempest_plugin.api import base
@@ -33,6 +34,7 @@
         cls.cidr = cls.subnet['cidr']
         cls.port = cls.create_port(cls.network)
 
+    @test.unstable_test("bug 1906654")
     @decorators.idempotent_id('f164801e-1dd8-4b8b-b5d3-cc3ac77cfaa5')
     def test_dhcp_port_status_active(self):
 
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index d63dec8..4833c71 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -140,6 +140,17 @@
         cls.conntrack_helpers = []
 
     @classmethod
+    def reserve_external_subnet_cidrs(cls):
+        client = cls.os_admin.network_client
+        ext_nets = client.list_networks(
+            **{"router:external": True})['networks']
+        for ext_net in ext_nets:
+            ext_subnets = client.list_subnets(
+                network_id=ext_net['id'])['subnets']
+            for ext_subnet in ext_subnets:
+                cls.reserve_subnet_cidr(ext_subnet['cidr'])
+
+    @classmethod
     def resource_cleanup(cls):
         if CONF.service_available.neutron:
             # Clean up trunks
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability.py b/neutron_tempest_plugin/api/test_network_ip_availability.py
index e798680..22d2fc6 100644
--- a/neutron_tempest_plugin/api/test_network_ip_availability.py
+++ b/neutron_tempest_plugin/api/test_network_ip_availability.py
@@ -175,3 +175,22 @@
 class NetworksIpAvailabilityIPv6Test(NetworksIpAvailabilityIPv4Test):
 
     _ip_version = lib_constants.IP_VERSION_6
+
+    def setUp(self):
+        super(NetworksIpAvailabilityIPv6Test, self).setUp()
+        net_name = data_utils.rand_name('network')
+        self.network = self.create_network(network_name=net_name)
+
+    @decorators.idempotent_id('0d5a03f2-fdb7-4ec3-b746-734c51d74b69')
+    def test_list_ipv6_ip_availability_after_subnet_and_ports(self):
+        subnet = self.create_subnet(self.network, ip_version=self._ip_version,
+                                    enable_dhcp=False)
+        prefix = netaddr.IPNetwork(subnet['cidr']).prefixlen
+        body = self.admin_client.list_network_ip_availabilities()
+        used_ips_before_port_create = self._get_used_ips(self.network, body)
+        self.create_port(self.network)
+        net_availability = self.admin_client.list_network_ip_availabilities()
+        self._assert_total_and_used_ips(
+            used_ips_before_port_create + 1,
+            calc_total_ips(prefix, self._ip_version),
+            self.network, net_availability)
diff --git a/neutron_tempest_plugin/api/test_ports.py b/neutron_tempest_plugin/api/test_ports.py
index c59ee83..f1dfe5c 100644
--- a/neutron_tempest_plugin/api/test_ports.py
+++ b/neutron_tempest_plugin/api/test_ports.py
@@ -15,6 +15,7 @@
 
 import copy
 
+from neutron_lib import constants as lib_constants
 from tempest.common import utils
 from tempest.lib import decorators
 
@@ -60,6 +61,20 @@
         body = self.client.list_ports(id=body['port']['id'])['ports'][0]
         self.assertEqual('d2', body['description'])
 
+    @decorators.idempotent_id('3ae162e8-ff00-490c-a423-6a88e48f1ed6')
+    def test_create_update_port_security(self):
+        body = self.create_port(self.network,
+                                port_security_enabled=True)
+        self.assertTrue(body['port_security_enabled'])
+        body = self.client.list_ports(id=body['id'])['ports'][0]
+        self.assertTrue(body['port_security_enabled'])
+        body = self.client.update_port(body['id'],
+                                       port_security_enabled=False,
+                                       security_groups=[])
+        self.assertFalse(body['port']['port_security_enabled'])
+        body = self.client.list_ports(id=body['port']['id'])['ports'][0]
+        self.assertFalse(body['port_security_enabled'])
+
     @decorators.idempotent_id('539fbefe-fb36-48aa-9a53-8c5fbd44e492')
     @utils.requires_ext(extension="dns-integration",
                        service="network")
@@ -137,6 +152,28 @@
         self.assertEqual(expected, subnets)
 
 
+class PortsIpv6TestJSON(base.BaseNetworkTest):
+
+    _ip_version = lib_constants.IP_VERSION_6
+
+    @classmethod
+    def resource_setup(cls):
+        super(PortsIpv6TestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    @decorators.idempotent_id('b85879fb-4852-4b99-aa32-3f8a7a6a3f01')
+    def test_add_ipv6_ips_to_port(self):
+        s = self.create_subnet(self.network, ip_version=self._ip_version)
+        port = self.create_port(self.network)
+        # request another IP on the same subnet
+        port['fixed_ips'].append({'subnet_id': s['id']})
+        updated = self.client.update_port(port['id'],
+                                          fixed_ips=port['fixed_ips'])
+        subnets = [ip['subnet_id'] for ip in updated['port']['fixed_ips']]
+        expected = [s['id'], s['id']]
+        self.assertEqual(expected, subnets)
+
+
 class PortsSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
     resource = 'port'
diff --git a/neutron_tempest_plugin/api/test_ports_negative.py b/neutron_tempest_plugin/api/test_ports_negative.py
new file mode 100644
index 0000000..e327c25
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_ports_negative.py
@@ -0,0 +1,76 @@
+#    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 neutron_lib.db import constants as db_const
+from oslo_utils import uuidutils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+LONG_NAME_NG = 'z' * (db_const.NAME_FIELD_SIZE + 1)
+LONG_DESCRIPTION_NG = 'z' * (db_const.LONG_DESCRIPTION_FIELD_SIZE + 1)
+
+
+class PortsNegativeTestJSON(base.BaseNetworkTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(PortsNegativeTestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('0cbd256a-a6d4-4afa-a039-44cc13704bab')
+    def test_add_port_with_too_long_name(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_port,
+                          self.network, name=LONG_NAME_NG)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('e10da38c-1071-49c9-95c2-0c451e18ae31')
+    def test_add_port_with_too_long_description(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_port,
+                          self.network, description=LONG_DESCRIPTION_NG)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('5b69a905-3a84-43a4-807a-1a67ab85caeb')
+    def test_add_port_with_nonexist_tenant_id(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_port,
+                          self.network,
+                          project_id=uuidutils.generate_uuid())
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('7cf473ae-7ec8-4834-ae17-9ef6ec6b8a32')
+    def test_add_port_with_nonexist_network_id(self):
+        network = self.network
+        network['id'] = uuidutils.generate_uuid()
+        self.assertRaises(lib_exc.NotFound,
+                          self.create_port,
+                          network)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('cad2d349-25fa-490e-9675-cd2ea24164bc')
+    def test_add_port_with_nonexist_security_groups_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.create_port,
+                          self.network,
+                          security_groups=[uuidutils.generate_uuid()])
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('9b0a4152-9aa4-4169-9b2c-579609e2fb4a')
+    def test_add_port_with_illegal_ip(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_port,
+                          self.network,
+                          allowed_address_pairs=[{"ip_address: 12.12.12.a"}])
diff --git a/neutron_tempest_plugin/api/test_qos_negative.py b/neutron_tempest_plugin/api/test_qos_negative.py
index 8432c6a..f6c4afc 100644
--- a/neutron_tempest_plugin/api/test_qos_negative.py
+++ b/neutron_tempest_plugin/api/test_qos_negative.py
@@ -67,6 +67,28 @@
                           self.client.update_qos_policy, policy['id'],
                           description=LONG_DESCRIPTION_NG)
 
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('88b54ab0-804b-446c-bc19-8e54222d70ef')
+    def test_get_non_existent_qos_policy(self):
+        non_exist_id = data_utils.rand_name('qos_policy')
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_client.show_qos_policy, non_exist_id)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('21050859-1284-4bf5-b05a-13846f83988f')
+    def test_update_non_existent_qos_policy(self):
+        non_exist_id = data_utils.rand_name('qos_policy')
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_client.update_qos_policy, non_exist_id,
+                          shared=False)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('09e435b7-44d3-4f9d-8aa8-c295d46b5866')
+    def test_delete_non_existent_qos_policy(self):
+        non_exist_id = data_utils.rand_name('qos_policy')
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_client.delete_qos_policy, non_exist_id)
+
 
 class QosBandwidthLimitRuleNegativeTestJSON(base.BaseAdminNetworkTest):
 
diff --git a/neutron_tempest_plugin/api/test_routers_negative.py b/neutron_tempest_plugin/api/test_routers_negative.py
index 8700761..86d58e2 100644
--- a/neutron_tempest_plugin/api/test_routers_negative.py
+++ b/neutron_tempest_plugin/api/test_routers_negative.py
@@ -19,6 +19,10 @@
 import testtools
 
 from neutron_tempest_plugin.api import base_routers as base
+from neutron_tempest_plugin import config
+
+
+CONF = config.CONF
 
 
 class RoutersNegativeTestBase(base.BaseRouterTest):
@@ -88,6 +92,23 @@
             self.client.add_router_interface_with_port_id,
             self.router['id'], invalid_id)
 
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('dad7a8ba-2726-11eb-82dd-74e5f9e2a801')
+    def test_remove_associated_ports(self):
+        self.client.update_router(
+            self.router['id'],
+            external_gateway_info={
+                'network_id': CONF.network.public_network_id})
+        network = self.create_network()
+        subnet = self.create_subnet(network)
+        self.create_router_interface(self.router['id'], subnet['id'])
+        port_ids = [
+            item['id'] for item in self.admin_client.list_ports(
+                device_id=self.router['id'])['ports']]
+        for port_id in port_ids:
+            with testtools.ExpectedException(lib_exc.Conflict):
+                self.admin_client.delete_port(port_id)
+
 
 class DvrRoutersNegativeTest(RoutersNegativeTestBase):
 
diff --git a/neutron_tempest_plugin/common/ip.py b/neutron_tempest_plugin/common/ip.py
index d981770..7b172b0 100644
--- a/neutron_tempest_plugin/common/ip.py
+++ b/neutron_tempest_plugin/common/ip.py
@@ -57,6 +57,20 @@
         return shell.execute(command_line, ssh_client=self.ssh_client,
                              timeout=self.timeout).stdout
 
+    def configure_vlan(self, addresses, port, vlan_tag, subport_ips):
+        port_device = get_port_device_name(addresses=addresses, port=port)
+        subport_device = '{!s}.{!s}'.format(port_device, vlan_tag)
+        LOG.debug('Configuring VLAN subport interface %r on top of interface '
+                  '%r with IPs: %s', subport_device, port_device,
+                  ', '.join(subport_ips))
+
+        self.add_link(link=port_device, name=subport_device, link_type='vlan',
+                      segmentation_id=vlan_tag)
+        self.set_link(device=subport_device, state='up')
+        for subport_ip in subport_ips:
+            self.add_address(address=subport_ip, device=subport_device)
+        return subport_device
+
     def configure_vlan_subport(self, port, subport, vlan_tag, subnets):
         addresses = self.list_addresses()
         try:
@@ -77,18 +91,19 @@
                 "Unable to get IP address and subnet prefix lengths for "
                 "subport")
 
-        port_device = get_port_device_name(addresses=addresses, port=port)
-        subport_device = '{!s}.{!s}'.format(port_device, vlan_tag)
-        LOG.debug('Configuring VLAN subport interface %r on top of interface '
-                  '%r with IPs: %s', subport_device, port_device,
-                  ', '.join(subport_ips))
+        return self.configure_vlan(addresses, port, vlan_tag, subport_ips)
 
-        self.add_link(link=port_device, name=subport_device, link_type='vlan',
-                      segmentation_id=vlan_tag)
-        self.set_link(device=subport_device, state='up')
-        for subport_ip in subport_ips:
-            self.add_address(address=subport_ip, device=subport_device)
-        return subport_device
+    def configure_vlan_transparent(self, port, vlan_tag, ip_addresses):
+        addresses = self.list_addresses()
+        try:
+            subport_device = get_vlan_device_name(addresses, ip_addresses)
+        except ValueError:
+            pass
+        else:
+            LOG.debug('Interface %r already configured.', subport_device)
+            return subport_device
+
+        return self.configure_vlan(addresses, port, vlan_tag, ip_addresses)
 
     def list_namespaces(self):
         namespaces_output = self.execute("netns")
@@ -128,6 +143,23 @@
         # ip addr add 192.168.1.1/24 dev em1
         return self.execute('address', 'add', address, 'dev', device)
 
+    def delete_address(self, address, device):
+        # ip addr del 192.168.1.1/24 dev em1
+        return self.execute('address', 'del', address, 'dev', device)
+
+    def add_route(self, address, device, gateway=None):
+        if gateway:
+            # ip route add 192.168.1.0/24 via 192.168.22.1 dev em1
+            return self.execute(
+                'route', 'add', address, 'via', gateway, 'dev', device)
+        else:
+            # ip route add 192.168.1.0/24 dev em1
+            return self.execute('route', 'add', address, 'dev', device)
+
+    def delete_route(self, address, device):
+        # ip route del 192.168.1.0/24 dev em1
+        return self.execute('route', 'del', address, 'dev', device)
+
     def list_routes(self, *args):
         output = self.execute('route', 'show', *args)
         return list(parse_routes(output))
@@ -312,6 +344,15 @@
     raise ValueError(msg)
 
 
+def get_vlan_device_name(addresses, ip_addresses):
+    for address in list_ip_addresses(addresses=addresses,
+            ip_addresses=ip_addresses):
+        return address.device.name
+
+    msg = "Fixed IPs {0!r} not found on server.".format(' '.join(ip_addresses))
+    raise ValueError(msg)
+
+
 def _get_ip_address_prefix_len_pairs(port, subnets):
     subnets = {subnet['id']: subnet for subnet in subnets}
     for fixed_ip in port['fixed_ips']:
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
index c6204a4..8334521 100644
--- a/neutron_tempest_plugin/common/ssh.py
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -286,6 +286,13 @@
                 command=shell, host=self.host, script=script, stderr=stderr,
                 stdout=stdout, exit_status=exit_status)
 
+    def get_hostname(self):
+        """Retrieve the remote machine hostname"""
+        try:
+            return self.exec_command('hostname')
+        except exceptions.SSHExecCommandFailed:
+            return self.exec_command('cat /etc/hostname')
+
 
 def _buffer_to_string(data_buffer, encoding):
     return data_buffer.decode(encoding).replace("\r\n", "\n").replace(
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index c0e21c1..2290d0f 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -120,18 +120,6 @@
                     'This is required if advanced image has to be used in '
                     'tests.'),
 
-    # Enable/disable metadata over IPv6 tests. This feature naturally
-    # does not have an API extension, but at the time of first implementation
-    # it works only on victoria+ deployments with dhcp- and/or l3-agents
-    # (which in the gate is the same as non-ovn jobs).
-    cfg.BoolOpt('ipv6_metadata',
-                default=True,
-                help='Enable metadata over IPv6 tests where the feature is '
-                     'implemented, disable where it is not. Use this instead '
-                     'of network-feature-enabled.api_extensions, since API '
-                     'extensions do not make sense for a feature not '
-                     'exposed on the API.'),
-
     # Option for creating QoS policies configures as "shared".
     # The default is false in order to prevent undesired usage
     # while testing in parallel.
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index c7c5459..127701c 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -205,7 +205,6 @@
         else:
             router = cls.create_admin_router(**kwargs)
         LOG.debug("Created router %s", router['name'])
-        cls.routers.append(router)
         return router
 
     @removals.remove(version='Stein',
@@ -307,13 +306,19 @@
             self._log_ns_network_status(ns_name=ns_name)
 
     def _log_ns_network_status(self, ns_name=None):
-        local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
+        try:
+            local_ips = ip_utils.IPCommand(namespace=ns_name).list_addresses()
+            local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
+            arp_table = ip_utils.arp_table()
+        except exceptions.ShellCommandFailed:
+            LOG.debug('Namespace %s has been deleted synchronously during the '
+                      'host network collection process', ns_name)
+            return
+
         LOG.debug('Namespace %s; IP Addresses:\n%s',
                   ns_name, '\n'.join(str(r) for r in local_ips))
-        local_routes = ip_utils.IPCommand(namespace=ns_name).list_routes()
         LOG.debug('Namespace %s; Local routes:\n%s',
                   ns_name, '\n'.join(str(r) for r in local_routes))
-        arp_table = ip_utils.arp_table()
         LOG.debug('Namespace %s; Local ARP table:\n%s',
                   ns_name, '\n'.join(str(r) for r in arp_table))
 
@@ -513,7 +518,7 @@
                     pkey=self.keypair['private_key'],
                     **kwargs)
                 self.assertIn(server['name'],
-                              ssh_client.exec_command('hostname'))
+                              ssh_client.get_hostname())
         except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             if log_errors:
diff --git a/neutron_tempest_plugin/scenario/test_dhcp.py b/neutron_tempest_plugin/scenario/test_dhcp.py
new file mode 100644
index 0000000..b95eaa2
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_dhcp.py
@@ -0,0 +1,94 @@
+# All Rights Reserved.
+#
+#    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_log import log
+from paramiko import ssh_exception as ssh_exc
+from tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+CONF = config.CONF
+LOG = log.getLogger(__name__)
+
+
+class DHCPTest(base.BaseTempestTestCase):
+
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    @classmethod
+    def resource_setup(cls):
+        super(DHCPTest, cls).resource_setup()
+        cls.rand_name = data_utils.rand_name(
+            cls.__name__.rsplit('.', 1)[-1])
+        cls.network = cls.create_network(name=cls.rand_name)
+        cls.subnet = cls.create_subnet(
+            network=cls.network, name=cls.rand_name)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+        cls.keypair = cls.create_keypair(name=cls.rand_name)
+        cls.security_group = cls.create_security_group(name=cls.rand_name)
+        cls.create_loginable_secgroup_rule(cls.security_group['id'])
+
+    @utils.requires_ext(extension='extra_dhcp_opt', service='network')
+    @decorators.idempotent_id('58f7c094-1980-4e03-b0d3-6c4dd27217b1')
+    def test_extra_dhcp_opts(self):
+        """This test case tests DHCP extra options configured for Neutron port.
+
+        Test is checking just extra option "15" which is domain-name
+        according to the RFC 2132:
+        https://tools.ietf.org/html/rfc2132#section-5.3
+
+        To test that option, there is spawned VM connected to the port with
+        configured extra_dhcp_opts and test asserts that search domain name is
+        configured inside VM in /etc/resolv.conf file
+        """
+
+        test_domain = "test.domain"
+        extra_dhcp_opts = [
+            {'opt_name': 'domain-name',
+             'opt_value': '"%s"' % test_domain}]
+        port = self.create_port(
+            network=self.network, name=self.rand_name,
+            security_groups=[self.security_group['id']],
+            extra_dhcp_opts=extra_dhcp_opts)
+        floating_ip = self.create_floatingip(port=port)
+
+        server = self.create_server(
+            flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref,
+            key_name=self.keypair['name'],
+            networks=[{'port': port['id']}])
+        self.wait_for_server_active(server['server'])
+        self.wait_for_guest_os_ready(server['server'])
+
+        try:
+            ssh_client = ssh.Client(
+                floating_ip['floating_ip_address'],
+                CONF.validation.image_ssh_user,
+                pkey=self.keypair['private_key'])
+            vm_resolv_conf = ssh_client.exec_command(
+                "cat /etc/resolv.conf")
+            self.assertIn(test_domain, vm_resolv_conf)
+        except (lib_exc.SSHTimeout,
+                ssh_exc.AuthenticationException,
+                AssertionError) as error:
+            LOG.debug(error)
+            self._log_console_output([server])
+            self._log_local_network_status()
+            raise
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index c620233..406af3d 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -59,7 +59,7 @@
             CONF.validation.image_ssh_user,
             pkey=self.keypair['private_key'])
 
-        self.assertIn('luke', ssh_client.exec_command('hostname'))
+        self.assertIn('luke', ssh_client.get_hostname())
 
         leia_port = self.client.list_ports(
             network_id=self.network['id'],
diff --git a/neutron_tempest_plugin/scenario/test_ipv6.py b/neutron_tempest_plugin/scenario/test_ipv6.py
index 732c96d..4237d4f 100644
--- a/neutron_tempest_plugin/scenario/test_ipv6.py
+++ b/neutron_tempest_plugin/scenario/test_ipv6.py
@@ -84,6 +84,7 @@
     @tempest_utils.requires_ext(extension="router", service="network")
     def resource_setup(cls):
         super(IPv6Test, cls).resource_setup()
+        cls.reserve_external_subnet_cidrs()
         cls._setup_basic_resources()
 
     @classmethod
diff --git a/neutron_tempest_plugin/scenario/test_metadata.py b/neutron_tempest_plugin/scenario/test_metadata.py
index 3897183..91ecc97 100644
--- a/neutron_tempest_plugin/scenario/test_metadata.py
+++ b/neutron_tempest_plugin/scenario/test_metadata.py
@@ -16,6 +16,7 @@
 
 from neutron_lib import constants as nlib_const
 from oslo_log import log as logging
+from tempest.common import utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 import testtools
@@ -45,10 +46,17 @@
     force_tenant_isolation = False
 
     @classmethod
+    def skip_checks(cls):
+        super(MetadataTest, cls).skip_checks()
+        if not utils.is_network_feature_enabled('ipv6_metadata'):
+            raise cls.skipException("Metadata over IPv6 is not enabled")
+
+    @classmethod
     def resource_setup(cls):
         super(MetadataTest, cls).resource_setup()
         cls.rand_name = data_utils.rand_name(
             cls.__name__.rsplit('.', 1)[-1])
+        cls.reserve_external_subnet_cidrs()
         cls.network = cls.create_network(name=cls.rand_name)
         cls.subnet_v4 = cls.create_subnet(
             network=cls.network, name=cls.rand_name)
@@ -113,11 +121,9 @@
         return interface
 
     @testtools.skipUnless(
-        (CONF.neutron_plugin_options.ipv6_metadata and
-         (CONF.neutron_plugin_options.advanced_image_ref or
-          CONF.neutron_plugin_options.default_image_is_advanced)),
-        'Advanced image and neutron_plugin_options.ipv6_metadata=True '
-        'is required to run this test.')
+        CONF.neutron_plugin_options.advanced_image_ref or
+        CONF.neutron_plugin_options.default_image_is_advanced,
+        'Advanced image is required to run this test.')
     @decorators.idempotent_id('e680949a-f1cc-11ea-b49a-cba39bbbe5ad')
     def test_metadata_routed(self):
         use_advanced_image = (
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index a4711ae..6a5d3c9 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 from neutron_lib import constants
-from neutron_lib.utils import test
 from oslo_log import log
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -186,7 +185,6 @@
                     server[0]['id'],
                     server[0]['port_forwarding_tcp']['external_port'])))
 
-    @test.unstable_test("bug 1896735")
     @decorators.idempotent_id('6d05b1b2-6109-4c30-b402-1503f4634acb')
     def test_port_forwarding_editing_and_deleting_udp_rule(self):
         test_ext_port = 3344
@@ -243,7 +241,6 @@
                     server[0]['id'],
                     server[0]['port_forwarding_udp']['external_port'])))
 
-    @test.unstable_test("bug 1896735")
     @decorators.idempotent_id('5971881d-06a0-459e-b636-ce5d1929e2d4')
     def test_port_forwarding_to_2_fixed_ips(self):
         port = self.create_port(self.network,
diff --git a/neutron_tempest_plugin/scenario/test_ports.py b/neutron_tempest_plugin/scenario/test_ports.py
index 3b0408a..b3aeb87 100644
--- a/neutron_tempest_plugin/scenario/test_ports.py
+++ b/neutron_tempest_plugin/scenario/test_ports.py
@@ -12,6 +12,9 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import ipaddress
+
+from oslo_log import log as logging
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -20,6 +23,7 @@
 from neutron_tempest_plugin.scenario import base
 from neutron_tempest_plugin.scenario import constants as const
 
+LOG = logging.getLogger(__name__)
 CONF = config.CONF
 
 
@@ -78,3 +82,34 @@
                 self.os_primary.servers_client,
                 servers[0]['server']['id'])
             self._try_delete_resource(self.delete_floatingip, fips[0])
+
+    @decorators.idempotent_id('62e32802-1d21-11eb-b322-74e5f9e2a801')
+    def test_port_with_fixed_ip(self):
+        """Test scenario:
+
+        1) Get the last IP from the range of Subnet "Allocation pool"
+        2) Create Port with fixed_ip resolved in #1
+        3) Create a VM using updated Port in #2 and add Floating IP
+        4) Check SSH access to VM
+        """
+        ip_range = [str(ip) for ip in ipaddress.IPv4Network(
+            self.subnet['cidr'])]
+        # Because of the tests executed in Parallel the IP may already
+        # be in use, so we'll try using IPs from Allocation pool
+        # (in reverse order) up until Port is successfully created.
+        for ip in reversed(ip_range):
+            try:
+                port = self.create_port(
+                    self.network,
+                    name=data_utils.rand_name("fixed_ip_port"),
+                    security_groups=[self.secgroup['id']],
+                    fixed_ips=[{'ip_address': ip}])
+                if port is not None:
+                    break
+            except Exception as e:
+                LOG.warn('Failed to create Port, using Fixed_IP:{}, '
+                         'the Error was:{}'.format(ip, e))
+        fip, server = self._create_instance_with_port(port)
+        self.check_connectivity(fip[0]['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index 6cd6b25..77520a7 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -191,32 +191,26 @@
 
     @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
     def test_qos_basic_and_update(self):
-        """This test covers both:
+        """This test covers following scenarios:
 
-            1) Basic QoS functionality
-            This is a basic test that check that a QoS policy with
-            a bandwidth limit rule is applied correctly by sending
-            a file from the instance to the test node.
-            Then calculating the bandwidth every ~1 sec by the number of bits
-            received / elapsed time.
+            1) Create a QoS policy associated with the network.
+            Expected result: BW is limited according the values set in
+            QoS policy rule.
 
-            2) Update QoS policy
-            Administrator has the ability to update existing QoS policy,
-            this test is planned to verify that:
-            - actual BW is affected as expected after updating QoS policy.
-            Test scenario:
-            1) Associating QoS Policy with "Original_bandwidth"
-               to the test node
-            2) BW validation - by downloading file on test node.
-               ("Original_bandwidth" is expected)
-            3) Updating existing QoS Policy to a new BW value
-               "Updated_bandwidth"
-            4) BW validation - by downloading file on test node.
-               ("Updated_bandwidth" is expected)
-            Note:
-            There are two options to associate QoS policy to VM:
-            "Neutron Port" or "Network", in this test
-            both options are covered.
+            2) Update QoS policy associated with the network.
+            Expected result: BW is limited according the new values
+            set in QoS policy rule.
+
+            3) Create a new QoS policy associated with the VM port.
+            Expected result: BW is limited according the values set in
+            new QoS policy rule.
+            Note: Neutron port is prioritized higher than Network, means
+            that: "Neutron Port Priority" is also covered.
+
+            4) Update QoS policy associated with the VM port.
+            Expected result: BW is limited according the new values set
+            in QoS policy rule.
+
         """
 
         # Setup resources
@@ -244,7 +238,10 @@
             self.fip['floating_ip_address'],
             port=self.NC_PORT),
             timeout=self.CHECK_TIMEOUT,
-            sleep=1)
+            sleep=1,
+            exception=RuntimeError(
+                'Failed scenario: "Create a QoS policy associated with'
+                ' the network" Actual BW is not as expected!'))
 
         # As admin user update QoS rule
         self.os_admin.network_client.update_bandwidth_limit_rule(
@@ -261,7 +258,10 @@
             port=self.NC_PORT,
             expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
             timeout=self.CHECK_TIMEOUT,
-            sleep=1)
+            sleep=1,
+            exception=RuntimeError(
+                'Failed scenario: "Update QoS policy associated with'
+                ' the network" Actual BW is not as expected!'))
 
         # Create a new QoS policy
         bw_limit_policy_id_new = self._create_qos_policy()
@@ -284,7 +284,10 @@
             self.fip['floating_ip_address'],
             port=self.NC_PORT),
             timeout=self.CHECK_TIMEOUT,
-            sleep=1)
+            sleep=1,
+            exception=RuntimeError(
+                'Failed scenario: "Create a new QoS policy associated with'
+                ' the VM port" Actual BW is not as expected!'))
 
         # As admin user update QoS rule
         self.os_admin.network_client.update_bandwidth_limit_rule(
@@ -301,7 +304,10 @@
             port=self.NC_PORT,
             expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
             timeout=self.CHECK_TIMEOUT,
-            sleep=1)
+            sleep=1,
+            exception=RuntimeError(
+                'Failed scenario: "Update QoS policy associated with'
+                ' the VM port" Actual BW is not as expected!'))
 
     @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
     def test_attach_previously_used_port_to_new_instance(self):
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
index 98fe6ae..8f260ea 100644
--- a/neutron_tempest_plugin/scenario/test_trunk.py
+++ b/neutron_tempest_plugin/scenario/test_trunk.py
@@ -15,6 +15,7 @@
 import collections
 
 from neutron_lib import constants
+from neutron_lib.utils import test
 from oslo_log import log as logging
 from tempest.common import utils as tutils
 from tempest.lib.common.utils import data_utils
@@ -246,6 +247,7 @@
             self._wait_for_trunk(vm.trunk)
             self._assert_has_ssh_connectivity(vm1.ssh_client)
 
+    @test.unstable_test("bug 1897796")
     @testtools.skipUnless(
         (CONF.neutron_plugin_options.advanced_image_ref or
          CONF.neutron_plugin_options.default_image_is_advanced),
diff --git a/neutron_tempest_plugin/scenario/test_vlan_transparency.py b/neutron_tempest_plugin/scenario/test_vlan_transparency.py
new file mode 100644
index 0000000..d9a529c
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_vlan_transparency.py
@@ -0,0 +1,186 @@
+# Copyright (c) 2020 Red Hat, Inc.
+#
+# All Rights Reserved.
+#
+#    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_log import log as logging
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.common import ip
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+MIN_VLAN_ID = 1
+MAX_VLAN_ID = 4094
+
+
+class VlanTransparencyTest(base.BaseTempestTestCase):
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    required_extensions = ['vlan-transparent', 'allowed-address-pairs']
+
+    @classmethod
+    def resource_setup(cls):
+        super(VlanTransparencyTest, cls).resource_setup()
+        # setup basic topology for servers we can log into
+        cls.rand_name = data_utils.rand_name(
+            cls.__name__.rsplit('.', 1)[-1])
+        cls.network = cls.create_network(name=cls.rand_name,
+                                         vlan_transparent=True)
+        cls.subnet = cls.create_subnet(network=cls.network,
+                                       name=cls.rand_name)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+        cls.keypair = cls.create_keypair(name=cls.rand_name)
+        cls.vm_ports = []
+        cls.security_group = cls.create_security_group(name=cls.rand_name)
+        cls.create_loginable_secgroup_rule(cls.security_group['id'])
+
+        if CONF.neutron_plugin_options.default_image_is_advanced:
+            cls.flavor_ref = CONF.compute.flavor_ref
+            cls.image_ref = CONF.compute.image_ref
+        else:
+            cls.flavor_ref = \
+                CONF.neutron_plugin_options.advanced_image_flavor_ref
+            cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
+
+    @classmethod
+    def skip_checks(cls):
+        super(VlanTransparencyTest, cls).skip_checks()
+        if not (CONF.neutron_plugin_options.advanced_image_ref or
+                CONF.neutron_plugin_options.default_image_is_advanced):
+            raise cls.skipException(
+                'Advanced image is required to run these tests.')
+
+    def _create_port_and_server(self, index,
+                                port_security=True,
+                                allowed_address_pairs=None):
+        server_name = 'server-%s-%d' % (self.rand_name, index)
+        port_name = 'port-%s-%d' % (self.rand_name, index)
+        if port_security:
+            sec_groups = [self.security_group['id']]
+        else:
+            sec_groups = None
+        self.vm_ports.append(
+            self.create_port(network=self.network, name=port_name,
+                             security_groups=sec_groups,
+                             port_security_enabled=port_security,
+                             allowed_address_pairs=allowed_address_pairs))
+        return self.create_server(flavor_ref=self.flavor_ref,
+                                  image_ref=self.image_ref,
+                                  key_name=self.keypair['name'],
+                                  networks=[{'port': self.vm_ports[-1]['id']}],
+                                  name=server_name)['server']
+
+    def _configure_vlan_transparent(self, port, ssh_client,
+                                    vlan_tag, vlan_ip):
+        ip_command = ip.IPCommand(ssh_client=ssh_client)
+        addresses = ip_command.list_addresses(port=port)
+        port_iface = ip.get_port_device_name(addresses, port)
+        subport_iface = ip_command.configure_vlan_transparent(
+            port=port, vlan_tag=vlan_tag, ip_addresses=[vlan_ip])
+
+        for address in ip_command.list_addresses(ip_addresses=vlan_ip):
+            self.assertEqual(subport_iface, address.device.name)
+            self.assertEqual(port_iface, address.device.parent)
+            break
+        else:
+            self.fail("Sub-port fixed IP not found on server.")
+
+    def _create_ssh_client(self, floating_ip):
+        if CONF.neutron_plugin_options.default_image_is_advanced:
+            username = CONF.validation.image_ssh_user
+        else:
+            username = CONF.neutron_plugin_options.advanced_image_ssh_user
+        return ssh.Client(host=floating_ip['floating_ip_address'],
+                          username=username,
+                          pkey=self.keypair['private_key'])
+
+    def _test_basic_vlan_transparency_connectivity(
+            self, port_security=True, use_allowed_address_pairs=False):
+        vlan_tag = data_utils.rand_int_id(start=MIN_VLAN_ID, end=MAX_VLAN_ID)
+        vlan_ipmask_template = '192.168.%d.{ip_last_byte}/24' % (vlan_tag %
+                                                                 256)
+        vms = []
+        vlan_ipmasks = []
+        floating_ips = []
+        ssh_clients = []
+
+        for i in range(2):
+            vlan_ipmasks.append(vlan_ipmask_template.format(
+                ip_last_byte=(i + 1) * 10))
+            if use_allowed_address_pairs:
+                allowed_address_pairs = [{'ip_address': vlan_ipmasks[i]}]
+            else:
+                allowed_address_pairs = None
+            vms.append(self._create_port_and_server(
+                index=i,
+                port_security=port_security,
+                allowed_address_pairs=allowed_address_pairs))
+            floating_ips.append(self.create_floatingip(port=self.vm_ports[-1]))
+            ssh_clients.append(
+                self._create_ssh_client(floating_ip=floating_ips[i]))
+
+            self.check_connectivity(
+                host=floating_ips[i]['floating_ip_address'],
+                ssh_client=ssh_clients[i])
+            self._configure_vlan_transparent(port=self.vm_ports[-1],
+                                             ssh_client=ssh_clients[i],
+                                             vlan_tag=vlan_tag,
+                                             vlan_ip=vlan_ipmasks[i])
+
+        if port_security:
+            # Ping from vm0 to vm1 via VLAN interface should fail because
+            # we haven't allowed ICMP
+            self.check_remote_connectivity(
+                ssh_clients[0],
+                vlan_ipmasks[1].split('/')[0],
+                servers=vms,
+                should_succeed=False)
+
+            # allow intra-security-group traffic
+            sg_rule = self.create_pingable_secgroup_rule(
+                self.security_group['id'])
+            self.addCleanup(
+                    self.os_primary.network_client.delete_security_group_rule,
+                    sg_rule['id'])
+
+        # Ping from vm0 to vm1 via VLAN interface should pass because
+        # either port security is disabled or the ICMP sec group rule has been
+        # added
+        self.check_remote_connectivity(
+            ssh_clients[0],
+            vlan_ipmasks[1].split('/')[0],
+            servers=vms)
+        # Ping from vm1 to vm0 and check untagged packets are not dropped
+        self.check_remote_connectivity(
+            ssh_clients[1],
+            self.vm_ports[-2]['fixed_ips'][0]['ip_address'],
+            servers=vms)
+
+    @decorators.idempotent_id('a2694e3a-6d4d-4a23-9fcc-c3ed3ef37b16')
+    def test_vlan_transparent_port_sec_disabled(self):
+        self._test_basic_vlan_transparency_connectivity(
+            port_security=False, use_allowed_address_pairs=False)
+
+    @decorators.idempotent_id('2dd03b4f-9c20-4cda-8c6a-40fa453ec69a')
+    def test_vlan_transparent_allowed_address_pairs(self):
+        self._test_basic_vlan_transparency_connectivity(
+            port_security=True, use_allowed_address_pairs=True)
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index e733cd0..2678d73 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -923,8 +923,6 @@
         return service_client.ResponseBody(resp, body)
 
     def list_security_groups(self, **kwargs):
-        post_body = {'security_groups': kwargs}
-        body = jsonutils.dumps(post_body)
         uri = '%s/security-groups' % self.uri_prefix
         if kwargs:
             uri += '?' + urlparse.urlencode(kwargs, doseq=1)
@@ -941,8 +939,6 @@
         return service_client.ResponseBody(resp, body)
 
     def list_ports(self, **kwargs):
-        post_body = {'ports': kwargs}
-        body = jsonutils.dumps(post_body)
         uri = '%s/ports' % self.uri_prefix
         if kwargs:
             uri += '?' + urlparse.urlencode(kwargs, doseq=1)
diff --git a/test-requirements.txt b/test-requirements.txt
index bf1c626..f5bac7c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,10 +7,6 @@
 coverage!=4.4,>=4.0 # Apache-2.0
 flake8-import-order==0.12 # LGPLv3
 python-subunit>=1.0.0 # Apache-2.0/BSD
-sphinx>=2.0.0,!=2.1.0 # BSD
 oslotest>=3.2.0 # Apache-2.0
 stestr>=1.0.0 # Apache-2.0
 testtools>=2.2.0 # MIT
-openstackdocstheme>=2.2.1 # Apache-2.0
-# releasenotes
-reno>=3.1.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index eecd16e..d3e722a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -14,7 +14,7 @@
    OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true}
    OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true}
 deps =
-  -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/test-requirements.txt
 commands = stestr run --slowest {posargs}
 
@@ -39,9 +39,12 @@
     coverage xml -o cover/coverage.xml
 
 [testenv:docs]
+deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+       -r{toxinidir}/doc/requirements.txt
 commands = sphinx-build -W -b html doc/source doc/build/html
 
 [testenv:releasenotes]
+deps = {[testenv:docs]deps}
 commands =
   sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
 
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index 28998cf..cc1479b 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -37,6 +37,12 @@
         neutron-tag-ports-during-bulk-creation: true
         br-ex-tcpdump: true
         br-int-flows: true
+        # Cinder services
+        c-api: false
+        c-bak: false
+        c-sch: false
+        c-vol: false
+        cinder: false
         # We don't need Swift to be run in the Neutron jobs
         s-account: false
         s-container: false
@@ -98,7 +104,10 @@
       # default test timeout set to 1200 seconds may be not enough if job is
       # run on slow node
       tempest_test_timeout: 2400
-      tempest_test_regex: ^neutron_tempest_plugin\.scenario
+      tempest_test_regex: "\
+          (^neutron_tempest_plugin.scenario)|\
+          (^tempest.api.compute.servers.test_attach_interfaces)|\
+          (^tempest.api.compute.servers.test_multiple_create)"
       devstack_localrc:
         PHYSICAL_NETWORK: default
         CIRROS_VERSION: 0.5.1
@@ -107,6 +116,4 @@
         ADVANCED_INSTANCE_TYPE: ds512M
         ADVANCED_INSTANCE_USER: ubuntu
         BUILD_TIMEOUT: 784
-      devstack_services:
-        cinder: true
-
+      tempest_concurrency: 3  # out of 4
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 072c8b4..ee7c6e3 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -79,9 +79,13 @@
         - uplink-status-propagation
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        - ipv6_metadata
       tempest_test_regex: ^neutron_tempest_plugin\.api
       devstack_services:
         neutron-log: true
+      devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: true
       devstack_local_conf:
         post-config:
           # NOTE(slaweq): We can get rid of this hardcoded absolute path when
@@ -99,6 +103,7 @@
     timeout: 10000
     vars:
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         Q_AGENT: openvswitch
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
@@ -118,6 +123,8 @@
               bridge_mappings: public:br-ex
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
 
@@ -127,6 +134,7 @@
     timeout: 10000
     vars:
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       # 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)"
@@ -151,6 +159,8 @@
               firewall_driver: iptables_hybrid
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
 
@@ -163,14 +173,21 @@
     pre-run: playbooks/linuxbridge-scenario-pre-run.yaml
     vars:
       network_api_extensions: *api_extensions
+      network_api_extensions_linuxbridge:
+        - vlan-transparent
+      network_available_features: *available_features
+      # TODO(eolivare): remove VLAN Transparency tests from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1907548 will be fixed
+      tempest_black_regex: "(^neutron_tempest_plugin.scenario.test_vlan_transparency.VlanTransparencyTest)"
       devstack_localrc:
         Q_AGENT: linuxbridge
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_linuxbridge) | join(',') }}"
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
             DEFAULT:
               enable_dvr: false
+              vlan_transparent: true
             AGENT:
               debug_iptables_rules: true
           # NOTE(slaweq): We can get rid of this hardcoded absolute path when
@@ -179,8 +196,11 @@
           /$NEUTRON_CORE_PLUGIN_CONF:
             ml2:
               type_drivers: flat,vlan,local,vxlan
+              mechanism_drivers: linuxbridge
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
               q_agent: linuxbridge
@@ -191,6 +211,8 @@
     timeout: 10000
     vars:
       network_api_extensions: *api_extensions
+      network_api_extensions_ovn:
+        - vlan-transparent
       # TODO(haleyb): Remove IPv6Test from blacklist when
       # https://bugs.launchpad.net/neutron/+bug/1881558 is fixed.
       # TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
@@ -204,7 +226,7 @@
           (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         Q_AGENT: ovn
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_ovn) | join(',') }}"
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: ovn,logger
         Q_ML2_PLUGIN_TYPE_DRIVERS: local,flat,vlan,geneve
         Q_ML2_TENANT_NETWORK_TYPE: geneve
@@ -215,6 +237,11 @@
         OVN_DBS_LOG_LEVEL: dbg
         ENABLE_TLS: True
         OVN_IGMP_SNOOPING_ENABLE: True
+        # TODO(eolivare): Remove OVN_BUILD_FROM_SOURCE once vlan-transparency
+        # is included in an ovn released version
+        OVN_BUILD_FROM_SOURCE: True
+        OVN_BRANCH: "v20.12.0"
+        OVS_BRANCH: "branch-2.15"
       devstack_services:
         br-ex-tcpdump: true
         br-int-flows: true
@@ -228,26 +255,34 @@
         q-l3: false
         q-meta: false
         q-metering: false
+        q-qos: true
+        tls-proxy: true
+        # Cinder services
+        c-api: false
+        c-bak: false
+        c-sch: false
+        c-vol: false
+        cinder: false
         s-account: false
         s-container-sync: false
         s-container: false
         s-object: false
         s-proxy: false
-        tls-proxy: true
-        q-qos: true
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
             DEFAULT:
               enable_dvr: false
+              vlan_transparent: true
           /$NEUTRON_CORE_PLUGIN_CONF:
             ml2:
               type_drivers: local,flat,vlan,geneve
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               available_type_drivers: local,flat,vlan,geneve
-              ipv6_metadata: False
               is_igmp_snooping_enabled: True
 
 - job:
@@ -299,7 +334,12 @@
         neutron-trunk: true
         neutron-log: true
         neutron-port-forwarding: true
-        cinder: true
+        # Cinder services
+        c-api: false
+        c-bak: false
+        c-sch: false
+        c-vol: false
+        cinder: false
         # We don't need Swift to be run in the Neutron jobs
         s-account: false
         s-container: false
@@ -350,6 +390,8 @@
               keystone: "cors request_id catch_errors osprofiler authtoken keystonecontext extensions neutronapiapp_v2_0"
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: *available_features
             neutron_plugin_options:
               provider_vlans: foo,
               agent_availability_zone: nova
@@ -367,6 +409,9 @@
           neutron-trunk: true
           neutron-log: true
           neutron-port-forwarding: true
+          # Cinder services
+          c-bak: false
+          c-vol: false
           # We don't need Swift to be run in the Neutron jobs
           s-account: false
           s-container: false
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 0355f69..973ab9f 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -164,7 +164,6 @@
     templates:
       - build-openstack-docs-pti
       - neutron-tempest-plugin-jobs
-      - neutron-tempest-plugin-jobs-stein
       - neutron-tempest-plugin-jobs-train
       - neutron-tempest-plugin-jobs-ussuri
       - neutron-tempest-plugin-jobs-victoria
@@ -177,30 +176,10 @@
         - neutron-tempest-plugin-sfc-train
         - neutron-tempest-plugin-sfc-ussuri
         - neutron-tempest-plugin-sfc-victoria
-        - neutron-tempest-plugin-bgpvpn-bagpipe:
-            # TODO(slaweq): switch it to be voting when bug
-            # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
-            # will be fixed
-            voting: false
+        - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-bgpvpn-bagpipe-train
-        - neutron-tempest-plugin-bgpvpn-bagpipe-ussuri:
-            # TODO(slaweq): switch it to be voting when bug
-            # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
-            # will be fixed
-            voting: false
-        - neutron-tempest-plugin-bgpvpn-bagpipe-victoria:
-            # TODO(slaweq): switch it to be voting when bug
-            # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
-            # will be fixed
-            voting: false
-        - neutron-tempest-plugin-fwaas-train:
-            # TODO(slaweq): switch it to be voting when bug
-            # https://bugs.launchpad.net/neutron/+bug/1858645 will be fixed
-            voting: false
-        - neutron-tempest-plugin-fwaas-ussuri:
-            # TODO(slaweq): switch it to be voting when bug
-            # https://bugs.launchpad.net/neutron/+bug/1858645 will be fixed
-            voting: false
+        - neutron-tempest-plugin-bgpvpn-bagpipe-ussuri
+        - neutron-tempest-plugin-bgpvpn-bagpipe-victoria
         - neutron-tempest-plugin-dynamic-routing
         - neutron-tempest-plugin-dynamic-routing-ussuri
         - neutron-tempest-plugin-dynamic-routing-victoria
@@ -211,8 +190,16 @@
     gate:
       jobs:
         - neutron-tempest-plugin-sfc
-        # TODO(slaweq): make bgpvpn-bagpipe job gating again when
-        # https://bugs.launchpad.net/networking-bagpipe/+bug/1897408
-        # will be fixed
-        #- neutron-tempest-plugin-bgpvpn-bagpipe
+        - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-dynamic-routing
+
+    experimental:
+      jobs:
+        - neutron-tempest-plugin-fwaas-train:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1858645 will be fixed
+            voting: false
+        - neutron-tempest-plugin-fwaas-ussuri:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1858645 will be fixed
+            voting: false
diff --git a/zuul.d/queens_jobs.yaml b/zuul.d/queens_jobs.yaml
index b0ee336..e1ecc00 100644
--- a/zuul.d/queens_jobs.yaml
+++ b/zuul.d/queens_jobs.yaml
@@ -65,7 +65,10 @@
         - trunk-details
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        -
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         USE_PYTHON3: false
         CIRROS_VERSION: 0.3.5
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
@@ -87,6 +90,7 @@
     vars:
       branch_override: stable/queens
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       # TODO(slaweq): remove trunks subport_connectivity test from blacklist
       # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
       # NOTE(bcafarel): remove DNS test as queens pinned version does not have
@@ -117,6 +121,7 @@
     vars:
       branch_override: stable/queens
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       # NOTE(bcafarel): remove DNS test as queens pinned version does not have
       # fix for https://bugs.launchpad.net/neutron/+bug/1826419
       tempest_black_regex: "\
diff --git a/zuul.d/rocky_jobs.yaml b/zuul.d/rocky_jobs.yaml
index 6eb8c15..4b6145b 100644
--- a/zuul.d/rocky_jobs.yaml
+++ b/zuul.d/rocky_jobs.yaml
@@ -75,6 +75,7 @@
       network_api_extensions_tempest:
         - dvr
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
@@ -108,6 +109,8 @@
     vars: &scenario_vars_rocky
       branch_override: stable/rocky
       network_api_extensions: *api_extensions
+      network_available_features: &available_features
+        -
       devstack_localrc:
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
diff --git a/zuul.d/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index ff6ed38..29dfa8a 100644
--- a/zuul.d/stein_jobs.yaml
+++ b/zuul.d/stein_jobs.yaml
@@ -3,6 +3,12 @@
     parent: neutron-tempest-plugin-api
     nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
+    required-projects: &required-projects-stein
+      - openstack/devstack-gate
+      - openstack/neutron
+      - name: openstack/neutron-tempest-plugin
+        override-checkout: 1.3.0
+      - openstack/tempest
     vars:
       branch_override: stable/stein
       # TODO(slaweq): find a way to put this list of extensions in
@@ -73,7 +79,10 @@
         - uplink-status-propagation
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        -
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
 
 - job:
@@ -81,14 +90,18 @@
     parent: neutron-tempest-plugin-scenario-openvswitch
     nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
+    required-projects: *required-projects-stein
     vars:
       branch_override: stable/stein
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -97,14 +110,18 @@
     parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
     nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
+    required-projects: *required-projects-stein
     vars:
       branch_override: stable/stein
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -113,14 +130,18 @@
     parent: neutron-tempest-plugin-scenario-linuxbridge
     nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
+    required-projects: *required-projects-stein
     vars:
       branch_override: stable/stein
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -129,6 +150,7 @@
     parent: neutron-tempest-plugin-dvr-multinode-scenario
     nodeset: openstack-two-node-bionic
     override-checkout: stable/stein
+    required-projects: *required-projects-stein
     vars:
       network_api_extensions_common: *api_extensions
       branch_override: stable/stein
@@ -141,7 +163,8 @@
     required-projects:
       - openstack/devstack-gate
       - openstack/neutron
-      - openstack/neutron-tempest-plugin
+      - name: openstack/neutron-tempest-plugin
+        override-checkout: 1.3.0
       - name: openstack/designate-tempest-plugin
         override-checkout: 0.7.0
       - openstack/tempest
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
index a9cc5be..0785924 100644
--- a/zuul.d/train_jobs.yaml
+++ b/zuul.d/train_jobs.yaml
@@ -78,7 +78,10 @@
         - uplink-status-propagation
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        -
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
 
 - job:
@@ -89,11 +92,14 @@
     vars:
       branch_override: stable/train
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -105,11 +111,14 @@
     vars:
       branch_override: stable/train
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -121,11 +130,14 @@
     vars:
       branch_override: stable/train
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
diff --git a/zuul.d/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
index 135d9f5..9cc0621 100644
--- a/zuul.d/ussuri_jobs.yaml
+++ b/zuul.d/ussuri_jobs.yaml
@@ -82,9 +82,13 @@
         - uplink-status-propagation
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        -
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
 
+
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-ussuri
     parent: neutron-tempest-plugin-scenario-openvswitch
@@ -93,14 +97,18 @@
     vars:
       branch_override: stable/ussuri
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
+
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-ussuri
     parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
@@ -109,11 +117,14 @@
     vars:
       branch_override: stable/ussuri
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -125,11 +136,14 @@
     vars:
       branch_override: stable/ussuri
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -149,6 +163,7 @@
         OVN_BUILD_MODULES: True
         # TODO(skaplons): v2.13.1 is incompatible with kernel 4.15.0-118, sticking to commit hash until new v2.13 tag is created
         OVS_BRANCH: 0047ca3a0290f1ef954f2c76b31477cf4b9755f5
+        OVN_BRANCH: "v20.03.0"
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-ussuri
diff --git a/zuul.d/victoria_jobs.yaml b/zuul.d/victoria_jobs.yaml
index 0bc1e13..5543ea7 100644
--- a/zuul.d/victoria_jobs.yaml
+++ b/zuul.d/victoria_jobs.yaml
@@ -81,7 +81,10 @@
         - uplink-status-propagation
       network_api_extensions_tempest:
         - dvr
+      network_available_features: &available_features
+        - ipv6_metadata
       devstack_localrc:
+        NEUTRON_DEPLOY_MOD_WSGI: false
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
 
 - job:
@@ -91,11 +94,14 @@
     vars:
       branch_override: stable/victoria
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -106,11 +112,14 @@
     vars:
       branch_override: stable-victoria
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -121,11 +130,14 @@
     vars:
       branch_override: stable/victoria
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               ipv6_metadata: False
 
@@ -138,6 +150,11 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-victoria