Merge "cases associated with the port"
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/admin/test_ports.py b/neutron_tempest_plugin/api/admin/test_ports.py
index 9c94322..6f51b1c 100644
--- a/neutron_tempest_plugin/api/admin/test_ports.py
+++ b/neutron_tempest_plugin/api/admin/test_ports.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 import netaddr
-import six
 
 from neutron_lib import constants as const
 from tempest.common import utils
@@ -123,8 +122,8 @@
         self.assertIn('resource_request', port)
         vnic_trait = 'CUSTOM_VNIC_TYPE_%s' % vnic_type.upper()
         physnet_trait = 'CUSTOM_PHYSNET_%s' % self.physnet_name.upper()
-        six.assertCountEqual(self, [physnet_trait, vnic_trait],
-                             port['resource_request']['required'])
+        self.assertCountEqual([physnet_trait, vnic_trait],
+                              port['resource_request']['required'])
 
         self.assertEqual(
             {'NET_BW_EGR_KILOBIT_PER_SEC': self.EGRESS_KBPS,
diff --git a/neutron_tempest_plugin/api/admin/test_quotas.py b/neutron_tempest_plugin/api/admin/test_quotas.py
index ae773c8..0cf474e 100644
--- a/neutron_tempest_plugin/api/admin/test_quotas.py
+++ b/neutron_tempest_plugin/api/admin/test_quotas.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 from tempest.common import utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
@@ -145,7 +144,7 @@
         # as requested for tenant
         quota_set = self.admin_client.show_details_quota(tenant_id)
         quota_set = quota_set['quota']
-        for key, value in six.iteritems(new_quotas):
+        for key, value in new_quotas.items():
             self.assertEqual(new_quotas[key]['limit'],
                              quota_set[key]['limit'])
             self.assertEqual(new_quotas[key]['reserved'],
@@ -155,5 +154,5 @@
 
         # validate 'default' action for old extension
         quota_limit = self.admin_client.show_quotas(tenant_id)['quota']
-        for key, value in six.iteritems(new_quotas):
+        for key, value in new_quotas.items():
             self.assertEqual(new_quotas[key]['limit'], quota_limit[key])
diff --git a/neutron_tempest_plugin/api/admin/test_tag.py b/neutron_tempest_plugin/api/admin/test_tag.py
index 7879b4c..eae7977 100644
--- a/neutron_tempest_plugin/api/admin/test_tag.py
+++ b/neutron_tempest_plugin/api/admin/test_tag.py
@@ -28,13 +28,13 @@
 
     def _get_and_compare_tags(self, tags):
         res_body = self.client.get_tags(self.resource, self.res_id)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
 
     def _test_tag_operations(self):
         # create and get tags
         tags = ['red', 'blue']
         res_body = self.client.update_tags(self.resource, self.res_id, tags)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
         self._get_and_compare_tags(tags)
 
         # add a tag
@@ -52,7 +52,7 @@
         # replace tags
         tags = ['red', 'yellow', 'purple']
         res_body = self.client.update_tags(self.resource, self.res_id, tags)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
         self._get_and_compare_tags(tags)
 
         # get tag
@@ -477,7 +477,7 @@
         # nothing specific about networks here, just a resource that is
         # available in all setups
         res_body = self.client.get_tags('networks', res_id)
-        self.assertItemsEqual(tags, res_body['tags'])
+        self.assertCountEqual(tags, res_body['tags'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('74c56fb1-a3b1-4a62-a8d2-d04dca6bd4cd')
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 1b02211..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
@@ -478,7 +489,7 @@
         """
 
         if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
-            raise ValueError('Subnet CIDR already reserved: %r'.format(
+            raise ValueError('Subnet CIDR already reserved: {0!r}'.format(
                 addr))
 
     @classmethod
@@ -558,6 +569,8 @@
         """Wrapper utility that returns a test port."""
         if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
             kwargs['binding:vnic_type'] = CONF.network.port_vnic_type
+        if CONF.network.port_profile and 'binding:profile' not in kwargs:
+            kwargs['binding:profile'] = CONF.network.port_profile
         body = cls.client.create_port(network_id=network['id'],
                                       **kwargs)
         port = body['port']
@@ -700,6 +713,12 @@
         return pf
 
     @classmethod
+    def update_port_forwarding(cls, fip_id, pf_id, client=None, **kwargs):
+        """Wrapper utility for update_port_forwarding."""
+        client = client or cls.client
+        return client.update_port_forwarding(fip_id, pf_id, **kwargs)
+
+    @classmethod
     def delete_port_forwarding(cls, pf, client=None):
         """Delete port forwarding
 
diff --git a/neutron_tempest_plugin/api/test_allowed_address_pair.py b/neutron_tempest_plugin/api/test_allowed_address_pair.py
index dd48382..7b11638 100644
--- a/neutron_tempest_plugin/api/test_allowed_address_pair.py
+++ b/neutron_tempest_plugin/api/test_allowed_address_pair.py
@@ -81,7 +81,7 @@
         body = self.client.update_port(
             port_id, allowed_address_pairs=allowed_address_pairs)
         allowed_address_pair = body['port']['allowed_address_pairs']
-        self.assertItemsEqual(allowed_address_pair, allowed_address_pairs)
+        self.assertCountEqual(allowed_address_pair, allowed_address_pairs)
 
     @decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
     def test_update_port_with_address_pair(self):
diff --git a/neutron_tempest_plugin/api/test_port_forwarding_negative.py b/neutron_tempest_plugin/api/test_port_forwarding_negative.py
index 76dd6ee..386cbed 100644
--- a/neutron_tempest_plugin/api/test_port_forwarding_negative.py
+++ b/neutron_tempest_plugin/api/test_port_forwarding_negative.py
@@ -12,6 +12,7 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
@@ -81,3 +82,43 @@
             internal_ip_address=port['fixed_ips'][0]['ip_address'],
             internal_port=1111, external_port=5555,
             protocol="tcp")
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('e9d3ffb6-e5bf-421d-acaa-ee6010dfbf14')
+    def test_out_of_range_ports(self):
+        port = self.create_port(self.network)
+        fip_for_pf = self.create_floatingip()
+
+        pf_params = {
+            'internal_port_id': port['id'],
+            'internal_ip_address': port['fixed_ips'][0]['ip_address'],
+            'internal_port': 1111,
+            'external_port': 3333,
+            'protocol': "tcp"}
+        pf = self.create_port_forwarding(fip_for_pf['id'], **pf_params)
+
+        # Check: Invalid input for external_port update
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.update_port_forwarding,
+            fip_for_pf['id'], pf['id'], external_port=108343)
+
+        # Check: Invalid input for internal_port update
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.update_port_forwarding,
+            fip_for_pf['id'], pf['id'], internal_port=108343)
+
+        # Check: Invalid input for external_port create
+        pf_params['internal_port'] = 4444
+        pf_params['external_port'] = 333333
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.create_port_forwarding, fip_for_pf['id'], **pf_params)
+
+        # Check: Invalid input for internal_port create
+        pf_params['internal_port'] = 444444
+        pf_params['external_port'] = 3333
+        self.assertRaises(
+            exceptions.BadRequest,
+            self.create_port_forwarding, fip_for_pf['id'], **pf_params)
diff --git a/neutron_tempest_plugin/api/test_port_forwardings.py b/neutron_tempest_plugin/api/test_port_forwardings.py
index 82c3a34..79cce39 100644
--- a/neutron_tempest_plugin/api/test_port_forwardings.py
+++ b/neutron_tempest_plugin/api/test_port_forwardings.py
@@ -124,31 +124,10 @@
         fip = self.client.show_floatingip(fip['id'])['floatingip']
         self.assertEqual(0, len(fip['port_forwardings']))
 
-    @decorators.idempotent_id('8202cded-7e82-4420-9585-c091105404f6')
-    def test_associate_2_port_forwardings_to_floating_ip(self):
-        fip = self.create_floatingip()
-        forwardings_data = [(1111, 2222), (3333, 4444)]
-        created_pfs = []
-        for data in forwardings_data:
-            internal_port = data[0]
-            external_port = data[1]
-            port = self.create_port(self.network)
-            created_pf = self.create_port_forwarding(
-                fip['id'],
-                internal_port_id=port['id'],
-                internal_ip_address=port['fixed_ips'][0]['ip_address'],
-                internal_port=internal_port, external_port=external_port,
-                protocol="tcp")
-            self.assertEqual(internal_port, created_pf['internal_port'])
-            self.assertEqual(external_port, created_pf['external_port'])
-            self.assertEqual('tcp', created_pf['protocol'])
-            self.assertEqual(port['fixed_ips'][0]['ip_address'],
-                             created_pf['internal_ip_address'])
-            created_pfs.append(created_pf)
-
+    def _verify_created_pfs(self, fip_id, created_pfs):
         # Check that all PFs are visible in Floating IP details
-        fip = self.client.show_floatingip(fip['id'])['floatingip']
-        self.assertEqual(len(forwardings_data), len(fip['port_forwardings']))
+        fip = self.client.show_floatingip(fip_id)['floatingip']
+        self.assertEqual(len(created_pfs), len(fip['port_forwardings']))
         for pf in created_pfs:
             expected_pf = {
                 'external_port': pf['external_port'],
@@ -160,13 +139,73 @@
         # Test list of port forwardings
         port_forwardings = self.client.list_port_forwardings(
             fip['id'])['port_forwardings']
-        self.assertEqual(len(forwardings_data), len(port_forwardings))
+        self.assertEqual(len(created_pfs), len(port_forwardings))
         for pf in created_pfs:
             expected_pf = pf.copy()
             expected_pf.pop('client')
             expected_pf.pop('floatingip_id')
             self.assertIn(expected_pf, port_forwardings)
 
+    def _create_and_validate_pf(self, fip_id, internal_port_id,
+                                internal_ip_address, internal_port,
+                                external_port, protocol):
+        created_pf = self.create_port_forwarding(
+            fip_id,
+            internal_port_id=internal_port_id,
+            internal_ip_address=internal_ip_address,
+            internal_port=internal_port,
+            external_port=external_port,
+            protocol=protocol)
+        self.assertEqual(internal_port, created_pf['internal_port'])
+        self.assertEqual(external_port, created_pf['external_port'])
+        self.assertEqual(protocol, created_pf['protocol'])
+        self.assertEqual(internal_ip_address,
+                         created_pf['internal_ip_address'])
+        return created_pf
+
+    @decorators.idempotent_id('8202cded-7e82-4420-9585-c091105404f6')
+    def test_associate_2_port_forwardings_to_floating_ip(self):
+        fip = self.create_floatingip()
+        forwardings_data = [(1111, 2222), (3333, 4444)]
+        created_pfs = []
+        for data in forwardings_data:
+            internal_port = data[0]
+            external_port = data[1]
+            port = self.create_port(self.network)
+            created_pf = self._create_and_validate_pf(
+                fip_id=fip['id'],
+                internal_port_id=port['id'],
+                internal_ip_address=port['fixed_ips'][0]['ip_address'],
+                internal_port=internal_port, external_port=external_port,
+                protocol="tcp")
+            created_pfs.append(created_pf)
+        self._verify_created_pfs(fip['id'], created_pfs)
+
+    @decorators.idempotent_id('a7e6cc48-8a9b-49be-82fb-cef6f5c29381')
+    def test_associate_port_forwarding_to_2_fixed_ips(self):
+        fip = self.create_floatingip()
+        port = self.create_port(self.network)
+        internal_subnet_id = port['fixed_ips'][0]['subnet_id']
+        # Add a second fixed_ip address to port (same subnet)
+        port['fixed_ips'].append({'subnet_id': internal_subnet_id})
+        port = self.update_port(port, fixed_ips=port['fixed_ips'])
+        internal_ip_address1 = port['fixed_ips'][0]['ip_address']
+        internal_ip_address2 = port['fixed_ips'][1]['ip_address']
+        forwardings_data = [(4001, internal_ip_address1),
+                            (4002, internal_ip_address2)]
+        created_pfs = []
+        for data in forwardings_data:
+            external_port = data[0]
+            internal_ip_address = data[1]
+            created_pf = self._create_and_validate_pf(
+                fip_id=fip['id'],
+                internal_port_id=port['id'],
+                internal_ip_address=internal_ip_address,
+                internal_port=123, external_port=external_port,
+                protocol="tcp")
+            created_pfs.append(created_pf)
+        self._verify_created_pfs(fip['id'], created_pfs)
+
     @decorators.idempotent_id('6a34e811-66d1-4f63-aa4d-9013f15deb62')
     def test_associate_port_forwarding_to_used_floating_ip(self):
         port_for_fip = self.create_port(self.network)
diff --git a/neutron_tempest_plugin/api/test_ports.py b/neutron_tempest_plugin/api/test_ports.py
index 8b0cd0a..ca3368c 100644
--- a/neutron_tempest_plugin/api/test_ports.py
+++ b/neutron_tempest_plugin/api/test_ports.py
@@ -150,28 +150,6 @@
         expected = [s['id'], s['id']]
         self.assertEqual(expected, subnets)
 
-    @decorators.idempotent_id('9700828d-86eb-4f21-9fa3-da487a2d77f2')
-    @utils.requires_ext(extension="uplink-status-propagation",
-                        service="network")
-    def test_create_port_with_propagate_uplink_status(self):
-        body = self.create_port(self.network, propagate_uplink_status=True)
-        self.assertTrue(body['propagate_uplink_status'])
-        body = self.client.list_ports(id=body['id'])['ports'][0]
-        self.assertTrue(body['propagate_uplink_status'])
-        body = self.client.show_port(body['id'])['port']
-        self.assertTrue(body['propagate_uplink_status'])
-
-    @decorators.idempotent_id('c396a880-0c7b-409d-a80b-800a3d09bdc4')
-    @utils.requires_ext(extension="uplink-status-propagation",
-                        service="network")
-    def test_create_port_without_propagate_uplink_status(self):
-        body = self.create_port(self.network)
-        self.assertFalse(body['propagate_uplink_status'])
-        body = self.client.list_ports(id=body['id'])['ports'][0]
-        self.assertFalse(body['propagate_uplink_status'])
-        body = self.client.show_port(body['id'])['port']
-        self.assertFalse(body['propagate_uplink_status'])
-
 
 class PortsSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
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_routers_negative.py b/neutron_tempest_plugin/api/test_routers_negative.py
index 5f24732..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):
@@ -67,6 +71,44 @@
             client2.add_router_interface_with_subnet_id(
                 self.router['id'], subnet['id'])
 
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('8010d27e-4ab7-4ea3-98b1-3995b7910efd')
+    def test_add_interface_in_use(self):
+        port = self.create_port(self.network)
+        self.client.add_router_interface_with_port_id(
+            self.router['id'], port['id'])
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.client.add_router_interface_with_port_id,
+            self.router['id'], port['id'])
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('ed84c800-ee29-4b76-9419-d6d7b143fc47')
+    def test_add_interface_port_nonexist(self):
+        # port id is not a valid UUID
+        invalid_id = data_utils.rand_name('port')
+        self.assertRaises(
+            lib_exc.BadRequest,
+            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/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index dd1597c..afce414 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -538,3 +538,32 @@
         # make sure the rbac-policy is invisible to the tenant for which it's
         # being shared
         self.assertFalse(self.client.list_rbac_policies()['rbac_policies'])
+
+
+class SecGroupSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+    required_extensions = ['security-group']
+    resource = 'security-group'
+
+    @classmethod
+    def resource_setup(cls):
+        super(SecGroupSearchCriteriaTest, cls).resource_setup()
+        cls.security_group = cls.create_security_group()
+        for name in cls.resource_names:
+            cls.create_security_group(name=name)
+
+    @decorators.idempotent_id('0064aa80-8a29-442d-a8de-9101af8210fa')
+    def test_list_sorts_by_name_asc(self):
+        self._test_list_sorts_asc()
+
+    @decorators.idempotent_id('35e86832-53cd-4e63-97ec-31a2413da591')
+    def test_list_sorts_by_name_desc(self):
+        self._test_list_sorts_desc()
+
+    @decorators.idempotent_id('b9654cdc-80bc-43f8-844e-dfe88fd2f125')
+    def test_list_pagination(self):
+        self._test_list_pagination()
+
+    @decorators.idempotent_id('5c78bd57-e6e9-4e71-a05c-9c4082a3f139')
+    def test_list_no_pagination_limit_0(self):
+        self._test_list_no_pagination_limit_0()
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/manager.py b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
index 8a5f9f2..4ff1c0d 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/manager.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
@@ -462,8 +462,8 @@
         port_map = [(p["id"], fxip["ip_address"])
                     for p in ports
                     for fxip in p["fixed_ips"]
-                    if netutils.is_valid_ipv4(fxip["ip_address"])
-                    and p['status'] in p_status]
+                    if netutils.is_valid_ipv4(fxip["ip_address"]) and
+                    p['status'] in p_status]
         inactive = [p for p in ports if p['status'] != 'ACTIVE']
         if inactive:
             LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
