Merge "Use tempest's ServiceClients rather than Manager"
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/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 ca3368c..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
 
@@ -151,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_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_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index afce414..94b6dbf 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -127,6 +127,21 @@
             self.assertIn(secgrp['name'], sec_nm)
             self.assertIsNotNone(secgrp['id'])
 
+    @decorators.idempotent_id('e93f33d8-57ea-11eb-b69b-74e5f9e2a801')
+    def test_create_sec_groups_with_the_same_name(self):
+        same_name_sg_number = 5
+        sg_name = 'sg_zahlabut'
+        sg_names = [sg_name] * same_name_sg_number
+        for name in sg_names:
+            self.create_security_group(name=name)
+        sec_groups = [item['id'] for item in
+                      self.client.list_security_groups(
+                          name=sg_name)['security_groups']]
+        self.assertEqual(
+            same_name_sg_number, len(set(sec_groups)),
+            'Failed - expected number of groups with the same name'
+            ' is: {}'.format(same_name_sg_number))
+
 
 class BaseSecGroupQuota(base.BaseAdminNetworkTest):
 
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/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 752e536..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',
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_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/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 3c18fbf..d3e722a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 5325f67..ee7c6e3 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -173,15 +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
@@ -190,6 +196,7 @@
           /$NEUTRON_CORE_PLUGIN_CONF:
             ml2:
               type_drivers: flat,vlan,local,vxlan
+              mechanism_drivers: linuxbridge
         test-config:
           $TEMPEST_CONFIG:
             network-feature-enabled:
@@ -204,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
@@ -217,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
@@ -228,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
@@ -259,6 +273,7 @@
           $NEUTRON_CONF:
             DEFAULT:
               enable_dvr: false
+              vlan_transparent: true
           /$NEUTRON_CORE_PLUGIN_CONF:
             ml2:
               type_drivers: local,flat,vlan,geneve
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index dc6e56c..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
@@ -181,14 +180,6 @@
         - neutron-tempest-plugin-bgpvpn-bagpipe-train
         - neutron-tempest-plugin-bgpvpn-bagpipe-ussuri
         - neutron-tempest-plugin-bgpvpn-bagpipe-victoria
-        - 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-dynamic-routing
         - neutron-tempest-plugin-dynamic-routing-ussuri
         - neutron-tempest-plugin-dynamic-routing-victoria
@@ -201,3 +192,14 @@
         - neutron-tempest-plugin-sfc
         - 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/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index 9de8384..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
@@ -84,6 +90,7 @@
     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
@@ -103,6 +110,7 @@
     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
@@ -122,6 +130,7 @@
     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
@@ -141,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
@@ -153,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/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
index b71a460..9cc0621 100644
--- a/zuul.d/ussuri_jobs.yaml
+++ b/zuul.d/ussuri_jobs.yaml
@@ -163,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