index 937b0dc..14b0572 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
@@ -353,6 +353,7 @@
         self._update_l3_bgpvpn(rts=[self.RT1], import_rts=[], export_rts=[])
         self._check_l3_bgpvpn()
 
+    @test.unstable_test("bug 1897408")
     @decorators.idempotent_id('c8bfd695-f731-47a6-86e3-3dfa492e08e0')
     @utils.services('compute', 'network')
     def test_bgpvpn_update_rt_and_keep_local_connectivity_variant1(self):
@@ -404,6 +405,7 @@
         self._check_l3_bgpvpn(self.servers[0], self.servers[2])
         self._check_l3_bgpvpn(self.servers[1], self.servers[3])
 
+    @test.unstable_test("bug 1897408")
     @decorators.idempotent_id('758a8731-5070-4b1e-9a66-d6ff05bb5be1')
     @utils.services('compute', 'network')
     def test_bgpvpn_update_rt_and_keep_local_connectivity_variant2(self):
@@ -544,6 +546,7 @@
             to_server_ip=IP_C_S1_1,
             validate_server=destination_srv_2)
 
+    @test.unstable_test("bug 1897408")
     @decorators.idempotent_id('f762e6ac-920e-4d0f-aa67-02bdd4ab8433')
     @utils.services('compute', 'network')
     def test_bgpvpn_tenant_separation_and_local_connectivity(self):
diff --git a/neutron_tempest_plugin/common/ip.py b/neutron_tempest_plugin/common/ip.py
index a286d6b..d981770 100644
--- a/neutron_tempest_plugin/common/ip.py
+++ b/neutron_tempest_plugin/common/ip.py
@@ -36,13 +36,19 @@
     sudo = 'sudo'
     ip_path = '/sbin/ip'
 
-    def __init__(self, ssh_client=None, timeout=None):
+    def __init__(self, ssh_client=None, timeout=None, namespace=None):
         self.ssh_client = ssh_client
         self.timeout = timeout
+        self.namespace = namespace
 
     def get_command(self, obj, *command):
-        command_line = '{sudo!s} {ip_path!r} {object!s} {command!s}'.format(
-            sudo=self.sudo, ip_path=self.ip_path, object=obj,
+        command_line = '{sudo!s} {ip_path!r} '.format(sudo=self.sudo,
+                                                     ip_path=self.ip_path)
+        if self.namespace:
+            command_line += 'netns exec {ns_name!s} {ip_path!r} '.format(
+                ns_name=self.namespace, ip_path=self.ip_path)
+        command_line += '{object!s} {command!s}'.format(
+            object=obj,
             command=subprocess.list2cmdline([str(c) for c in command]))
         return command_line
 
@@ -84,6 +90,13 @@
             self.add_address(address=subport_ip, device=subport_device)
         return subport_device
 
+    def list_namespaces(self):
+        namespaces_output = self.execute("netns")
+        ns_list = []
+        for ns_line in namespaces_output.split("\n"):
+            ns_list.append(ns_line.split(" ", 1)[0])
+        return ns_list
+
     def list_addresses(self, device=None, ip_addresses=None, port=None,
                        subnets=None):
         command = ['list']
@@ -295,7 +308,7 @@
     for address in list_ip_addresses(addresses=addresses, port=port):
         return address.device.name
 
-    msg = "Port %r fixed IPs not found on server.".format(port['id'])
+    msg = "Port {0!r} fixed IPs not found on server.".format(port['id'])
     raise ValueError(msg)
 
 
@@ -308,20 +321,24 @@
                    netaddr.IPNetwork(subnet['cidr']).prefixlen)
 
 
-def arp_table():
+def arp_table(namespace=None):
     # 192.168.0.16  0x1  0x2  dc:a6:32:06:56:51  *  enp0s31f6
     regex_str = (r"([^ ]+)\s+(0x\d+)\s+(0x\d+)\s+(\w{2}\:\w{2}\:\w{2}\:\w{2}\:"
                  r"\w{2}\:\w{2})\s+([\w+\*]+)\s+([\-\w]+)")
     regex = re.compile(regex_str)
     arp_table = []
-    with open('/proc/net/arp', 'r') as proc_file:
-        for line in proc_file.readlines():
-            m = regex.match(line)
-            if m:
-                arp_table.append(ARPregister(
-                    ip_address=m.group(1), hw_type=m.group(2),
-                    flags=m.group(3), mac_address=m.group(4),
-                    mask=m.group(5), device=m.group(6)))
+    cmd = ""
+    if namespace:
+        cmd = "sudo ip netns exec %s " % namespace
+    cmd += "cat /proc/net/arp"
+    arp_entries = shell.execute(cmd).stdout.split("\n")
+    for line in arp_entries:
+        m = regex.match(line)
+        if m:
+            arp_table.append(ARPregister(
+                ip_address=m.group(1), hw_type=m.group(2),
+                flags=m.group(3), mac_address=m.group(4),
+                mask=m.group(5), device=m.group(6)))
     return arp_table
 
 
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
index fa731d8..8334521 100644
--- a/neutron_tempest_plugin/common/ssh.py
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -19,7 +19,6 @@
 
 from oslo_log import log
 import paramiko
-import six
 from tempest.lib.common import ssh
 from tempest.lib import exceptions
 import tenacity
@@ -33,13 +32,7 @@
 
 
 RETRY_EXCEPTIONS = (exceptions.TimeoutException, paramiko.SSHException,
-                    socket.error)
-if six.PY2:
-    # NOTE(ralonsoh): TimeoutError was added in 3.3 and corresponds to
-    # OSError(errno.ETIMEDOUT)
-    RETRY_EXCEPTIONS += (OSError, )
-else:
-    RETRY_EXCEPTIONS += (TimeoutError, )
+                    socket.error, TimeoutError)
 
 
 class Client(ssh.Client):
@@ -293,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/fwaas/api/test_fwaasv2_extensions.py b/neutron_tempest_plugin/fwaas/api/test_fwaasv2_extensions.py
index f085e6d..0dd18f1 100644
--- a/neutron_tempest_plugin/fwaas/api/test_fwaasv2_extensions.py
+++ b/neutron_tempest_plugin/fwaas/api/test_fwaasv2_extensions.py
@@ -13,7 +13,6 @@
 #    under the License.
 
 import netaddr
-import six
 
 from tempest.common import utils
 from tempest import config
@@ -212,7 +211,7 @@
         # show a created firewall rule
         fw_rule = self.firewall_rules_client.show_firewall_rule(
             self.fw_rule_1['id'])
-        for key, value in six.iteritems(fw_rule['firewall_rule']):
+        for key, value in fw_rule['firewall_rule'].items():
             if key != 'firewall_policy_id':
                 self.assertEqual(self.fw_rule_1[key], value)
             # This check is placed because we cannot modify policy during
@@ -258,7 +257,7 @@
         fw_policy = self.firewall_policies_client.show_firewall_policy(
             self.fw_policy_1['id'])
         fw_policy = fw_policy['firewall_policy']
-        for key, value in six.iteritems(fw_policy):
+        for key, value in fw_policy.items():
             self.assertEqual(self.fw_policy_1[key], value)
 
     @decorators.idempotent_id('48dfcd75-3924-479d-bb65-b3ed33397663')
@@ -283,7 +282,7 @@
             fwg_id)
         fwg = firewall_group['firewall_group']
 
-        for key, value in six.iteritems(fwg):
+        for key, value in fwg.items():
             if key == 'status':
                 continue
             self.assertEqual(created_firewall_group[key], value)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py
index 6720c22..5cdd524 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py
@@ -26,29 +26,8 @@
 CONF = config.CONF
 
 
-def _setup_client_args(auth_provider):
-    """Set up ServiceClient arguments using config settings. """
-    service = CONF.network.catalog_type or 'network'
-    region = CONF.network.region or 'regionOne'
-    endpoint_type = CONF.network.endpoint_type
-    build_interval = CONF.network.build_interval
-    build_timeout = CONF.network.build_timeout
-
-    # The disable_ssl appears in identity
-    disable_ssl_certificate_validation = (
-        CONF.identity.disable_ssl_certificate_validation)
-    ca_certs = CONF.identity.ca_certificates_file
-
-    # Trace in debug section
-    trace_requests = CONF.debug.trace_requests
-
-    return [auth_provider, service, region, endpoint_type,
-            build_interval, build_timeout,
-            disable_ssl_certificate_validation, ca_certs,
-            trace_requests]
-
-
 class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest):
+    credentials = ['primary', 'admin']
 
     default_bgp_speaker_args = {'local_as': '1234',
                                 'ip_version': 4,
@@ -65,18 +44,20 @@
         super(BgpSpeakerTestJSONBase, self).setUp()
 
     @classmethod
-    def _setup_bgp_admin_client(cls):
-        mgr = cls.get_client_manager(credential_type='admin')
-        auth_provider = mgr.auth_provider
-        client_args = _setup_client_args(auth_provider)
-        cls.bgp_adm_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+    def setup_clients(cls):
+        super(BgpSpeakerTestJSONBase, cls).setup_clients()
+        cls.bgp_client = cls.os_primary.bgp_client
+        cls.bgp_adm_client = cls.os_admin.bgp_client
 
     @classmethod
-    def _setup_bgp_non_admin_client(cls):
-        mgr = cls.get_client_manager()
-        auth_provider = mgr.auth_provider
-        client_args = _setup_client_args(auth_provider)
-        cls.bgp_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+    def get_client_manager(cls, credential_type=None, roles=None,
+                           force_new=None):
+        manager = super(BgpSpeakerTestJSONBase, cls).get_client_manager(
+            credential_type=credential_type,
+            roles=roles,
+            force_new=force_new
+        )
+        return bgp_client.Manager(manager.credentials)
 
     @classmethod
     def resource_setup(cls):
@@ -89,8 +70,6 @@
         cls.admin_floatingips = []
         cls.admin_routers = []
         cls.ext_net_id = CONF.network.public_network_id
-        cls._setup_bgp_admin_client()
-        cls._setup_bgp_non_admin_client()
 
     @classmethod
     def resource_cleanup(cls):
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
index f752436..b4be57b 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
@@ -19,7 +19,6 @@
 import time
 
 import netaddr
-import six
 
 from os_ken.tests.integrated.common import docker_base as ctn_base
 from tempest.common import utils
@@ -125,10 +124,10 @@
         while True:
             with self.lock:
                 try:
-                    yield (i, str(six.next(subnet_gen)))
+                    yield (i, str(next(subnet_gen)))
                 except StopIteration:
                     subnet_gen = netaddr.iter_iprange(start, end, step=step)
-                    yield (i, str(six.next(subnet_gen)))
+                    yield (i, str(next(subnet_gen)))
                 i += 1
 
     def net_resource_cleanup(self):
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
index 1bcf5b1..643620f 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
@@ -14,7 +14,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import six
 from tempest import config
 
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
@@ -28,7 +27,7 @@
 
     def _test_check_neighbor_established(self, ip_version):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                         cidr=subnet + mask, router=None)
@@ -47,7 +46,7 @@
 
     def _test_check_advertised_tenant_network(self, ip_version):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                         cidr=subnet + mask, router=None)
@@ -73,7 +72,7 @@
         tnets = []
         tnets_cidr = []
         for i in range(0, 3):
-            num, subnet = six.next(self.tnet_gen)
+            num, subnet = next(self.tnet_gen)
             mask = '/' + str(self.TPool.prefixlen)
             TNet = base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                             cidr=subnet + mask, router=None)
@@ -102,7 +101,7 @@
         for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args,
                                             self.r_as_ip):
             bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                         cidr=subnet + mask, router=None)
@@ -124,7 +123,7 @@
         for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args,
                                             self.r_as_ip):
             bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                         cidr=subnet + mask, router=None)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
index 1c680f9..90a6815 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
@@ -15,7 +15,6 @@
 #    under the License.
 
 from os_ken.tests.integrated.common import docker_base as ctn_base
-import six
 from tempest import config
 from tempest.lib import decorators
 
@@ -31,7 +30,7 @@
     @decorators.idempotent_id('cc615252-c6cb-4d75-a70e-608fb2c3736a')
     def test_schedule_added_speaker(self):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                           cidr=subnet + mask, router=None)
@@ -51,7 +50,7 @@
     @decorators.idempotent_id('ce98c33c-0ffa-49ae-b365-da836406793b')
     def test_unschedule_deleted_speaker(self):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                           cidr=subnet + mask, router=None)
@@ -77,7 +76,7 @@
     @decorators.idempotent_id('aa6c565c-ded3-413b-8dc9-3928b3b0e38f')
     def test_remove_add_speaker_agent(self):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = six.next(self.tnet_gen)
+        num, subnet = next(self.tnet_gen)
         mask = '/' + str(self.TPool.prefixlen)
         TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
                           cidr=subnet + mask, router=None)
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 9223893..752e536 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -21,6 +21,7 @@
 from neutron_lib.api import validators
 from neutron_lib import constants as neutron_lib_constants
 from oslo_log import log
+from paramiko import ssh_exception as ssh_exc
 from tempest.common.utils import net_utils
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
@@ -121,9 +122,11 @@
         if not kwargs.get('security_groups'):
             kwargs['security_groups'] = [{'name': 'default'}]
 
-        client = self.os_primary.servers_client
-        if kwargs.get('availability_zone'):
-            client = self.os_admin.servers_client
+        client = kwargs.pop('client', None)
+        if client is None:
+            client = self.os_primary.servers_client
+            if kwargs.get('availability_zone'):
+                client = self.os_admin.servers_client
 
         server = client.create_server(
             flavorRef=flavor_ref,
@@ -139,6 +142,10 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         client.delete_server,
                         server['server']['id'])
+
+        self.wait_for_server_active(server['server'], client=client)
+        self.wait_for_guest_os_ready(server['server'], client=client)
+
         return server
 
     @classmethod
@@ -252,19 +259,20 @@
             server_kwargs['name'] = server_name
 
         self.server = self.create_server(**server_kwargs)
-        self.wait_for_server_active(self.server['server'])
         self.port = self.client.list_ports(network_id=self.network['id'],
                                            device_id=self.server[
                                                'server']['id'])['ports'][0]
         self.fip = self.create_floatingip(port=self.port)
 
-    def check_connectivity(self, host, ssh_user, ssh_key,
-                           servers=None, ssh_timeout=None):
-        ssh_client = ssh.Client(host, ssh_user,
-                                pkey=ssh_key, timeout=ssh_timeout)
+    def check_connectivity(self, host, ssh_user=None, ssh_key=None,
+                           servers=None, ssh_timeout=None, ssh_client=None):
+        # Either ssh_client or ssh_user+ssh_key is mandatory.
+        if ssh_client is None:
+            ssh_client = ssh.Client(host, ssh_user,
+                                    pkey=ssh_key, timeout=ssh_timeout)
         try:
             ssh_client.test_connection_auth()
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
             self._log_local_network_status()
@@ -294,15 +302,32 @@
                           "for the console log", server['id'])
 
     def _log_local_network_status(self):
-        local_routes = ip_utils.IPCommand().list_routes()
-        LOG.debug('Local routes:\n%s', '\n'.join(str(r) for r in local_routes))
-        arp_table = ip_utils.arp_table()
-        LOG.debug('Local ARP table:\n%s', '\n'.join(str(r) for r in arp_table))
+        self._log_ns_network_status()
+        for ns_name in ip_utils.IPCommand().list_namespaces():
+            self._log_ns_network_status(ns_name=ns_name)
+
+    def _log_ns_network_status(self, ns_name=None):
+        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))
+        LOG.debug('Namespace %s; Local routes:\n%s',
+                  ns_name, '\n'.join(str(r) for r in local_routes))
+        LOG.debug('Namespace %s; Local ARP table:\n%s',
+                  ns_name, '\n'.join(str(r) for r in arp_table))
 
     def _check_remote_connectivity(self, source, dest, count,
                                    should_succeed=True,
                                    nic=None, mtu=None, fragmentation=True,
-                                   timeout=None, pattern=None):
+                                   timeout=None, pattern=None,
+                                   forbid_packet_loss=False):
         """check ping server via source ssh connection
 
         :param source: RemoteClient: an ssh connection from which to ping
@@ -314,6 +339,7 @@
         :param fragmentation: Flag for packet fragmentation
         :param timeout: Timeout for all ping packet(s) to succeed
         :param pattern: hex digits included in ICMP messages
+        :param forbid_packet_loss: forbid or allow some lost packets
         :returns: boolean -- should_succeed == ping
         :returns: ping is false if ping failed
         """
@@ -352,6 +378,10 @@
                 return not should_succeed
             LOG.debug('ping result: %s', result)
 
+            if forbid_packet_loss and ' 0% packet loss' not in result:
+                LOG.debug('Packet loss detected')
+                return not should_succeed
+
             if validators.validate_ip_address(dest) is None:
                 # Assert that the return traffic was from the correct
                 # source address.
@@ -366,18 +396,21 @@
                                   nic=None, mtu=None, fragmentation=True,
                                   servers=None, timeout=None,
                                   ping_count=CONF.validation.ping_count,
-                                  pattern=None):
+                                  pattern=None, forbid_packet_loss=False):
         try:
             self.assertTrue(self._check_remote_connectivity(
                 source, dest, ping_count, should_succeed, nic, mtu,
                 fragmentation,
-                timeout=timeout, pattern=pattern))
-        except lib_exc.SSHTimeout as ssh_e:
+                timeout=timeout, pattern=pattern,
+                forbid_packet_loss=forbid_packet_loss))
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
         except AssertionError:
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
 
     def ping_ip_address(self, ip_address, should_succeed=True,
@@ -445,7 +478,30 @@
         self.wait_for_server_status(
             server, constants.SERVER_STATUS_ACTIVE, client)
 
-    def check_servers_hostnames(self, servers, timeout=None, log_errors=True):
+    def wait_for_guest_os_ready(self, server, client=None):
+        if not CONF.compute_feature_enabled.console_output:
+            LOG.debug('Console output not supported, cannot check if server '
+                      '%s is ready.', server['id'])
+            return
+
+        client = client or self.os_primary.servers_client
+
+        def system_booted():
+            console_output = client.get_console_output(server['id'])['output']
+            for line in console_output.split('\n'):
+                if 'login:' in line.lower():
+                    return True
+            return False
+
+        try:
+            utils.wait_until_true(system_booted, sleep=5)
+        except utils.WaitTimeout:
+            LOG.debug("No correct output in console of server %s found. "
+                      "Guest operating system status can't be checked.",
+                      server['id'])
+
+    def check_servers_hostnames(self, servers, timeout=None, log_errors=True,
+                                external_port=None):
         """Compare hostnames of given servers with their names."""
         try:
             for server in servers:
@@ -453,7 +509,7 @@
                 if timeout:
                     kwargs['timeout'] = timeout
                 try:
-                    kwargs['port'] = (
+                    kwargs['port'] = external_port or (
                         server['port_forwarding_tcp']['external_port'])
                 except KeyError:
                     pass
@@ -463,16 +519,18 @@
                     pkey=self.keypair['private_key'],
                     **kwargs)
                 self.assertIn(server['name'],
-                              ssh_client.exec_command('hostname'))
-        except lib_exc.SSHTimeout as ssh_e:
+                              ssh_client.get_hostname())
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             if log_errors:
                 self._log_console_output(servers)
+                self._log_local_network_status()
             raise
         except AssertionError as assert_e:
             LOG.debug(assert_e)
             if log_errors:
                 self._log_console_output(servers)
+                self._log_local_network_status()
             raise
 
     def ensure_nc_listen(self, ssh_client, port, protocol, echo_msg=None,
@@ -497,9 +555,10 @@
             return ssh_client.execute_script(
                 get_ncat_server_cmd(port, protocol, echo_msg),
                 become_root=True, combine_stderr=True)
-        except lib_exc.SSHTimeout as ssh_e:
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output(servers)
+            self._log_local_network_status()
             raise
 
     def nc_client(self, ip_address, port, protocol):
diff --git a/neutron_tempest_plugin/scenario/test_connectivity.py b/neutron_tempest_plugin/scenario/test_connectivity.py
index 1a7468a..66c4789 100644
--- a/neutron_tempest_plugin/scenario/test_connectivity.py
+++ b/neutron_tempest_plugin/scenario/test_connectivity.py
@@ -65,6 +65,7 @@
 
         for vm in vms:
             self.wait_for_server_active(vm['server'])
+            self.wait_for_guest_os_ready(vm['server'])
 
         return vms
 
@@ -231,6 +232,7 @@
             networks=[{'uuid': network['id']}],
             security_groups=[{'name': self.secgroup['name']}])
         self.wait_for_server_active(vm['server'])
+        self.wait_for_guest_os_ready(vm['server'])
 
         vm_port = self.client.list_ports(
             network_id=network['id'], device_id=vm['server']['id'])['ports'][0]
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_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index 7c59d3a..262f429 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -341,6 +341,15 @@
     def resource_setup(cls):
         super(FloatingIPQosTest, cls).resource_setup()
 
+    def skip_if_no_extension_enabled_in_l3_agents(self, extension):
+        l3_agents = self.os_admin.network_client.list_agents(
+                binary='neutron-l3-agent')['agents']
+        for agent in l3_agents:
+            if extension in agent['configurations'].get('extensions', []):
+                return
+        raise self.skipTest("No L3 agent with '%s' extension enabled found." %
+                            extension)
+
     @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
     def test_qos(self):
         """Test floating IP is binding to a QoS policy with
@@ -351,6 +360,8 @@
            received / elapsed time.
         """
 
+        self.skip_if_no_extension_enabled_in_l3_agents("fip_qos")
+
         self._test_basic_resources()
         policy_id = self._create_qos_policy()
         ssh_client = self._create_ssh_client()
@@ -417,6 +428,7 @@
             servers.append(server)
         for server in servers:
             self.wait_for_server_active(server)
+            self.wait_for_guest_os_ready(server)
 
         self.fip = self.create_floatingip(port=ports[0])
         self.check_connectivity(self.fip['floating_ip_address'],
@@ -434,8 +446,7 @@
 
         # The FIP is now associated with the port of the second server.
         try:
-            common_utils.wait_until_true(_wait_for_fip_associated,
-                                         timeout=15, sleep=3)
+            common_utils.wait_until_true(_wait_for_fip_associated, sleep=3)
         except common_utils.WaitTimeout:
             self._log_console_output(servers[-1:])
             self.fail(
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index d19286c..406af3d 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -52,13 +52,14 @@
                 {'name': self.security_groups[-1]['name']}],
             name='leia')
         self.wait_for_server_active(leia['server'])
+        self.wait_for_guest_os_ready(leia['server'])
 
         ssh_client = ssh.Client(
             self.fip['floating_ip_address'],
             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 02e2846..4237d4f 100644
--- a/neutron_tempest_plugin/scenario/test_ipv6.py
+++ b/neutron_tempest_plugin/scenario/test_ipv6.py
@@ -15,6 +15,7 @@
 
 from neutron_lib import constants as lib_constants
 from oslo_log import log
+from paramiko import ssh_exception as ssh_exc
 from tempest.common import utils as tempest_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -83,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
@@ -108,16 +110,22 @@
                     return True
             return False
 
-        # Set NIC with IPv6 to be UP and wait until IPv6 address will be
-        # configured on this NIC
-        turn_nic6_on(ssh_client, ipv6_port)
-        # And check if IPv6 address will be properly configured on this NIC
-        utils.wait_until_true(
-            lambda: guest_has_address(ipv6_address),
-            timeout=120,
-            exception=RuntimeError(
-                "Timed out waiting for IP address {!r} to be configured in "
-                "the VM {!r}.".format(ipv6_address, vm['id'])))
+        try:
+            # Set NIC with IPv6 to be UP and wait until IPv6 address will be
+            # configured on this NIC
+            turn_nic6_on(ssh_client, ipv6_port)
+            # And check if IPv6 address will be properly configured on this NIC
+            utils.wait_until_true(
+                lambda: guest_has_address(ipv6_address),
+                timeout=120,
+                exception=RuntimeError(
+                    "Timed out waiting for IP address {!r} to be configured "
+                    "in the VM {!r}.".format(ipv6_address, vm['id'])))
+        except (lib_exc.SSHTimeout, ssh_exc.AuthenticationException) as ssh_e:
+            LOG.debug(ssh_e)
+            self._log_console_output([vm])
+            self._log_local_network_status()
+            raise
 
     def _test_ipv6_hotplug(self, ra_mode, address_mode):
         ipv6_networks = [self.create_network() for _ in range(2)]
@@ -138,6 +146,7 @@
         }
         vm = self.create_server(**server_kwargs)['server']
         self.wait_for_server_active(vm)
+        self.wait_for_guest_os_ready(vm)
         ipv4_port = self.client.list_ports(
             network_id=self.network['id'],
             device_id=vm['id'])['ports'][0]
diff --git a/neutron_tempest_plugin/scenario/test_metadata.py b/neutron_tempest_plugin/scenario/test_metadata.py
new file mode 100644
index 0000000..91ecc97
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_metadata.py
@@ -0,0 +1,149 @@
+# Copyright 2020 Ericsson Software Technology
+#
+# 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.
+
+import collections
+
+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
+
+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
+
+Server = collections.namedtuple(
+    'Server', ['floating_ip', 'server', 'ssh_client'])
+
+
+class MetadataTest(base.BaseTempestTestCase):
+
+    """Test metadata access over IPv6 tenant subnet.
+
+    Please note that there is metadata over IPv4 test coverage in tempest:
+
+    tempest.scenario.test_server_basic_ops\
+        .TestServerBasicOps.test_server_basic_ops
+    """
+
+    credentials = ['primary', 'admin']
+    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)
+        cls.subnet_v6 = cls.create_subnet(
+            network=cls.network, name=cls.rand_name, ip_version=6)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet_v4['id'])
+        cls.create_router_interface(cls.router['id'], cls.subnet_v6['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'])
+
+    def _create_server_with_network(self, network, use_advanced_image=False):
+        port = self._create_server_port(network=network)
+        floating_ip = self.create_floatingip(port=port)
+        ssh_client = self._create_ssh_client(
+            floating_ip=floating_ip, use_advanced_image=use_advanced_image)
+        server = self._create_server(port=port,
+                                     use_advanced_image=use_advanced_image)
+        return Server(
+            floating_ip=floating_ip, server=server, ssh_client=ssh_client)
+
+    def _create_server_port(self, network=None, **params):
+        network = network or self.network
+        return self.create_port(network=network, name=self.rand_name,
+                                security_groups=[self.security_group['id']],
+                                **params)
+
+    def _create_server(self, port, use_advanced_image=False, **params):
+        if use_advanced_image:
+            flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref
+            image_ref = CONF.neutron_plugin_options.advanced_image_ref
+        else:
+            flavor_ref = CONF.compute.flavor_ref
+            image_ref = CONF.compute.image_ref
+        return self.create_server(flavor_ref=flavor_ref,
+                                  image_ref=image_ref,
+                                  key_name=self.keypair['name'],
+                                  networks=[{'port': port['id']}],
+                                  **params)['server']
+
+    def _create_ssh_client(self, floating_ip, use_advanced_image=False):
+        if use_advanced_image:
+            username = CONF.neutron_plugin_options.advanced_image_ssh_user
+        else:
+            username = CONF.validation.image_ssh_user
+        return ssh.Client(host=floating_ip['floating_ip_address'],
+                          username=username,
+                          pkey=self.keypair['private_key'])
+
+    def _assert_has_ssh_connectivity(self, ssh_client):
+        ssh_client.exec_command('true')
+
+    def _get_primary_interface(self, ssh_client):
+        out = ssh_client.exec_command(
+            "ip -6 -br address show scope link up | head -1 | cut -d ' ' -f1")
+        interface = out.strip()
+        if not interface:
+            self.fail(
+                'Could not find a single interface '
+                'with an IPv6 link-local address.')
+        return interface
+
+    @testtools.skipUnless(
+        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 = (
+            not CONF.neutron_plugin_options.default_image_is_advanced)
+
+        vm = self._create_server_with_network(
+            self.network, use_advanced_image=use_advanced_image)
+        self.wait_for_server_active(server=vm.server)
+        self.wait_for_guest_os_ready(vm.server)
+        self._assert_has_ssh_connectivity(vm.ssh_client)
+        interface = self._get_primary_interface(vm.ssh_client)
+
+        out = vm.ssh_client.exec_command(
+            'curl http://[%(address)s%%25%(interface)s]/' % {
+                'address': nlib_const.METADATA_V6_IP,
+                'interface': interface})
+        self.assertIn('latest', out)
+
+        out = vm.ssh_client.exec_command(
+            'curl http://[%(address)s%%25%(interface)s]/openstack/' % {
+                'address': nlib_const.METADATA_V6_IP,
+                'interface': interface})
+        self.assertIn('latest', out)
diff --git a/neutron_tempest_plugin/scenario/test_multicast.py b/neutron_tempest_plugin/scenario/test_multicast.py
index b2ea8ae..79f835e 100644
--- a/neutron_tempest_plugin/scenario/test_multicast.py
+++ b/neutron_tempest_plugin/scenario/test_multicast.py
@@ -201,6 +201,7 @@
             security_groups=[{'name': self.secgroup['security_group']['name']}]
         )['server']
         self.wait_for_server_active(server)
+        self.wait_for_guest_os_ready(server)
         port = self.client.list_ports(
             network_id=self.network['id'], device_id=server['id'])['ports'][0]
         server['fip'] = self.create_floatingip(port=port)
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index da1db1b..a4711ae 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -14,6 +14,7 @@
 #    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
@@ -44,9 +45,14 @@
         cls.secgroup = cls.create_security_group(
             name=data_utils.rand_name("test_port_secgroup"))
         cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
+        udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
+                       'direction': constants.INGRESS_DIRECTION,
+                       'remote_ip_prefix': '0.0.0.0/0'}
+        cls.create_secgroup_rules(
+            [udp_sg_rule], secgroup_id=cls.secgroup['id'])
         cls.keypair = cls.create_keypair()
 
-    def _prepare_resources(self, num_servers, internal_tcp_port, protocol,
+    def _prepare_resources(self, num_servers, internal_tcp_port=22,
                            external_port_base=1025):
         servers = []
         for i in range(1, num_servers + 1):
@@ -64,6 +70,7 @@
                 networks=[{'port': port['id']}])['server']
             server['name'] = name
             self.wait_for_server_active(server)
+            self.wait_for_guest_os_ready(server)
             server['port_forwarding_tcp'] = self.create_port_forwarding(
                 self.fip['id'],
                 internal_port_id=port['id'],
@@ -81,7 +88,7 @@
             servers.append(server)
         return servers
 
-    def _test_udp_port_forwarding(self, servers):
+    def _test_udp_port_forwarding(self, servers, timeout=None):
 
         def _message_received(server, ssh_client, expected_msg):
             self.nc_listen(ssh_client,
@@ -102,22 +109,21 @@
                 CONF.validation.image_ssh_user,
                 pkey=self.keypair['private_key'],
                 port=server['port_forwarding_tcp']['external_port'])
+            wait_params = {
+                'exception': RuntimeError(
+                    "Timed out waiting for message from server {!r} ".format(
+                        server['id']))
+            }
+            if timeout:
+                wait_params['timeout'] = timeout
             utils.wait_until_true(
                 lambda: _message_received(server, ssh_client, expected_msg),
-                exception=RuntimeError(
-                    "Timed out waiting for message from server {!r} ".format(
-                        server['id'])))
+                **wait_params)
 
     @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
     def test_port_forwarding_to_2_servers(self):
-        udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
-                       'direction': constants.INGRESS_DIRECTION,
-                       'remote_ip_prefix': '0.0.0.0/0'}
-        self.create_secgroup_rules(
-            [udp_sg_rule], secgroup_id=self.secgroup['id'])
-        servers = self._prepare_resources(
-            num_servers=2, internal_tcp_port=22,
-            protocol=constants.PROTO_NAME_TCP)
+        servers = self._prepare_resources(num_servers=2,
+                                          external_port_base=1035)
         # Test TCP port forwarding by SSH to each server
         self.check_servers_hostnames(servers)
         # And now test UDP port forwarding using nc
@@ -125,25 +131,16 @@
 
     @decorators.idempotent_id('aa19d46c-a4a6-11ea-bb37-0242ac130002')
     def test_port_forwarding_editing_and_deleting_tcp_rule(self):
-        server = self._prepare_resources(
-            num_servers=1, internal_tcp_port=22,
-            protocol=constants.PROTO_NAME_TCP,
-            external_port_base=1035)
+        test_ext_port = 3333
+        server = self._prepare_resources(num_servers=1,
+                                         external_port_base=1045)
         fip_id = server[0]['port_forwarding_tcp']['floatingip_id']
         pf_id = server[0]['port_forwarding_tcp']['id']
 
         # Check connectivity with the original parameters
         self.check_servers_hostnames(server)
 
-        # Use a reasonable timeout to verify that connections will not
-        # happen. Default would be 196 seconds, which is an overkill.
-        test_ssh_connect_timeout = 6
-
-        # Update external port and check connectivity with original parameters
-        # Port under server[0]['port_forwarding_tcp']['external_port'] should
-        # not answer at this point.
-
-        def fip_pf_connectivity():
+        def fip_pf_connectivity(test_ssh_connect_timeout=60):
             try:
                 self.check_servers_hostnames(
                     server, timeout=test_ssh_connect_timeout)
@@ -152,9 +149,13 @@
                 return False
 
         def no_fip_pf_connectivity():
-            return not fip_pf_connectivity()
+            return not fip_pf_connectivity(6)
 
-        self.client.update_port_forwarding(fip_id, pf_id, external_port=3333)
+        # Update external port and check connectivity with original parameters
+        # Port under server[0]['port_forwarding_tcp']['external_port'] should
+        # not answer at this point.
+        self.client.update_port_forwarding(fip_id, pf_id,
+                                           external_port=test_ext_port)
         utils.wait_until_true(
             no_fip_pf_connectivity,
             exception=RuntimeError(
@@ -164,7 +165,7 @@
                     server[0]['port_forwarding_tcp']['external_port'])))
 
         # Check connectivity with the new parameters
-        server[0]['port_forwarding_tcp']['external_port'] = 3333
+        server[0]['port_forwarding_tcp']['external_port'] = test_ext_port
         utils.wait_until_true(
             fip_pf_connectivity,
             exception=RuntimeError(
@@ -184,3 +185,106 @@
                 "port {!r} is still possible.".format(
                     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
+        server = self._prepare_resources(num_servers=1,
+                                         external_port_base=1055)
+        fip_id = server[0]['port_forwarding_udp']['floatingip_id']
+        pf_id = server[0]['port_forwarding_udp']['id']
+
+        # Check connectivity with the original parameters
+        self.check_servers_hostnames(server)
+
+        def fip_pf_udp_connectivity(test_udp_timeout=60):
+            try:
+                self._test_udp_port_forwarding(server, test_udp_timeout)
+                return True
+            except (AssertionError, RuntimeError):
+                return False
+
+        def no_fip_pf_udp_connectivity():
+            return not fip_pf_udp_connectivity(6)
+
+        # Update external port and check connectivity with original parameters
+        # Port under server[0]['port_forwarding_udp']['external_port'] should
+        # not answer at this point.
+        self.client.update_port_forwarding(fip_id, pf_id,
+                                           external_port=test_ext_port)
+        utils.wait_until_true(
+            no_fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is still possible.".format(
+                    server[0]['id'],
+                    server[0]['port_forwarding_udp']['external_port'])))
+
+        # Check connectivity with the new parameters
+        server[0]['port_forwarding_udp']['external_port'] = test_ext_port
+        utils.wait_until_true(
+            fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is not possible.".format(
+                    server[0]['id'],
+                    server[0]['port_forwarding_udp']['external_port'])))
+
+        # Remove port forwarding and ensure connection stops working.
+        self.client.delete_port_forwarding(fip_id, pf_id)
+        self.assertRaises(lib_exc.NotFound, self.client.get_port_forwarding,
+                          fip_id, pf_id)
+        utils.wait_until_true(
+            no_fip_pf_udp_connectivity,
+            exception=RuntimeError(
+                "Connection to the server {!r} through "
+                "port {!r} is still possible.".format(
+                    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,
+            security_groups=[self.secgroup['id']])
+        name = data_utils.rand_name("server-0")
+        server = self.create_server(flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref, key_name=self.keypair['name'],
+            name=name, networks=[{'port': port['id']}])['server']
+        server['name'] = name
+        self.wait_for_server_active(server)
+        self.wait_for_guest_os_ready(server)
+
+        # Add a second fixed_ip address to port (same subnet)
+        internal_subnet_id = port['fixed_ips'][0]['subnet_id']
+        port['fixed_ips'].append({'subnet_id': internal_subnet_id})
+        port = self.update_port(port, fixed_ips=port['fixed_ips'])
+        internal_ip_address1 = port['fixed_ips'][0]['ip_address']
+        internal_ip_address2 = port['fixed_ips'][1]['ip_address']
+        pfs = []
+        for ip_address, external_port in [(internal_ip_address1, 1066),
+                                          (internal_ip_address2, 1067)]:
+            pf = self.create_port_forwarding(
+                self.fip['id'], internal_port_id=port['id'],
+                internal_ip_address=ip_address,
+                internal_port=22, external_port=external_port,
+                protocol=constants.PROTO_NAME_TCP)
+            pfs.append(pf)
+
+        test_ssh_connect_timeout = 32
+        number_of_connects = 0
+        for pf in pfs:
+            try:
+                self.check_servers_hostnames(
+                    [server], timeout=test_ssh_connect_timeout,
+                    external_port=pf['external_port'])
+                number_of_connects += 1
+            except (AssertionError, lib_exc.SSHTimeout):
+                pass
+
+        # TODO(flaviof): Quite possibly, the server is using only one of the
+        # fixed ips associated with the neutron port. Being so, we should not
+        # fail the test, as long as at least one connection was successful.
+        self.assertGreaterEqual(
+            number_of_connects, 1, "Did not connect via FIP port forwarding")
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index 938d2b0..77520a7 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -175,6 +175,7 @@
 
         server = self.create_server(**server_kwargs)
         self.wait_for_server_active(server['server'])
+        self.wait_for_guest_os_ready(server['server'])
         self.check_connectivity(self.fip['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
                                 keypair['private_key'])
@@ -190,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
@@ -243,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(
@@ -260,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()
@@ -283,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(
@@ -300,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_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 23a5224..9059a2f 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -33,13 +33,15 @@
     required_extensions = ['router', 'security-group']
 
     def _verify_http_connection(self, ssh_client, ssh_server,
-                                test_ip, test_port, should_pass=True):
+                                test_ip, test_port, servers, should_pass=True):
         """Verify if HTTP connection works using remote hosts.
 
         :param ssh.Client ssh_client: The client host active SSH client.
         :param ssh.Client ssh_server: The HTTP server host active SSH client.
         :param string test_ip: IP address of HTTP server
         :param string test_port: Port of HTTP server
+        :param list servers: List of servers for which console output will be
+                             logged in case when test case
         :param bool should_pass: Wheter test should pass or not.
 
         :return: if passed or not
@@ -57,6 +59,8 @@
         except Exception as e:
             if not should_pass:
                 return
+            self._log_console_output(servers)
+            self._log_local_network_status()
             raise e
 
     @classmethod
@@ -378,6 +382,7 @@
                 ssh_clients[0],
                 ssh_clients[2],
                 test_ip, port,
+                servers,
                 should_pass=False)
 
         # add two remote-group rules with port-ranges
@@ -399,7 +404,8 @@
             self._verify_http_connection(
                 ssh_clients[0],
                 ssh_clients[2],
-                test_ip, port)
+                test_ip, port,
+                servers)
 
     @decorators.idempotent_id('f07d0159-8f9e-4faa-87f5-a869ab0ad490')
     def test_intra_sg_isolation(self):
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
index 585af06..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
@@ -161,6 +162,7 @@
 
     def _configure_vlan_subport(self, vm, vlan_tag, vlan_subnet):
         self.wait_for_server_active(server=vm.server)
+        self.wait_for_guest_os_ready(vm.server)
         self._wait_for_trunk(trunk=vm.trunk)
         self._wait_for_port(port=vm.port)
         self._wait_for_port(port=vm.subport)
@@ -199,6 +201,7 @@
         vm2 = self._create_server_with_trunk_port()
         for vm in (vm1, vm2):
             self.wait_for_server_active(server=vm.server)
+            self.wait_for_guest_os_ready(vm.server)
             self._wait_for_trunk(vm.trunk)
             self._assert_has_ssh_connectivity(vm.ssh_client)
 
@@ -244,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),
@@ -267,6 +271,9 @@
             use_advanced_image=use_advanced_image)
 
         for vm in [vm1, vm2]:
+            self.check_connectivity(
+                host=vm.floating_ip['floating_ip_address'],
+                ssh_client=vm.ssh_client)
             self._configure_vlan_subport(vm=vm,
                                          vlan_tag=vlan_tag,
                                          vlan_subnet=vlan_subnet)
@@ -325,6 +332,7 @@
                                 use_advanced_image=use_advanced_image)
         for role in ['migrate', 'connection_test']:
             self.wait_for_server_active(servers[role].server)
+            self.wait_for_guest_os_ready(servers[role].server)
             self._configure_vlan_subport(vm=servers[role],
                                          vlan_tag=vlan_tag,
                                          vlan_subnet=vlan_subnet)
@@ -377,6 +385,7 @@
                                      vlan_subnet=vlan_subnet)
         for vm in vms:
             self.wait_for_server_active(vm.server)
+            self.wait_for_guest_os_ready(vm.server)
 
         # allow ICMP traffic
         sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
diff --git a/neutron_tempest_plugin/services/bgp/bgp_client.py b/neutron_tempest_plugin/services/bgp/bgp_client.py
index ae51427..7d46e29 100644
--- a/neutron_tempest_plugin/services/bgp/bgp_client.py
+++ b/neutron_tempest_plugin/services/bgp/bgp_client.py
@@ -15,6 +15,25 @@
 from oslo_serialization import jsonutils
 from tempest.lib.common import rest_client
 
+from neutron_tempest_plugin.api import clients as manager
+from neutron_tempest_plugin import config
+
+
+CONF = config.CONF
+
+
+class Manager(manager.Manager):
+    def __init__(self, credentials=None, service=None):
+        super(Manager, self).__init__(credentials, service)
+        self.bgp_client = BgpSpeakerClientJSON(
+            self.auth_provider,
+            CONF.network.catalog_type,
+            CONF.network.region or CONF.identity.region,
+            endpoint_type=CONF.network.endpoint_type,
+            build_interval=CONF.network.build_interval,
+            build_timeout=CONF.network.build_timeout,
+            **self.default_params)
+
 
 class BgpSpeakerClientJSON(rest_client.RestClient):
 
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index f056c7f..2678d73 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -11,9 +11,9 @@
 #    under the License.
 
 import time
+from urllib import parse as urlparse
 
 from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urlparse
 from tempest.lib.common import rest_client as service_client
 from tempest.lib import exceptions as lib_exc
 
@@ -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)
@@ -952,8 +948,6 @@
         return service_client.ResponseBody(resp, body)
 
     def list_floatingips(self, **kwargs):
-        post_body = {'floatingips': kwargs}
-        body = jsonutils.dumps(post_body)
         uri = '%s/floatingips' % self.uri_prefix
         if kwargs:
             uri += '?' + urlparse.urlencode(kwargs, doseq=1)
diff --git a/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py b/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
index 995868a..2f091e0 100644
--- a/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
+++ b/neutron_tempest_plugin/sfc/tests/scenario/test_sfc.py
@@ -14,6 +14,7 @@
 
 import time
 
+from neutron_lib.utils import test
 from oslo_log import log
 from tempest.common import utils
 from tempest import config
@@ -953,6 +954,7 @@
             username=self.ssh_user,
             private_key=self.keypair['private_key'])
 
+    @test.unstable_test("bug 1897753")
     @decorators.idempotent_id('f970f6b3-6541-47ac-a9ea-f769be1e21ba')
     @utils.services('compute', 'network')
     def test_update_port_pair_group_remove_port_pairs(self):
diff --git a/playbooks/linuxbridge-scenario-pre-run.yaml b/playbooks/linuxbridge-scenario-pre-run.yaml
new file mode 100644
index 0000000..26586f6
--- /dev/null
+++ b/playbooks/linuxbridge-scenario-pre-run.yaml
@@ -0,0 +1,7 @@
+- hosts: all
+  tasks:
+    # TODO(slaweq): remove it when nftables will support syntax for src and
+    # destination IP addresses in arp tables:
+    - include_role:
+        name: legacy_ebtables
+      when: ansible_distribution_release | lower == 'focal'
diff --git a/requirements.txt b/requirements.txt
index 3edf7dc..47dd923 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,7 +11,6 @@
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
 oslo.utils>=3.33.0 # Apache-2.0
 paramiko>=2.0.0 # LGPLv2.1+
-six>=1.10.0 # MIT
 tempest>=17.1.0 # Apache-2.0
 tenacity>=3.2.1 # Apache-2.0
 ddt>=1.0.1 # MIT
diff --git a/test-requirements.txt b/test-requirements.txt
index 6cbe947..bf1c626 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,7 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
-hacking<0.13,>=0.12.0 # Apache-2.0
+hacking>=3.2.0,<3.3.0 # Apache-2.0
 
 coverage!=4.4,>=4.0 # Apache-2.0
 flake8-import-order==0.12 # LGPLv3
diff --git a/tox.ini b/tox.ini
index 760cc47..3c18fbf 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}
 
@@ -53,7 +53,8 @@
 # E128 continuation line under-indented for visual indent
 # E129 visually indented line with same indent as next logical line
 # N530 direct neutron imports not allowed
-ignore = E126,E128,E129,N530
+# W504 line break after binary operator
+ignore = E126,E128,E129,N530,W504
 # H106: Don't put vim configuration in source files
 # H203: Use assertIs(Not)None to check for None
 # H204: Use assert(Not)Equal to check for equality
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index db59ac0..cc1479b 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -17,13 +17,14 @@
       devstack_localrc:
         USE_PYTHON3: true
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
+        CIRROS_VERSION: 0.5.1
+        BUILD_TIMEOUT: 784
       devstack_plugins:
         neutron: https://opendev.org/openstack/neutron.git
         neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
       tempest_plugins:
         - neutron-tempest-plugin
       devstack_services:
-        tls-proxy: false
         tempest: true
         neutron-dns: true
         neutron-qos: true
@@ -36,6 +37,17 @@
         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
+        s-object: false
+        s-proxy: false
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -62,11 +74,6 @@
           $NEUTRON_DHCP_CONF:
             agent:
               availability_zone: nova
-          /etc/neutron/api-paste.ini:
-            composite:neutronapi_v2_0:
-              use: call:neutron.auth:pipeline_factory
-              noauth: cors request_id catch_errors osprofiler extensions neutronapiapp_v2_0
-              keystone: cors request_id catch_errors osprofiler authtoken keystonecontext extensions neutronapiapp_v2_0
         test-config:
           $TEMPEST_CONFIG:
             neutron_plugin_options:
@@ -97,14 +104,16 @@
       # 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
-        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img
-        ADVANCED_IMAGE_NAME: ubuntu-16.04-server-cloudimg-amd64-disk1
+        CIRROS_VERSION: 0.5.1
+        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-18.04-server-cloudimg-amd64
         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 45862c8..5325f67 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
 
@@ -158,8 +168,12 @@
     name: neutron-tempest-plugin-scenario-linuxbridge
     parent: neutron-tempest-plugin-scenario
     timeout: 10000
+    roles:
+      - zuul: openstack/neutron
+    pre-run: playbooks/linuxbridge-scenario-pre-run.yaml
     vars:
       network_api_extensions: *api_extensions
+      network_available_features: *available_features
       devstack_localrc:
         Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
@@ -178,6 +192,8 @@
               type_drivers: flat,vlan,local,vxlan
         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
@@ -188,9 +204,17 @@
     timeout: 10000
     vars:
       network_api_extensions: *api_extensions
-      # TODO(haleyb): Remove this blacklist when
+      # TODO(haleyb): Remove IPv6Test from blacklist when
       # https://bugs.launchpad.net/neutron/+bug/1881558 is fixed.
-      tempest_black_regex: "(?:neutron_tempest_plugin.scenario.test_ipv6.IPv6Test)"
+      # TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
+      # blacklist when bug https://bugs.launchpad.net/neutron/+bug/1885900 will
+      # be fixed
+      # TODO(jlibosva): Remove the NetworkWritableMtuTest test from the list
+      # once east/west fragmentation is supported in core OVN
+      tempest_black_regex: "\
+          (?:neutron_tempest_plugin.scenario.test_ipv6.IPv6Test)|\
+          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_trunk_subport_lifecycle)|\
+          (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         Q_AGENT: ovn
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
@@ -202,10 +226,6 @@
         ENABLE_CHASSIS_AS_GW: true
         OVN_L3_CREATE_PUBLIC_NETWORK: true
         OVN_DBS_LOG_LEVEL: dbg
-        # TODO(mjozefcz): Stop compiling OVS modules when meter action in kernel
-        # will be released in Ubuntu Bionic.
-        # More info: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-December/048009.html
-        OVN_BUILD_MODULES: True
         ENABLE_TLS: True
         OVN_IGMP_SNOOPING_ENABLE: True
       devstack_services:
@@ -221,13 +241,19 @@
         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:
@@ -238,6 +264,8 @@
               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
               is_igmp_snooping_enabled: True
@@ -271,8 +299,9 @@
         USE_PYTHON3: true
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_dvr) | join(',') }}"
         PHYSICAL_NETWORK: default
-        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/xenial/release/ubuntu-16.04-server-cloudimg-amd64-disk1.img
-        ADVANCED_IMAGE_NAME: ubuntu-16.04-server-cloudimg-amd64-disk1
+        CIRROS_VERSION: 0.5.1
+        IMAGE_URLS: https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-amd64.img
+        ADVANCED_IMAGE_NAME: ubuntu-18.04-server-cloudimg-amd64
         ADVANCED_INSTANCE_TYPE: ds512M
         ADVANCED_INSTANCE_USER: ubuntu
         BUILD_TIMEOUT: 784
@@ -290,7 +319,17 @@
         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
+        s-object: false
+        s-proxy: false
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -336,6 +375,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
@@ -353,6 +394,14 @@
           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
+          s-object: false
+          s-proxy: false
         devstack_localrc:
           USE_PYTHON3: true
         devstack_local_conf:
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 3086d6c..dc6e56c 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -3,7 +3,11 @@
     check:
       jobs:
         - neutron-tempest-plugin-api
-        - neutron-tempest-plugin-designate-scenario
+        - neutron-tempest-plugin-designate-scenario:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-scenario-linuxbridge
         - neutron-tempest-plugin-scenario-openvswitch
         - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
@@ -27,7 +31,6 @@
     check:
       jobs:
         - neutron-tempest-plugin-api-queens
-        - neutron-tempest-plugin-dvr-multinode-scenario-queens
         - neutron-tempest-plugin-scenario-linuxbridge-queens
         - neutron-tempest-plugin-scenario-openvswitch-queens
     gate:
@@ -45,7 +48,11 @@
     check:
       jobs:
         - neutron-tempest-plugin-api-rocky
-        - neutron-tempest-plugin-designate-scenario-rocky
+        - neutron-tempest-plugin-designate-scenario-rocky:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-scenario-linuxbridge-rocky
         - neutron-tempest-plugin-scenario-openvswitch-rocky
         - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-rocky
@@ -64,7 +71,11 @@
     check:
       jobs:
         - neutron-tempest-plugin-api-stein
-        - neutron-tempest-plugin-designate-scenario-stein
+        - neutron-tempest-plugin-designate-scenario-stein:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-scenario-linuxbridge-stein
         - neutron-tempest-plugin-scenario-openvswitch-stein
         - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-stein
@@ -83,7 +94,11 @@
     check:
       jobs:
         - neutron-tempest-plugin-api-train
-        - neutron-tempest-plugin-designate-scenario-train
+        - neutron-tempest-plugin-designate-scenario-train:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-scenario-linuxbridge-train
         - neutron-tempest-plugin-scenario-openvswitch-train
         - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-train
@@ -102,7 +117,11 @@
     check:
       jobs:
         - neutron-tempest-plugin-api-ussuri
-        - neutron-tempest-plugin-designate-scenario-ussuri
+        - neutron-tempest-plugin-designate-scenario-ussuri:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
         - neutron-tempest-plugin-scenario-linuxbridge-ussuri
         - neutron-tempest-plugin-scenario-openvswitch-ussuri
         - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-ussuri
@@ -117,6 +136,30 @@
         - neutron-tempest-plugin-dvr-multinode-scenario-ussuri
 
 
+- project-template:
+    name: neutron-tempest-plugin-jobs-victoria
+    check:
+      jobs:
+        - neutron-tempest-plugin-api-victoria
+        - neutron-tempest-plugin-designate-scenario-victoria:
+            # TODO(slaweq): switch it to be voting when bug
+            # https://bugs.launchpad.net/neutron/+bug/1891309
+            # will be fixed
+            voting: false
+        - neutron-tempest-plugin-scenario-linuxbridge-victoria
+        - neutron-tempest-plugin-scenario-openvswitch-victoria
+        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-victoria
+        - neutron-tempest-plugin-scenario-ovn-victoria
+    gate:
+      jobs:
+        - neutron-tempest-plugin-api-victoria
+    #TODO(slaweq): Move neutron-tempest-plugin-dvr-multinode-scenario out of
+    #              the experimental queue when it will be more stable
+    experimental:
+      jobs:
+        - neutron-tempest-plugin-dvr-multinode-scenario-victoria
+
+
 - project:
     templates:
       - build-openstack-docs-pti
@@ -124,6 +167,7 @@
       - neutron-tempest-plugin-jobs-stein
       - neutron-tempest-plugin-jobs-train
       - neutron-tempest-plugin-jobs-ussuri
+      - neutron-tempest-plugin-jobs-victoria
       - check-requirements
       - tempest-plugin-jobs
       - release-notes-jobs-python3
@@ -132,9 +176,11 @@
         - neutron-tempest-plugin-sfc
         - neutron-tempest-plugin-sfc-train
         - neutron-tempest-plugin-sfc-ussuri
+        - neutron-tempest-plugin-sfc-victoria
         - neutron-tempest-plugin-bgpvpn-bagpipe
         - 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
@@ -145,8 +191,10 @@
             voting: false
         - neutron-tempest-plugin-dynamic-routing
         - neutron-tempest-plugin-dynamic-routing-ussuri
+        - neutron-tempest-plugin-dynamic-routing-victoria
         - neutron-tempest-plugin-vpnaas
         - neutron-tempest-plugin-vpnaas-ussuri
+        - neutron-tempest-plugin-vpnaas-victoria
 
     gate:
       jobs:
diff --git a/zuul.d/queens_jobs.yaml b/zuul.d/queens_jobs.yaml
index 2b52978..e1ecc00 100644
--- a/zuul.d/queens_jobs.yaml
+++ b/zuul.d/queens_jobs.yaml
@@ -65,8 +65,12 @@
         - 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(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
@@ -86,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
@@ -95,13 +100,17 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-queens
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
     nodeset: openstack-single-node-xenial
+    timeout: 10000
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/queens
     required-projects:
       - openstack/devstack-gate
@@ -112,21 +121,37 @@
     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: "\
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
+        Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
       devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+            AGENT:
+              debug_iptables_rules: true
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            ml2:
+              type_drivers: flat,vlan,local,vxlan
         test-config:
           # NOTE: ignores linux bridge's trunk delete on bound port test
-          # for queens branch (as https://review.opendev.org/#/c/605589/
-          # fix will not apply for queens branch)
+          # for rocky branch (as https://review.opendev.org/#/c/605589/
+          # fix will not apply for rocky branch)
           $TEMPEST_CONFIG:
             neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
               q_agent: None
 
 - job:
@@ -152,6 +177,7 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
 
 - job:
@@ -176,4 +202,5 @@
           (^neutron_tempest_plugin.scenario.test_internal_dns.InternalDNSTest.test_dns_domain_and_name)"
       devstack_localrc:
         USE_PYTHON3: false
+        CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
diff --git a/zuul.d/rocky_jobs.yaml b/zuul.d/rocky_jobs.yaml
index a45cae8..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(',') }}"
@@ -176,10 +179,13 @@
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-rocky
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
+    timeout: 10000
     description: |
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-single-node-xenial
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/rocky
     required-projects: *required-projects-rocky
     vars: &linuxbridge_vars_rocky
@@ -187,15 +193,29 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         USE_PYTHON3: false
+        Q_AGENT: linuxbridge
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
       devstack_local_conf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              enable_dvr: false
+            AGENT:
+              debug_iptables_rules: true
+          # NOTE(slaweq): We can get rid of this hardcoded absolute path when
+          # devstack-tempest job will be switched to use lib/neutron instead of
+          # lib/neutron-legacy
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            ml2:
+              type_drivers: flat,vlan,local,vxlan
         test-config:
           # NOTE: ignores linux bridge's trunk delete on bound port test
           # for rocky branch (as https://review.opendev.org/#/c/605589/
           # fix will not apply for rocky branch)
           $TEMPEST_CONFIG:
             neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
               q_agent: None
       # NOTE(bcafarel): newer tests, unstable on rocky branch
       tempest_black_regex: "\
@@ -206,17 +226,23 @@
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-rocky
-    parent: neutron-tempest-plugin-scenario-linuxbridge
+    parent: neutron-tempest-plugin-scenario
     nodeset: openstack-single-node-xenial
+    timeout: 10000
     description: |
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
+    roles:
+      - zuul: openstack/neutron
     override-checkout: stable/rocky
     required-projects: *required-projects-rocky
     vars:
       <<: *linuxbridge_vars_rocky
       devstack_localrc:
         USE_PYTHON3: True
+        Q_AGENT: linuxbridge
+        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
     branches: ^(?!stable/rocky).*$
 
 - job:
diff --git a/zuul.d/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index 1c9e299..9de8384 100644
--- a/zuul.d/stein_jobs.yaml
+++ b/zuul.d/stein_jobs.yaml
@@ -1,6 +1,7 @@
 - job:
     name: neutron-tempest-plugin-api-stein
     parent: neutron-tempest-plugin-api
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
     vars:
       branch_override: stable/stein
@@ -72,42 +73,73 @@
         - 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-stein
     parent: neutron-tempest-plugin-scenario-openvswitch
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/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
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-stein
     parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/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
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-stein
     parent: neutron-tempest-plugin-scenario-linuxbridge
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/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
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-stein
     parent: neutron-tempest-plugin-dvr-multinode-scenario
+    nodeset: openstack-two-node-bionic
     override-checkout: stable/stein
     vars:
       network_api_extensions_common: *api_extensions
@@ -116,6 +148,7 @@
 - job:
     name: neutron-tempest-plugin-designate-scenario-stein
     parent: neutron-tempest-plugin-designate-scenario
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/stein
     required-projects:
       - openstack/devstack-gate
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
index 132873b..0785924 100644
--- a/zuul.d/train_jobs.yaml
+++ b/zuul.d/train_jobs.yaml
@@ -1,6 +1,7 @@
 - job:
     name: neutron-tempest-plugin-api-train
     parent: neutron-tempest-plugin-api
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     vars:
       branch_override: stable/train
@@ -77,42 +78,73 @@
         - 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-train
     parent: neutron-tempest-plugin-scenario-openvswitch
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     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
 
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-train
     parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     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
 
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-train
     parent: neutron-tempest-plugin-scenario-linuxbridge
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     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
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-train
     parent: neutron-tempest-plugin-dvr-multinode-scenario
+    nodeset: openstack-two-node-bionic
     override-checkout: stable/train
     vars:
       network_api_extensions_common: *api_extensions
@@ -121,6 +153,7 @@
 - job:
     name: neutron-tempest-plugin-designate-scenario-train
     parent: neutron-tempest-plugin-designate-scenario
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     vars:
       branch_override: stable/train
@@ -129,6 +162,7 @@
 - job:
     name: neutron-tempest-plugin-sfc-train
     parent: neutron-tempest-plugin-sfc
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     vars:
       branch_override: stable/train
@@ -137,6 +171,7 @@
 - job:
     name: neutron-tempest-plugin-bgpvpn-bagpipe-train
     parent: neutron-tempest-plugin-bgpvpn-bagpipe
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     vars:
       branch_override: stable/train
@@ -145,6 +180,7 @@
 - job:
     name: neutron-tempest-plugin-fwaas-train
     parent: neutron-tempest-plugin-fwaas-ussuri
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/train
     vars:
       branch_override: stable/train
diff --git a/zuul.d/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
index a9e578e..b71a460 100644
--- a/zuul.d/ussuri_jobs.yaml
+++ b/zuul.d/ussuri_jobs.yaml
@@ -1,6 +1,7 @@
 - job:
     name: neutron-tempest-plugin-api-ussuri
     parent: neutron-tempest-plugin-api
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
@@ -81,52 +82,92 @@
         - 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
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     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
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     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-linuxbridge-ussuri
     parent: neutron-tempest-plugin-scenario-linuxbridge
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     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-ovn-ussuri
     parent: neutron-tempest-plugin-scenario-ovn
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+        # TODO(mjozefcz): Stop compiling OVS modules when meter action in kernel
+        # will be released in Ubuntu Bionic.
+        # More info: https://mail.openvswitch.org/pipermail/ovs-discuss/2018-December/048009.html
+        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
 
 - job:
     name: neutron-tempest-plugin-dvr-multinode-scenario-ussuri
     parent: neutron-tempest-plugin-dvr-multinode-scenario
+    nodeset: openstack-two-node-bionic
     override-checkout: stable/ussuri
     vars:
       network_api_extensions_common: *api_extensions
@@ -135,6 +176,7 @@
 - job:
     name: neutron-tempest-plugin-designate-scenario-ussuri
     parent: neutron-tempest-plugin-designate-scenario
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
@@ -143,6 +185,7 @@
 - job:
     name: neutron-tempest-plugin-sfc-ussuri
     parent: neutron-tempest-plugin-sfc
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
@@ -151,6 +194,7 @@
 - job:
     name: neutron-tempest-plugin-bgpvpn-bagpipe-ussuri
     parent: neutron-tempest-plugin-bgpvpn-bagpipe
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
@@ -159,6 +203,7 @@
 - job:
     name: neutron-tempest-plugin-fwaas-ussuri
     parent: neutron-tempest-plugin-base
+    nodeset: openstack-single-node-bionic
     timeout: 10800
     override-checkout: stable/ussuri
     required-projects:
@@ -182,6 +227,7 @@
 - job:
     name: neutron-tempest-plugin-dynamic-routing-ussuri
     parent: neutron-tempest-plugin-dynamic-routing
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
@@ -190,6 +236,7 @@
 - job:
     name: neutron-tempest-plugin-vpnaas-ussuri
     parent: neutron-tempest-plugin-vpnaas
+    nodeset: openstack-single-node-bionic
     override-checkout: stable/ussuri
     vars:
       branch_override: stable/ussuri
diff --git a/zuul.d/victoria_jobs.yaml b/zuul.d/victoria_jobs.yaml
new file mode 100644
index 0000000..5543ea7
--- /dev/null
+++ b/zuul.d/victoria_jobs.yaml
@@ -0,0 +1,205 @@
+- job:
+    name: neutron-tempest-plugin-api-victoria
+    parent: neutron-tempest-plugin-api
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      # TODO(slaweq): find a way to put this list of extensions in
+      # neutron repository and keep it different per branch,
+      # then it could be removed from here
+      network_api_extensions_common: &api_extensions
+        - address-scope
+        - agent
+        - allowed-address-pairs
+        - auto-allocated-topology
+        - availability_zone
+        - binding
+        - default-subnetpools
+        - dhcp_agent_scheduler
+        - dns-domain-ports
+        - dns-integration
+        - empty-string-filtering
+        - expose-port-forwarding-in-fip
+        - expose-l3-conntrack-helper
+        - ext-gw-mode
+        - external-net
+        - extra_dhcp_opt
+        - extraroute
+        - extraroute-atomic
+        - filter-validation
+        - fip-port-details
+        - flavors
+        - floating-ip-port-forwarding
+        - floatingip-pools
+        - ip-substring-filtering
+        - l3-conntrack-helper
+        - l3-flavors
+        - l3-ha
+        - l3_agent_scheduler
+        - logging
+        - metering
+        - multi-provider
+        - net-mtu
+        - net-mtu-writable
+        - network-ip-availability
+        - network_availability_zone
+        - network-segment-range
+        - pagination
+        - port-resource-request
+        - port-mac-address-regenerate
+        - port-security
+        - port-security-groups-filtering
+        - project-id
+        - provider
+        - qos
+        - qos-bw-minimum-ingress
+        - qos-fip
+        - quotas
+        - quota_details
+        - rbac-address-scope
+        - rbac-policies
+        - rbac-security-groups
+        - rbac-subnetpool
+        - router
+        - router-admin-state-down-before-update
+        - router_availability_zone
+        - security-group
+        - segment
+        - service-type
+        - sorting
+        - standard-attr-description
+        - standard-attr-revisions
+        - standard-attr-segment
+        - standard-attr-tag
+        - standard-attr-timestamp
+        - subnet_allocation
+        - subnet-dns-publish-fixed-ip
+        - subnetpool-prefix-ops
+        - tag-ports-during-bulk-creation
+        - trunk
+        - trunk-details
+        - 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:
+    name: neutron-tempest-plugin-scenario-openvswitch-victoria
+    parent: neutron-tempest-plugin-scenario-openvswitch
+    override-checkout: stable/victoria
+    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
+
+- job:
+    name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-victoria
+    parent: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid
+    override-checkout: stable-victoria
+    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
+
+- job:
+    name: neutron-tempest-plugin-scenario-linuxbridge-victoria
+    parent: neutron-tempest-plugin-scenario-linuxbridge
+    override-checkout: stable/victoria
+    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
+
+- job:
+    name: neutron-tempest-plugin-scenario-ovn-victoria
+    parent: neutron-tempest-plugin-scenario-ovn
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      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
+    parent: neutron-tempest-plugin-dvr-multinode-scenario
+    override-checkout: stable/victoria
+    vars:
+      network_api_extensions_common: *api_extensions
+      branch_override: stable/victoria
+
+- job:
+    name: neutron-tempest-plugin-designate-scenario-victoria
+    parent: neutron-tempest-plugin-designate-scenario
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-sfc-victoria
+    parent: neutron-tempest-plugin-sfc
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-bgpvpn-bagpipe-victoria
+    parent: neutron-tempest-plugin-bgpvpn-bagpipe
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-dynamic-routing-victoria
+    parent: neutron-tempest-plugin-dynamic-routing
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-vpnaas-victoria
+    parent: neutron-tempest-plugin-vpnaas
+    override-checkout: stable/victoria
+    vars:
+      branch_override: stable/victoria
+      network_api_extensions_common: *api_extensions