Merge "Check device_owner with actual availability zone"
diff --git a/.gitignore b/.gitignore
index 228b0a8..1ce41cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,7 @@
 
 # Sphinx
 doc/build
+doc/source/tests
 
 # pbr generates these
 AUTHORS
@@ -54,4 +55,4 @@
 .*sw?
 
 # Files created by releasenotes build
-releasenotes/build
\ No newline at end of file
+releasenotes/build
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 30628c0..cec7d37 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,17 +1,16 @@
-If you would like to contribute to the development of OpenStack, you must
-follow the steps in this page:
+The source repository for this project can be found at:
 
-   https://docs.openstack.org/infra/manual/developers.html
+   https://opendev.org/openstack/neutron-tempest-plugin
 
-If you already have a good understanding of how the system works and your
-OpenStack accounts are set up, you can skip to the development workflow
-section of this documentation to learn how changes to OpenStack should be
-submitted for review via the Gerrit tool:
+Pull requests submitted through GitHub are not monitored.
 
-   https://docs.openstack.org/infra/manual/developers.html#development-workflow
+To start contributing to OpenStack, follow the steps in the contribution guide
+to set up and use Gerrit:
 
-Pull requests submitted through GitHub will be ignored.
+   https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
 
-Bugs should be filed on Launchpad, not GitHub:
+Bugs should be filed on Launchpad:
 
-   https://bugs.launchpad.net/neutron_tempest_plugin
+   https://bugs.launchpad.net/neutron
+
+And the tag 'tempest' should be added to the bug.
diff --git a/README.rst b/README.rst
index e658b90..7222c76 100644
--- a/README.rst
+++ b/README.rst
@@ -9,5 +9,5 @@
 * Free software: Apache license
 * Documentation: https://docs.openstack.org/neutron/latest/
 * Source: https://opendev.org/openstack/neutron-tempest-plugin
-* Bugs: https://bugs.launchpad.net/neutron
+* Bugs: https://bugs.launchpad.net/neutron - tag: tempest
 * Release notes: https://docs.openstack.org/releasenotes/neutron-tempest-plugin/
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 2349713..88b9718 100755
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -15,7 +15,14 @@
 import os
 import sys
 
-sys.path.insert(0, os.path.abspath('../..'))
+def autodoc_skip_member_handler(app, what, name, obj, skip, options):
+    return skip or (what == "class" and not name.startswith("test"))
+
+def setup(app):
+    app.connect('autodoc-skip-member', autodoc_skip_member_handler)
+
+sys.path.insert(0, os.path.abspath('../../neutron_tempest_plugin'))
+
 # -- General configuration ----------------------------------------------------
 
 # Add any Sphinx extension module names here, as strings. They can be
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 91388a9..e69a4ec 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -15,6 +15,7 @@
    readme
    installation
    contributing
+   test_descriptions
 
 Indices and tables
 ==================
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index ddf0fd4..e014aed 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -2,11 +2,7 @@
 Installation
 ============
 
-At the command line::
+For installation and usage of neutron-tempest-plugin, please refer
+to the documentation in the Neutron tree:
 
-    $ pip install openstack
-
-Or, if you have virtualenvwrapper installed::
-
-    $ mkvirtualenv openstack
-    $ pip install openstack
+    https://docs.openstack.org/neutron/latest/contributor/testing/tempest.html
diff --git a/doc/source/test_descriptions.rst b/doc/source/test_descriptions.rst
new file mode 100644
index 0000000..240e448
--- /dev/null
+++ b/doc/source/test_descriptions.rst
@@ -0,0 +1,7 @@
+Description of Tests
+====================
+
+.. toctree::
+   :maxdepth: 2
+
+   tests/modules
diff --git a/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py b/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py
new file mode 100644
index 0000000..604dbea
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py
@@ -0,0 +1,313 @@
+# 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.
+
+import copy
+import random
+
+from neutron_lib import constants
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+RULE_KEYWORDS_TO_CHECK = [
+    'direction', 'remote_group_id', 'remote_address_group_id', 'description',
+    'protocol', 'port_range_min', 'port_range_max', 'ethertype',
+    'remote_ip_prefix', 'used_in_default_sg', 'used_in_non_default_sg'
+]
+
+
+class DefaultSecurityGroupRuleTest(base.BaseAdminNetworkTest):
+    required_extensions = ['security-groups-default-rules']
+
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    def setup_clients(cls):
+        super(DefaultSecurityGroupRuleTest, cls).setup_clients()
+        cls.admin_client = cls.os_admin.network_client
+
+    def _filter_not_relevant_rule_keys(self, rule, expected_keys=None):
+        expected_keys = expected_keys or RULE_KEYWORDS_TO_CHECK
+        new_rule = {}
+        for k in rule.keys():
+            if k in expected_keys:
+                new_rule[k] = rule[k]
+        return new_rule
+
+    def _filter_not_relevant_rules_keys(self, rules, keys=None):
+        keys = keys or RULE_KEYWORDS_TO_CHECK
+        return [self._filter_not_relevant_rule_keys(r, keys) for r in rules]
+
+    def _assert_rules_exists(self, expected_rules, actual_rules):
+        for expected_rule in expected_rules:
+            self.assertIn(expected_rule, actual_rules)
+
+    @decorators.idempotent_id('2f3d3070-e9fa-4127-a33f-f1532fd89108')
+    def test_legacy_default_sg_rules_created_by_default(self):
+        expected_legacy_template_rules = [
+            {
+                'direction': 'egress',
+                'ethertype': 'IPv4',
+                'remote_group_id': None,
+                'protocol': None,
+                'remote_ip_prefix': None,
+                'remote_address_group_id': None,
+                'port_range_max': None,
+                'port_range_min': None,
+                'used_in_default_sg': True,
+                'used_in_non_default_sg': True,
+                'description': 'Legacy default SG rule for egress traffic'
+            }, {
+                'remote_group_id': 'PARENT',
+                'direction': 'ingress',
+                'ethertype': 'IPv6',
+                'protocol': None,
+                'remote_ip_prefix': None,
+                'remote_address_group_id': None,
+                'port_range_max': None,
+                'port_range_min': None,
+                'used_in_default_sg': True,
+                'used_in_non_default_sg': False,
+                'description': 'Legacy default SG rule for ingress traffic'
+            }, {
+                'remote_group_id': 'PARENT',
+                'direction': 'ingress',
+                'ethertype': 'IPv4',
+                'protocol': None,
+                'remote_ip_prefix': None,
+                'remote_address_group_id': None,
+                'port_range_max': None,
+                'port_range_min': None,
+                'used_in_default_sg': True,
+                'used_in_non_default_sg': False,
+                'description': 'Legacy default SG rule for ingress traffic'
+            }, {
+                'direction': 'egress',
+                'ethertype': 'IPv6',
+                'remote_group_id': None,
+                'protocol': None,
+                'remote_ip_prefix': None,
+                'remote_address_group_id': None,
+                'port_range_max': None,
+                'port_range_min': None,
+                'used_in_default_sg': True,
+                'used_in_non_default_sg': True,
+                'description': 'Legacy default SG rule for egress traffic'
+            }
+        ]
+        sg_rules_template = (
+            self.admin_client.list_default_security_group_rules()[
+                'default_security_group_rules'
+            ])
+        self._assert_rules_exists(
+            expected_legacy_template_rules,
+            self._filter_not_relevant_rules_keys(sg_rules_template))
+
+    @decorators.idempotent_id('df98f969-ff2d-4597-9765-f5d4f81f775f')
+    def test_default_security_group_rule_lifecycle(self):
+        tcp_port = random.randint(constants.PORT_RANGE_MIN,
+                                  constants.PORT_RANGE_MAX)
+        rule_args = {
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': False,
+            'used_in_non_default_sg': True,
+            'description': (
+                'Allow tcp connections over IPv4 on port %s' % tcp_port)
+        }
+        expected_rule = {
+            'remote_group_id': None,
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'port_range_min': tcp_port,
+            'port_range_max': tcp_port,
+            'remote_ip_prefix': None,
+            'remote_address_group_id': None,
+            'used_in_default_sg': False,
+            'used_in_non_default_sg': True,
+            'description': (
+                'Allow tcp connections over IPv4 on port %s' % tcp_port)
+        }
+        created_rule_template = self.create_default_security_group_rule(
+            **rule_args)
+        self.assertDictEqual(
+            expected_rule,
+            self._filter_not_relevant_rule_keys(created_rule_template)
+        )
+        observed_rule_template = (
+            self.admin_client.get_default_security_group_rule(
+                created_rule_template['id'])
+        )['default_security_group_rule']
+        self.assertDictEqual(
+            expected_rule,
+            self._filter_not_relevant_rule_keys(observed_rule_template)
+        )
+
+        self.admin_client.delete_default_security_group_rule(
+            created_rule_template['id']
+        )
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.admin_client.get_default_security_group_rule,
+            created_rule_template['id']
+        )
+
+    @decorators.idempotent_id('6c5a2f41-5899-47f4-9daf-4f8ddbbd3ad5')
+    def test_create_duplicate_default_security_group_rule_different_templates(
+            self):
+        tcp_port = random.randint(constants.PORT_RANGE_MIN,
+                                  constants.PORT_RANGE_MAX)
+        rule_args = {
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': True,
+            'used_in_non_default_sg': True}
+        self.create_default_security_group_rule(**rule_args)
+
+        # Now, even if 'used_in_non_default_sg' will be different error should
+        # be returned as 'used_in_default_sg' is the same
+        new_rule_args = copy.copy(rule_args)
+        new_rule_args['used_in_non_default_sg'] = False
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.admin_client.create_default_security_group_rule,
+            **new_rule_args)
+
+        # Same in the opposite way: even if 'used_in_default_sg' will be
+        # different error should be returned as 'used_in_non_default_sg'
+        # is the same
+        new_rule_args = copy.copy(rule_args)
+        new_rule_args['used_in_default_sg'] = False
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.admin_client.create_default_security_group_rule,
+            **new_rule_args)
+
+    @decorators.idempotent_id('e4696607-1a13-48eb-8912-ee1e742d9471')
+    def test_create_same_default_security_group_rule_for_different_templates(
+            self):
+        tcp_port = random.randint(constants.PORT_RANGE_MIN,
+                                  constants.PORT_RANGE_MAX)
+        expected_rules = [{
+            'remote_group_id': None,
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'remote_ip_prefix': None,
+            'remote_address_group_id': None,
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': True,
+            'used_in_non_default_sg': False,
+            'description': ''
+        }, {
+            'remote_group_id': None,
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'remote_ip_prefix': None,
+            'remote_address_group_id': None,
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': False,
+            'used_in_non_default_sg': True,
+            'description': ''
+        }]
+
+        default_sg_rule_args = {
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': True,
+            'used_in_non_default_sg': False}
+        self.create_default_security_group_rule(**default_sg_rule_args)
+
+        custom_sg_rule_args = {
+            'direction': 'ingress',
+            'ethertype': 'IPv4',
+            'protocol': 'tcp',
+            'port_range_max': tcp_port,
+            'port_range_min': tcp_port,
+            'used_in_default_sg': False,
+            'used_in_non_default_sg': True}
+        self.create_default_security_group_rule(**custom_sg_rule_args)
+
+        sg_rules_template = (
+            self.admin_client.list_default_security_group_rules()[
+                'default_security_group_rules'
+            ])
+        self._assert_rules_exists(
+            expected_rules,
+            self._filter_not_relevant_rules_keys(sg_rules_template))
+
+    def _validate_security_group_rules(self, sg, is_default_sg):
+        keys_to_check = [
+            'remote_group_id', 'direction', 'ethertype', 'protocol',
+            'remote_ip_prefix', 'remote_address_group_id', 'port_range_min',
+            'port_range_max']
+
+        if is_default_sg:
+            sg_rules_template = (
+                self.admin_client.list_default_security_group_rules(
+                    used_in_default_sg=True)['default_security_group_rules'])
+        else:
+            sg_rules_template = (
+                self.admin_client.list_default_security_group_rules(
+                    used_in_non_default_sg=True
+                )['default_security_group_rules'])
+        # NOTE(slaweq): We need to replace "PARENT" keyword in
+        # the "remote_group_id" attribute of every default sg rule template
+        # with actual SG ID
+        for rule in sg_rules_template:
+            if rule['remote_group_id'] == 'PARENT':
+                rule['remote_group_id'] = sg['id']
+
+        self._assert_rules_exists(
+            self._filter_not_relevant_rules_keys(
+                sg_rules_template, keys_to_check),
+            self._filter_not_relevant_rules_keys(
+                sg['security_group_rules'], keys_to_check))
+
+    @decorators.idempotent_id('29feedb1-6f04-4a1f-a778-2fae2c7b7dc8')
+    def test_security_group_rules_created_from_default_sg_rules_template(
+            self):
+        """Test if default SG and custom new SG have got proper SG rules.
+
+        This test creates new project and checks if its default SG has SG
+        rules matching default SG rules for that kind of SG.
+        Next it creates new SG for the same project and checks if that SG also
+        have proper SG rules based on the default SG rules template.
+        """
+
+        project = self.create_project()
+        # First check rules for default SG created automatically for each
+        # project
+        default_sg = self.admin_client.list_security_groups(
+            tenant_id=project['id'], name='default')['security_groups'][0]
+        self._validate_security_group_rules(default_sg, is_default_sg=True)
+
+        # And now create different SG for same project and check SG rules for
+        # such additional SG
+        sg = self.create_security_group(project=project)
+        self._validate_security_group_rules(sg, is_default_sg=False)
diff --git a/neutron_tempest_plugin/api/admin/test_logging.py b/neutron_tempest_plugin/api/admin/test_logging.py
index b76377d..dda31b4 100644
--- a/neutron_tempest_plugin/api/admin/test_logging.py
+++ b/neutron_tempest_plugin/api/admin/test_logging.py
@@ -15,12 +15,9 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
-import testscenarios
 
 from neutron_tempest_plugin.api import base
 
-load_tests = testscenarios.load_tests_apply_scenarios
-
 
 class LoggingTestJSON(base.BaseAdminNetworkTest):
 
diff --git a/neutron_tempest_plugin/api/admin/test_routers_dvr.py b/neutron_tempest_plugin/api/admin/test_routers_dvr.py
deleted file mode 100644
index ab25a3f..0000000
--- a/neutron_tempest_plugin/api/admin/test_routers_dvr.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright 2015 OpenStack Foundation
-# 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 tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.api import base_routers as base
-
-
-class RoutersTestDVRBase(base.BaseRouterTest):
-
-    required_extensions = ['router', 'dvr']
-
-    @classmethod
-    def resource_setup(cls):
-        # The check above will pass if api_extensions=all, which does
-        # not mean DVR extension itself is present.
-        # Instead, we have to check whether DVR is actually present by using
-        # admin credentials to create router with distributed=True attribute
-        # and checking for BadRequest exception and that the resulting router
-        # has a distributed attribute.
-        super(RoutersTestDVRBase, cls).resource_setup()
-        name = data_utils.rand_name('pretest-check')
-        router = cls.admin_client.create_router(name)
-        if 'distributed' not in router['router']:
-            msg = "'distributed' attribute not found. DVR Possibly not enabled"
-            raise cls.skipException(msg)
-        cls.admin_client.delete_router(router['router']['id'])
-
-
-class RoutersTestDVR(RoutersTestDVRBase):
-
-    @decorators.idempotent_id('08a2a0a8-f1e4-4b34-8e30-e522e836c44e')
-    def test_distributed_router_creation(self):
-        """Test distributed router creation
-
-        Test uses administrative credentials to creates a
-        DVR (Distributed Virtual Routing) router using the
-        distributed=True.
-
-        Acceptance
-        The router is created and the "distributed" attribute is
-        set to True
-        """
-        name = data_utils.rand_name('router')
-        router = self._create_admin_router(name, distributed=True)
-        self.assertTrue(router['distributed'])
-
-    @decorators.idempotent_id('8a0a72b4-7290-4677-afeb-b4ffe37bc352')
-    def test_centralized_router_creation(self):
-        """Test centralized router creation
-
-        Test uses administrative credentials to creates a
-        CVR (Centralized Virtual Routing) router using the
-        distributed=False.
-
-        Acceptance
-        The router is created and the "distributed" attribute is
-        set to False, thus making it a "Centralized Virtual Router"
-        as opposed to a "Distributed Virtual Router"
-        """
-        name = data_utils.rand_name('router')
-        router = self._create_admin_router(name, distributed=False)
-        self.assertFalse(router['distributed'])
-
-
-class RouterTestCentralizedToDVR(RoutersTestDVRBase):
-
-    required_extensions = ['l3-ha']
-
-    @decorators.idempotent_id('acd43596-c1fb-439d-ada8-31ad48ae3c2e')
-    def test_centralized_router_update_to_dvr(self):
-        """Test centralized to DVR router update
-
-        Test uses administrative credentials to creates a
-        CVR (Centralized Virtual Routing) router using the
-        distributed=False.Then it will "update" the router
-        distributed attribute to True
-
-        Acceptance
-        The router is created and the "distributed" attribute is
-        set to False. Once the router is updated, the distributed
-        attribute will be set to True
-        """
-        name = data_utils.rand_name('router')
-        # router needs to be in admin state down in order to be upgraded to DVR
-        router = self._create_admin_router(name, distributed=False,
-                                           ha=False, admin_state_up=False)
-        self.assertFalse(router['distributed'])
-        self.assertFalse(router['ha'])
-        router = self.admin_client.update_router(router['id'],
-                                                 distributed=True)
-        self.assertTrue(router['router']['distributed'])
diff --git a/neutron_tempest_plugin/api/admin/test_routers_ha.py b/neutron_tempest_plugin/api/admin/test_routers_ha.py
index b717a10..2b18728 100644
--- a/neutron_tempest_plugin/api/admin/test_routers_ha.py
+++ b/neutron_tempest_plugin/api/admin/test_routers_ha.py
@@ -64,6 +64,9 @@
         set to False, thus making it a "Single Failure Router"
         as opposed to a "High Availability Router"
         """
+        # OVN driver doesn't accept ha=False (all OVN routers support HA)
+        if self.is_driver_ovn:
+            raise self.skipException("Test not meant for OVN driver")
         name = data_utils.rand_name('router')
         router = self._create_admin_router(name, ha=False)
         self.assertFalse(router['ha'])
@@ -81,6 +84,9 @@
         set to False. Once the router is updated, the ha
         attribute will be set to True
         """
+        # OVN driver doesn't accept ha=False (all OVN routers support HA)
+        if self.is_driver_ovn:
+            raise self.skipException("Test not meant for OVN driver")
         name = data_utils.rand_name('router')
         # router needs to be in admin state down in order to be upgraded to HA
         router = self._create_admin_router(name, ha=False,
@@ -101,6 +107,10 @@
         deleted, those segmentation data are kept in HA network. This tests
         regression of https://bugs.launchpad.net/neutron/+bug/1732543.
         """
+        # Test not meant for OVN implementation of HA
+        if self.is_driver_ovn:
+            raise self.skipException(
+                "Test not meant for OVN implementation of HA")
         for i in range(2):
             router = self._create_admin_router(
                 data_utils.rand_name('router%d' % i),
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index b66fe0d..b659637 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -68,6 +68,21 @@
 
     external_network_id = CONF.network.public_network_id
 
+    __is_driver_ovn = None
+
+    @classmethod
+    def _is_driver_ovn(cls):
+        ovn_agents = cls.os_admin.network_client.list_agents(
+            binary='ovn-controller')['agents']
+        return len(ovn_agents) > 0
+
+    @property
+    def is_driver_ovn(self):
+        if self.__is_driver_ovn is None:
+            if hasattr(self, 'os_admin'):
+                self.__is_driver_ovn = self._is_driver_ovn()
+        return self.__is_driver_ovn
+
     @classmethod
     def get_client_manager(cls, credential_type=None, roles=None,
                            force_new=None):
@@ -135,6 +150,7 @@
         cls.admin_subnetpools = []
         cls.security_groups = []
         cls.admin_security_groups = []
+        cls.sg_rule_templates = []
         cls.projects = []
         cls.log_objects = []
         cls.reserved_subnet_cidrs = set()
@@ -243,6 +259,12 @@
                                          security_group,
                                          client=cls.admin_client)
 
+            # Clean up security group rule templates
+            for sg_rule_template in cls.sg_rule_templates:
+                cls._try_delete_resource(
+                    cls.admin_client.delete_default_security_group_rule,
+                    sg_rule_template['id'])
+
             for subnetpool in cls.subnetpools:
                 cls._try_delete_resource(cls.client.delete_subnetpool,
                                          subnetpool['id'])
@@ -409,7 +431,7 @@
     @classmethod
     def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
                       ip_version=None, client=None, reserve_cidr=True,
-                      **kwargs):
+                      allocation_pool_size=None, **kwargs):
         """Wrapper utility that returns a test subnet.
 
         Convenient wrapper for client.create_subnet method. It reserves and
@@ -447,10 +469,19 @@
         using the same CIDR for further subnets in the scope of the same
         test case class
 
+        :param allocation_pool_size: if the CIDR is not defined, this method
+        will assign one in ``get_subnet_cidrs``. Once done, the allocation pool
+        will be defined reserving the number of IP addresses requested,
+        starting from the end of the assigned CIDR.
+
         :param **kwargs: optional parameters to be forwarded to wrapped method
 
         [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets  # noqa
         """
+        def allocation_pool(cidr, pool_size):
+            start = str(netaddr.IPAddress(cidr.last) - pool_size)
+            end = str(netaddr.IPAddress(cidr.last) - 1)
+            return {'start': start, 'end': end}
 
         # allow tests to use admin client
         if not client:
@@ -473,6 +504,9 @@
                 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
             else:
                 kwargs['gateway_ip'] = None
+            if allocation_pool_size:
+                kwargs['allocation_pools'] = [
+                    allocation_pool(subnet_cidr, allocation_pool_size)]
             try:
                 body = client.create_subnet(
                     network_id=network['id'],
@@ -971,6 +1005,15 @@
         client.delete_security_group(security_group['id'])
 
     @classmethod
+    def get_security_group(cls, name='default', client=None):
+        client = client or cls.client
+        security_groups = client.list_security_groups()['security_groups']
+        for security_group in security_groups:
+            if security_group['name'] == name:
+                return security_group
+        raise ValueError("No such security group named {!r}".format(name))
+
+    @classmethod
     def create_security_group_rule(cls, security_group=None, project=None,
                                    client=None, ip_version=None, **kwargs):
         if project:
@@ -1006,13 +1049,11 @@
             'security_group_rule']
 
     @classmethod
-    def get_security_group(cls, name='default', client=None):
-        client = client or cls.client
-        security_groups = client.list_security_groups()['security_groups']
-        for security_group in security_groups:
-            if security_group['name'] == name:
-                return security_group
-        raise ValueError("No such security group named {!r}".format(name))
+    def create_default_security_group_rule(cls, **kwargs):
+        body = cls.admin_client.create_default_security_group_rule(**kwargs)
+        default_sg_rule = body['default_security_group_rule']
+        cls.sg_rule_templates.append(default_sg_rule)
+        return default_sg_rule
 
     @classmethod
     def create_keypair(cls, client=None, name=None, **kwargs):
@@ -1423,7 +1464,9 @@
         self.assertNotEmpty(
             resources, "%s list returned is empty" % self.resource)
         retrieved_names = [res[self.field] for res in resources]
-        expected = sorted(retrieved_names)
+        # sort without taking into account whether the network is named with
+        # a capital letter or not
+        expected = sorted(retrieved_names, key=lambda v: v.upper())
         if direction == constants.SORT_DIRECTION_DESC:
             expected = list(reversed(expected))
         self.assertEqual(expected, retrieved_names)
diff --git a/neutron_tempest_plugin/api/test_dhcp_ipv6.py b/neutron_tempest_plugin/api/test_dhcp_ipv6.py
deleted file mode 100644
index 4f2e576..0000000
--- a/neutron_tempest_plugin/api/test_dhcp_ipv6.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# 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.
-
-import netaddr
-from neutron_lib import constants
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-
-from neutron_tempest_plugin.api import base
-from neutron_tempest_plugin import config
-
-CONF = config.CONF
-
-
-class NetworksTestDHCPv6(base.BaseNetworkTest):
-    _ip_version = 6
-
-    def setUp(self):
-        super(NetworksTestDHCPv6, self).setUp()
-        self.addCleanup(self._clean_network)
-
-    @classmethod
-    def skip_checks(cls):
-        super(NetworksTestDHCPv6, cls).skip_checks()
-        msg = None
-        if not CONF.network_feature_enabled.ipv6:
-            msg = "IPv6 is not enabled"
-        elif not CONF.network_feature_enabled.ipv6_subnet_attributes:
-            msg = "DHCPv6 attributes are not enabled."
-        if msg:
-            raise cls.skipException(msg)
-
-    @classmethod
-    def resource_setup(cls):
-        super(NetworksTestDHCPv6, cls).resource_setup()
-        cls.network = cls.create_network()
-
-    def _remove_from_list_by_index(self, things_list, elem):
-        for index, i in enumerate(things_list):
-            if i['id'] == elem['id']:
-                del things_list[index]
-                return
-
-    def _clean_network(self):
-        body = self.client.list_ports()
-        ports = body['ports']
-        for port in ports:
-            if (port['device_owner'].startswith(
-                    constants.DEVICE_OWNER_ROUTER_INTF) and
-                port['device_id'] in [r['id'] for r in self.routers]):
-                self.client.remove_router_interface_with_port_id(
-                    port['device_id'], port['id']
-                )
-            else:
-                if port['id'] in [p['id'] for p in self.ports]:
-                    self.client.delete_port(port['id'])
-                    self._remove_from_list_by_index(self.ports, port)
-        body = self.client.list_subnets()
-        subnets = body['subnets']
-        for subnet in subnets:
-            if subnet['id'] in [s['id'] for s in self.subnets]:
-                self.client.delete_subnet(subnet['id'])
-                self._remove_from_list_by_index(self.subnets, subnet)
-        body = self.client.list_routers()
-        routers = body['routers']
-        for router in routers:
-            if router['id'] in [r['id'] for r in self.routers]:
-                self.client.delete_router(router['id'])
-                self._remove_from_list_by_index(self.routers, router)
-
-    @decorators.idempotent_id('98244d88-d990-4570-91d4-6b25d70d08af')
-    def test_dhcp_stateful_fixedips_outrange(self):
-        """Test DHCP Stateful fixed IPs out of range
-
-        When port gets IP address from fixed IP range it
-        shall be checked if it's from subnets range.
-        """
-        kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
-                  'ipv6_address_mode': 'dhcpv6-stateful'}
-        subnet = self.create_subnet(self.network, **kwargs)
-        ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"],
-                                   subnet["allocation_pools"][0]["end"])
-        for i in range(1, 3):
-            ip = netaddr.IPAddress(ip_range.last + i).format()
-            self.assertRaises(lib_exc.BadRequest,
-                              self.create_port,
-                              self.network,
-                              fixed_ips=[{'subnet_id': subnet['id'],
-                                          'ip_address': ip}])
diff --git a/neutron_tempest_plugin/api/test_extra_dhcp_options.py b/neutron_tempest_plugin/api/test_extra_dhcp_options.py
deleted file mode 100644
index 91c270d..0000000
--- a/neutron_tempest_plugin/api/test_extra_dhcp_options.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2013 OpenStack Foundation
-# 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 neutron_lib import constants
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.api import base
-
-
-class ExtraDHCPOptionsTestJSON(base.BaseNetworkTest):
-    """Test Extra DHCP Options
-
-    Tests the following operations with the Extra DHCP Options Neutron API
-    extension:
-
-        port create
-        port list
-        port show
-        port update
-
-    v2.0 of the Neutron API is assumed. It is also assumed that the Extra
-    DHCP Options extension is enabled in the [network-feature-enabled]
-    section of etc/tempest.conf
-    """
-
-    required_extensions = ['extra_dhcp_opt']
-
-    @classmethod
-    def resource_setup(cls):
-        super(ExtraDHCPOptionsTestJSON, cls).resource_setup()
-        cls.network = cls.create_network()
-        cls.subnet = cls.create_subnet(cls.network)
-        cls.port = cls.create_port(cls.network)
-        cls.ip_tftp = ('123.123.123.123' if cls._ip_version == 4
-                       else '2015::dead')
-        cls.ip_server = ('123.123.123.45' if cls._ip_version == 4
-                         else '2015::badd')
-        cls.extra_dhcp_opts = [
-            {'opt_value': 'pxelinux.0',
-             'opt_name': 'bootfile-name'},  # default ip_version is 4
-            {'opt_value': cls.ip_tftp,
-             'opt_name': 'tftp-server',
-             'ip_version': cls._ip_version},
-            {'opt_value': cls.ip_server,
-             'opt_name': 'server-ip-address',
-             'ip_version': cls._ip_version}
-        ]
-
-    @decorators.idempotent_id('d2c17063-3767-4a24-be4f-a23dbfa133c9')
-    def test_create_list_port_with_extra_dhcp_options(self):
-        # Create a port with Extra DHCP Options
-        body = self.create_port(
-            self.network,
-            extra_dhcp_opts=self.extra_dhcp_opts)
-        port_id = body['id']
-
-        # Confirm port created has Extra DHCP Options
-        body = self.client.list_ports()
-        ports = body['ports']
-        port = [p for p in ports if p['id'] == port_id]
-        self.assertTrue(port)
-        self._confirm_extra_dhcp_options(port[0], self.extra_dhcp_opts)
-
-    @decorators.idempotent_id('9a6aebf4-86ee-4f47-b07a-7f7232c55607')
-    def test_update_show_port_with_extra_dhcp_options(self):
-        # Update port with extra dhcp options
-        name = data_utils.rand_name('new-port-name')
-        body = self.client.update_port(
-            self.port['id'],
-            name=name,
-            extra_dhcp_opts=self.extra_dhcp_opts)
-        # Confirm extra dhcp options were added to the port
-        body = self.client.show_port(self.port['id'])
-        self._confirm_extra_dhcp_options(body['port'], self.extra_dhcp_opts)
-
-    def _confirm_extra_dhcp_options(self, port, extra_dhcp_opts):
-        retrieved = port['extra_dhcp_opts']
-        self.assertEqual(len(retrieved), len(extra_dhcp_opts))
-        for retrieved_option in retrieved:
-            for option in extra_dhcp_opts:
-                # default ip_version is 4
-                ip_version = option.get('ip_version', constants.IP_VERSION_4)
-                if (retrieved_option['opt_value'] == option['opt_value'] and
-                    retrieved_option['opt_name'] == option['opt_name'] and
-                    retrieved_option['ip_version'] == ip_version):
-                    break
-            else:
-                self.fail('Extra DHCP option not found in port %s' %
-                          str(retrieved_option))
-
-
-class ExtraDHCPOptionsIpV6TestJSON(ExtraDHCPOptionsTestJSON):
-    _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability.py b/neutron_tempest_plugin/api/test_network_ip_availability.py
index 22d2fc6..cb2820b 100644
--- a/neutron_tempest_plugin/api/test_network_ip_availability.py
+++ b/neutron_tempest_plugin/api/test_network_ip_availability.py
@@ -176,11 +176,6 @@
 
     _ip_version = lib_constants.IP_VERSION_6
 
-    def setUp(self):
-        super(NetworksIpAvailabilityIPv6Test, self).setUp()
-        net_name = data_utils.rand_name('network')
-        self.network = self.create_network(network_name=net_name)
-
     @decorators.idempotent_id('0d5a03f2-fdb7-4ec3-b746-734c51d74b69')
     def test_list_ipv6_ip_availability_after_subnet_and_ports(self):
         subnet = self.create_subnet(self.network, ip_version=self._ip_version,
diff --git a/neutron_tempest_plugin/api/test_ports.py b/neutron_tempest_plugin/api/test_ports.py
index 8d5772b..ef6d4fa 100644
--- a/neutron_tempest_plugin/api/test_ports.py
+++ b/neutron_tempest_plugin/api/test_ports.py
@@ -20,9 +20,10 @@
 from tempest.lib import decorators
 
 from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.common import utils as plugin_utils
 
 
-class PortsTestJSON(base.BaseNetworkTest):
+class PortsTestJSON(base.BaseAdminNetworkTest):
 
     @classmethod
     def resource_setup(cls):
@@ -151,6 +152,45 @@
         expected = [s['id'], s['id']]
         self.assertEqual(expected, subnets)
 
+    @decorators.idempotent_id('12a3f5f1-4d9c-4fe1-9e7b-08c75fe77790')
+    def test_port_shut_down(self):
+        # NOTE(slaweq): In some cases (like default ML2/OVN deployment)
+        # extension is enabled but there may not be any DHCP agent available.
+        # In such case those tests should be also skipped.
+        dhcp_agents = self.admin_client.list_agents(
+            agent_type="DHCP Agent")['agents']
+        if not dhcp_agents:
+            msg = ("At least one DHCP agent is required to be running in "
+                   "the environment for this test.")
+            raise self.skipException(msg)
+
+        self.create_subnet(self.network)
+
+        def dhcp_port_created():
+            return self.client.list_ports(
+                network_id=self.network['id'],
+                device_owner=lib_constants.DEVICE_OWNER_DHCP)['ports']
+
+        plugin_utils.wait_until_true(dhcp_port_created)
+
+        dhcp_port = self.client.list_ports(
+            network_id=self.network['id'],
+            device_owner=lib_constants.DEVICE_OWNER_DHCP)['ports'][0]
+
+        def port_active():
+            port = self.client.show_port(dhcp_port['id'])['port']
+            return port['status'] == lib_constants.PORT_STATUS_ACTIVE
+
+        plugin_utils.wait_until_true(port_active)
+
+        self.client.update_port(dhcp_port['id'], admin_state_up=False)
+
+        def port_down():
+            port = self.client.show_port(dhcp_port['id'])['port']
+            return port['status'] == lib_constants.PORT_STATUS_DOWN
+
+        plugin_utils.wait_until_true(port_down)
+
 
 class PortsIpv6TestJSON(base.BaseNetworkTest):
 
diff --git a/neutron_tempest_plugin/api/test_qos.py b/neutron_tempest_plugin/api/test_qos.py
index 448f391..f1867d1 100644
--- a/neutron_tempest_plugin/api/test_qos.py
+++ b/neutron_tempest_plugin/api/test_qos.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+
+import ddt
 from neutron_lib.api.definitions import qos as qos_apidef
 from neutron_lib import constants as n_constants
 from neutron_lib.services.qos import constants as qos_consts
@@ -20,14 +22,11 @@
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
-
-import testscenarios
 import testtools
 
 from neutron_tempest_plugin.api import base
 from neutron_tempest_plugin import config
 
-load_tests = testscenarios.load_tests_apply_scenarios
 CONF = config.CONF
 
 
@@ -484,15 +483,6 @@
             policy_id, rule['id'])
         return rule
 
-    @property
-    def opposite_direction(self):
-        if self.direction == "ingress":
-            return "egress"
-        elif self.direction == "egress":
-            return "ingress"
-        else:
-            return None
-
     @decorators.idempotent_id('8a59b00b-3e9c-4787-92f8-93a5cdf5e378')
     def test_rule_create(self):
         policy = self.create_qos_policy(name=self.policy_name,
@@ -544,17 +534,20 @@
 
     @decorators.idempotent_id('149a6988-2568-47d2-931e-2dbc858943b3')
     def test_rule_update(self):
+        self._test_rule_update(opposite_direction=None)
+
+    def _test_rule_update(self, opposite_direction=None):
         policy = self.create_qos_policy(name=self.policy_name,
                                         description='test policy',
                                         shared=False)
         rule = self._create_qos_bw_limit_rule(
             policy['id'], {'max_kbps': 1, 'max_burst_kbps': 1})
 
-        if self.opposite_direction:
+        if opposite_direction:
             self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
                 policy['id'], rule['id'],
                 **{'max_kbps': 200, 'max_burst_kbps': 1337,
-                   'direction': self.opposite_direction})
+                   'direction': opposite_direction})
         else:
             self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
                 policy['id'], rule['id'],
@@ -564,8 +557,8 @@
         retrieved_policy = retrieved_policy['bandwidth_limit_rule']
         self.assertEqual(200, retrieved_policy['max_kbps'])
         self.assertEqual(1337, retrieved_policy['max_burst_kbps'])
-        if self.opposite_direction:
-            self.assertEqual(self.opposite_direction,
+        if opposite_direction:
+            self.assertEqual(opposite_direction,
                              retrieved_policy['direction'])
 
     @decorators.idempotent_id('67ee6efd-7b33-4a68-927d-275b4f8ba958')
@@ -697,16 +690,13 @@
             policy['id'])
 
 
+@ddt.ddt
 class QosBandwidthLimitRuleWithDirectionTestJSON(
         QosBandwidthLimitRuleTestJSON):
     required_extensions = (
         QosBandwidthLimitRuleTestJSON.required_extensions +
         ['qos-bw-limit-direction']
     )
-    scenarios = [
-        ('ingress', {'direction': 'ingress'}),
-        ('egress', {'direction': 'egress'}),
-    ]
 
     @classmethod
     @base.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
@@ -753,6 +743,13 @@
             {'max_kbps': 1025, 'max_burst_kbps': 1025,
              'direction': n_constants.INGRESS_DIRECTION})
 
+    @decorators.idempotent_id('7ca7c83b-0555-4a0f-a422-4338f77f79e5')
+    @ddt.unpack
+    @ddt.data({'opposite_direction': 'ingress'},
+              {'opposite_direction': 'egress'})
+    def test_rule_update(self, opposite_direction):
+        self._test_rule_update(opposite_direction=opposite_direction)
+
 
 class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest):
 
diff --git a/neutron_tempest_plugin/api/test_revisions.py b/neutron_tempest_plugin/api/test_revisions.py
index 09bb3f1..9034cc3 100644
--- a/neutron_tempest_plugin/api/test_revisions.py
+++ b/neutron_tempest_plugin/api/test_revisions.py
@@ -342,6 +342,9 @@
     @utils.requires_ext(extension="router", service="network")
     @utils.requires_ext(extension="l3-ha", service="network")
     def test_update_router_extra_attributes_bumps_revision(self):
+        # OVN driver doesn't accept ha=False (all OVN routers support HA)
+        if self.is_driver_ovn:
+            raise self.skipException("Test not meant for OVN driver")
         # updates from CVR to CVR-HA are supported on every release,
         # but only the admin can forcibly create a non-HA router
         router_args = {'tenant_id': self.client.project_id,
diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py
index 4179e6d..ff5d391 100644
--- a/neutron_tempest_plugin/api/test_routers.py
+++ b/neutron_tempest_plugin/api/test_routers.py
@@ -293,6 +293,38 @@
         self.assertEqual(port_show1['device_id'], router1['id'])
         self.assertEqual(port_show2['device_id'], router2['id'])
 
+    @decorators.idempotent_id('4f8a2a1e-7fe9-4d99-9bff-5dc0e78b7e06')
+    def test_router_interface_update_and_remove_gateway_ip(self):
+        network = self.create_network()
+        subnet = self.create_subnet(network, allocation_pool_size=5)
+
+        # Update the subnet gateway IP, using the next one. Because the
+        # allocation pool is on the upper part of the CIDR, the lower IP
+        # addresses are free. This operation must be allowed because the subnet
+        # does not have yet a router port.
+        gateway_ip = netaddr.IPAddress(subnet['gateway_ip'])
+        self.client.update_subnet(subnet['id'], gateway_ip=str(gateway_ip + 1))
+
+        router = self._create_router(data_utils.rand_name('router'), True)
+        intf = self.create_router_interface(router['id'], subnet['id'])
+
+        def _status_active():
+            return self.client.show_port(
+                intf['port_id'])['port']['status'] == 'ACTIVE'
+
+        utils.wait_until_true(_status_active, exception=AssertionError)
+
+        # The gateway update must raise a ``GatewayIpInUse`` exception because
+        # there is an allocated router port.
+        gateway_ip = netaddr.IPAddress(subnet['gateway_ip'])
+        self.assertRaises(lib_exc.Conflict, self.client.update_subnet,
+                          subnet['id'], gateway_ip=str(gateway_ip + 2))
+
+        # The gateway deletion returns the same exception.
+        gateway_ip = netaddr.IPAddress(subnet['gateway_ip'])
+        self.assertRaises(lib_exc.Conflict, self.client.update_subnet,
+                          subnet['id'], gateway_ip=None)
+
 
 class RoutersIpV6Test(RoutersTest):
     _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index 14e0c66..5e8b18e 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -245,6 +245,14 @@
 
 class BaseSecGroupQuota(base.BaseAdminNetworkTest):
 
+    def setUp(self):
+        super().setUp()
+        # NOTE(slaweq): we don't know exactly how many rule templates may be
+        # created in the neutron db and used for every SG so, as in this test
+        # class we are checking quotas of SG, not SG rules, lets set quota for
+        # SG rules to -1
+        self._set_sg_rules_quota(-1)
+
     def _create_max_allowed_sg_amount(self):
         sg_amount = self._get_sg_amount()
         sg_quota = self._get_sg_quota()
@@ -270,17 +278,23 @@
         self.assertEqual(self._get_sg_quota(), new_sg_quota,
                          "Security group quota wasn't changed correctly")
 
-    def _set_sg_quota(self, val):
-        sg_quota = self._get_sg_quota()
+    def _set_quota(self, val, resource):
+        res_quota = self._get_quota(resource)
         project_id = self.client.project_id
-        self.admin_client.update_quotas(project_id, **{'security_group': val})
+        self.admin_client.update_quotas(project_id, **{resource: val})
         self.addCleanup(self.admin_client.update_quotas,
-                        project_id, **{'security_group': sg_quota})
+                        project_id, **{resource: res_quota})
 
-    def _get_sg_quota(self):
+    def _get_quota(self, resource):
         project_id = self.client.project_id
         quotas = self.admin_client.show_quotas(project_id)
-        return quotas['quota']['security_group']
+        return quotas['quota'][resource]
+
+    def _set_sg_quota(self, val):
+        return self._set_quota(val, 'security_group')
+
+    def _get_sg_quota(self):
+        return self._get_quota('security_group')
 
     def _get_sg_amount(self):
         project_id = self.client.project_id
@@ -288,6 +302,9 @@
         security_groups = self.client.list_security_groups(**filter_query)
         return len(security_groups['security_groups'])
 
+    def _set_sg_rules_quota(self, val):
+        return self._set_quota(val, 'security_group_rule')
+
 
 class SecGroupQuotaTest(BaseSecGroupQuota):
 
@@ -723,3 +740,25 @@
     @decorators.idempotent_id('5c78bd57-e6e9-4e71-a05c-9c4082a3f139')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
+
+
+class SecGroupNormalizedCidrTest(base.BaseNetworkTest):
+
+    required_extensions = ['security-group', 'security-groups-normalized-cidr']
+
+    @decorators.idempotent_id('f87bb108-205c-4f06-a378-81a26f71b829')
+    def test_normalized_cidr_in_rule(self):
+        security_group = self.create_security_group()
+        rule = self.create_security_group_rule(
+            security_group=security_group,
+            direction=constants.INGRESS_DIRECTION,
+            remote_ip_prefix='10.0.0.34/24')
+        self.assertEqual('10.0.0.0/24', rule['normalized_cidr'])
+        self.assertEqual('10.0.0.34/24', rule['remote_ip_prefix'])
+
+        rule = self.create_security_group_rule(
+            security_group=security_group,
+            remote_group_id=security_group['id'],
+            direction=constants.INGRESS_DIRECTION)
+        self.assertIsNone(rule['normalized_cidr'])
+        self.assertIsNone(rule['remote_ip_prefix'])
diff --git a/neutron_tempest_plugin/common/shell.py b/neutron_tempest_plugin/common/shell.py
index eebb07d..073bf55 100644
--- a/neutron_tempest_plugin/common/shell.py
+++ b/neutron_tempest_plugin/common/shell.py
@@ -47,18 +47,18 @@
     :param timeout: command execution timeout in seconds
 
     :param check: when False it doesn't raises ShellCommandFailed when
-    exit status is not zero. True by default
+       exit status is not zero. True by default
 
     :returns: STDOUT text when command execution terminates with zero exit
-    status.
+       status.
 
     :raises ShellTimeoutExpired: when timeout expires before command execution
-    terminates. In such case it kills the process, then it eventually would
-    try to read STDOUT and STDERR buffers (not fully implemented) before
-    raising the exception.
+       terminates. In such case it kills the process, then it eventually would
+       try to read STDOUT and STDERR buffers (not fully implemented) before
+       raising the exception.
 
     :raises ShellCommandFailed: when command execution terminates with non-zero
-    exit status.
+       exit status.
     """
     ssh_client = ssh_client or SSH_PROXY_CLIENT
     if timeout:
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
index 4cb1474..700d21b 100644
--- a/neutron_tempest_plugin/common/ssh.py
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -133,7 +133,7 @@
         :returns: paramiko.Client connected to remote server.
 
         :raises tempest.lib.exceptions.SSHTimeout: in case it fails to connect
-        to remote server.
+           to remote server.
         """
         return super(Client, self)._get_ssh_connection(*args, **kwargs)
 
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
index ab99db9..c62aa78 100644
--- a/neutron_tempest_plugin/common/utils.py
+++ b/neutron_tempest_plugin/common/utils.py
@@ -73,7 +73,7 @@
     """Wait until callable predicate is evaluated as True
 
     :param predicate: Callable deciding whether waiting should continue.
-    Best practice is to instantiate predicate with functools.partial()
+       Best practice is to instantiate predicate with functools.partial()
     :param timeout: Timeout in seconds how long should function wait.
     :param sleep: Polling interval for results in seconds.
     :param exception: Exception instance to raise on timeout. If None is passed
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index 4fad1fa..a6d5c09 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -242,6 +242,17 @@
 CONF.register_group(taas_group)
 CONF.register_opts(TaasGroup, group="taas")
 
+# DNS Integration with an External Service
+DnsFeatureGroup = [
+    cfg.IntOpt(
+        'segmentation_id', default=12345,
+        help="For network types VLAN, GRE, VXLAN or GENEVE, the segmentation"
+             " ID must be outside the ranges assigned to project networks."),
+]
+dns_feature_group = cfg.OptGroup(
+    name='designate_feature_enabled', title='Enabled Designate Features')
+CONF.register_group(dns_feature_group)
+CONF.register_opts(DnsFeatureGroup, group="designate_feature_enabled")
 
 config_opts_translator = {
     'project_network_cidr': 'tenant_network_cidr',
diff --git a/neutron_tempest_plugin/fwaas/scenario/test_fwaas_v2.py b/neutron_tempest_plugin/fwaas/scenario/test_fwaas_v2.py
index 4d5fdac..9896073 100644
--- a/neutron_tempest_plugin/fwaas/scenario/test_fwaas_v2.py
+++ b/neutron_tempest_plugin/fwaas/scenario/test_fwaas_v2.py
@@ -14,8 +14,6 @@
 #    under the License.
 
 
-import testscenarios
-
 from oslo_log import log as logging
 from tempest.common import utils
 from tempest import config
@@ -28,7 +26,6 @@
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
-load_tests = testscenarios.load_tests_apply_scenarios
 
 
 class TestFWaaS_v2(base.FWaaSScenarioTest_V2):
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
index fe0f3fc..a7bc12a 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
@@ -68,24 +68,26 @@
 class Bgp(BgpClientMixin, base.BaseTempestTestCase):
     """Test the following topology
 
-          +-------------------+
-          | public            |
-          | network           |
-          |                   |
-          +-+---------------+-+
-            |               |
-            |               |
-    +-------+-+           +-+-------+
-    | LEFT    |           | RIGHT   |
-    | router  | <--BGP--> | router  |
-    |         |           |         |
-    +----+----+           +----+----+
-         |                     |
-    +----+----+           +----+----+
-    | LEFT    |           | RIGHT   |
-    | network |           | network |
-    |         |           |         |
-    +---------+           +---------+
+    .. code-block:: HTML
+
+              +-------------------+
+              | public            |
+              | network           |
+              |                   |
+              +-+---------------+-+
+                |               |
+                |               |
+        +-------+-+           +-+-------+
+        | LEFT    |           | RIGHT   |
+        | router  | <--BGP--> | router  |
+        |         |           |         |
+        +----+----+           +----+----+
+             |                     |
+        +----+----+           +----+----+
+        | LEFT    |           | RIGHT   |
+        | network |           | network |
+        |         |           |         |
+        +---------+           +---------+
     """
 
     credentials = ['primary', 'admin']
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 9d53f79..72139de 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -236,7 +236,7 @@
     @classmethod
     def _wait_for_router_ha_active(cls, router_id):
         router = cls.os_admin.network_client.show_router(router_id)['router']
-        if not router.get('ha'):
+        if not router.get('ha') or cls.is_driver_ovn:
             return
 
         def _router_active_on_l3_agent():
@@ -285,7 +285,8 @@
         client.delete_interface(server_id, port_id=port_id)
 
     def setup_network_and_server(self, router=None, server_name=None,
-                                 network=None, **kwargs):
+                                 network=None, use_stateless_sg=False,
+                                 **kwargs):
         """Create network resources and a server.
 
         Creating a network, subnet, router, keypair, security group
@@ -296,8 +297,13 @@
         self.subnet = self.create_subnet(self.network)
         LOG.debug("Created subnet %s", self.subnet['id'])
 
+        sg_args = {
+            'name': data_utils.rand_name('secgroup')
+        }
+        if use_stateless_sg:
+            sg_args['stateful'] = False
         secgroup = self.os_primary.network_client.create_security_group(
-            name=data_utils.rand_name('secgroup'))
+            **sg_args)
         LOG.debug("Created security group %s",
                   secgroup['security_group']['name'])
         self.security_groups.append(secgroup['security_group'])
@@ -307,6 +313,9 @@
         self.keypair = self.create_keypair()
         self.create_loginable_secgroup_rule(
             secgroup_id=secgroup['security_group']['id'])
+        if use_stateless_sg:
+            self.create_ingress_metadata_secgroup_rule(
+                secgroup_id=secgroup['security_group']['id'])
 
         server_kwargs = {
             'flavor_ref': CONF.compute.flavor_ref,
diff --git a/neutron_tempest_plugin/scenario/test_connectivity.py b/neutron_tempest_plugin/scenario/test_connectivity.py
index 5608dae..a6fc893 100644
--- a/neutron_tempest_plugin/scenario/test_connectivity.py
+++ b/neutron_tempest_plugin/scenario/test_connectivity.py
@@ -173,18 +173,21 @@
         ensures that connectivity from VM to both routers is working.
 
         Test scenario: (NOTE: 10.1.0.0/24 private CIDR is used as an example)
-        +----------------+                  +------------+
-        | Non-dvr router |                  | DVR router |
-        |                |                  |            |
-        |    10.1.0.1    |                  |  10.1.0.x  |
-        +-------+--------+                  +-----+------+
-                |                                 |
-                |         10.1.0.0/24             |
-                +----------------+----------------+
-                                 |
-                               +-+-+
-                               |VM |
-                               +---+
+
+        .. code-block:: HTML
+
+            +----------------+                  +------------+
+            | Non-dvr router |                  | DVR router |
+            |                |                  |            |
+            |    10.1.0.1    |                  |  10.1.0.x  |
+            +-------+--------+                  +-----+------+
+                    |                                 |
+                    |         10.1.0.0/24             |
+                    +----------------+----------------+
+                                     |
+                                   +-+-+
+                                   |VM |
+                                   +---+
 
         where:
         10.1.0.1 - is subnet's gateway IP address,
diff --git a/neutron_tempest_plugin/scenario/test_dns_integration.py b/neutron_tempest_plugin/scenario/test_dns_integration.py
index be9a477..d520aa2 100644
--- a/neutron_tempest_plugin/scenario/test_dns_integration.py
+++ b/neutron_tempest_plugin/scenario/test_dns_integration.py
@@ -212,11 +212,10 @@
     @classmethod
     def resource_setup(cls):
         super(DNSIntegrationAdminTests, cls).resource_setup()
-        # TODO(jh): We should add the segmentation_id as tempest option
-        # so that it can be changed to match the deployment if needed
-        cls.network2 = cls.create_network(dns_domain=cls.zone['name'],
-                provider_network_type='vxlan',
-                provider_segmentation_id=12345)
+        segmentation_id = CONF.designate_feature_enabled.segmentation_id
+        cls.network2 = cls.create_network(
+            dns_domain=cls.zone['name'], provider_network_type='vxlan',
+            provider_segmentation_id=segmentation_id)
         cls.subnet2 = cls.create_subnet(cls.network2)
 
     def _verify_dns_assignment(self, port):
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index 77a1e4a..f222396 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -15,6 +15,7 @@
 
 import time
 
+import ddt
 from neutron_lib import constants as lib_constants
 from neutron_lib.services.qos import constants as qos_consts
 from neutron_lib.utils import test
@@ -24,8 +25,6 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions
-import testscenarios
-from testscenarios.scenarios import multiply_scenarios
 import testtools
 
 from neutron_tempest_plugin.api import base as base_api
@@ -41,9 +40,6 @@
 LOG = log.getLogger(__name__)
 
 
-load_tests = testscenarios.load_tests_apply_scenarios
-
-
 class FloatingIpTestCasesMixin(object):
     credentials = ['primary', 'admin']
 
@@ -104,10 +100,10 @@
                                        constants.SERVER_STATUS_ACTIVE)
         return {'port': port, 'fip': fip, 'server': server}
 
-    def _test_east_west(self):
+    def _test_east_west(self, src_has_fip, dest_has_fip):
         # The proxy VM is used to control the source VM when it doesn't
         # have a floating-ip.
-        if self.src_has_fip:
+        if src_has_fip:
             proxy = None
             proxy_client = None
         else:
@@ -117,7 +113,7 @@
                                       pkey=self.keypair['private_key'])
 
         # Source VM
-        if self.src_has_fip:
+        if src_has_fip:
             src_server = self._create_server()
             src_server_ip = src_server['fip']['floating_ip_address']
         else:
@@ -129,7 +125,7 @@
                                 proxy_client=proxy_client)
 
         # Destination VM
-        if self.dest_has_fip:
+        if dest_has_fip:
             dest_server = self._create_server(network=self._dest_network)
         else:
             dest_server = self._create_server(create_floating_ip=False,
@@ -139,46 +135,46 @@
         self.check_remote_connectivity(ssh_client,
             dest_server['port']['fixed_ips'][0]['ip_address'],
             servers=[src_server, dest_server])
-        if self.dest_has_fip:
+        if dest_has_fip:
             self.check_remote_connectivity(ssh_client,
                 dest_server['fip']['floating_ip_address'],
                 servers=[src_server, dest_server])
 
 
+@ddt.ddt
 class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
                             base.BaseTempestTestCase):
-    scenarios = multiply_scenarios([
-        ('SRC with FIP', dict(src_has_fip=True)),
-        ('SRC without FIP', dict(src_has_fip=False)),
-    ], [
-        ('DEST with FIP', dict(dest_has_fip=True)),
-        ('DEST without FIP', dict(dest_has_fip=False)),
-    ])
 
     same_network = True
 
     @test.unstable_test("bug 1717302")
     @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
-    def test_east_west(self):
-        self._test_east_west()
+    @ddt.unpack
+    @ddt.data({'src_has_fip': True, 'dest_has_fip': True},
+              {'src_has_fip': True, 'dest_has_fip': False},
+              {'src_has_fip': False, 'dest_has_fip': True},
+              {'src_has_fip': True, 'dest_has_fip': False})
+    def test_east_west(self, src_has_fip, dest_has_fip):
+        self._test_east_west(src_has_fip=src_has_fip,
+                             dest_has_fip=dest_has_fip)
 
 
+@ddt.ddt
 class FloatingIpSeparateNetwork(FloatingIpTestCasesMixin,
                                 base.BaseTempestTestCase):
-    scenarios = multiply_scenarios([
-        ('SRC with FIP', dict(src_has_fip=True)),
-        ('SRC without FIP', dict(src_has_fip=False)),
-    ], [
-        ('DEST with FIP', dict(dest_has_fip=True)),
-        ('DEST without FIP', dict(dest_has_fip=False)),
-    ])
 
     same_network = False
 
     @test.unstable_test("bug 1717302")
     @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
-    def test_east_west(self):
-        self._test_east_west()
+    @ddt.unpack
+    @ddt.data({'src_has_fip': True, 'dest_has_fip': True},
+              {'src_has_fip': True, 'dest_has_fip': False},
+              {'src_has_fip': False, 'dest_has_fip': True},
+              {'src_has_fip': True, 'dest_has_fip': False})
+    def test_east_west(self, src_has_fip, dest_has_fip):
+        self._test_east_west(src_has_fip=src_has_fip,
+                             dest_has_fip=dest_has_fip)
 
 
 class DefaultSnatToExternal(FloatingIpTestCasesMixin,
@@ -611,7 +607,7 @@
                deleted just before the creation of the new IP to "reserve" the
                IP address associated (see LP#1880976).
             10. Create a FIP for the VM3 in the external network with
-               the same IP address that was used for VM2.
+                the same IP address that was used for VM2.
             11. Make sure that now VM1 is able to reach VM3 using the FIP.
 
         Note, the scenario passes only in case corresponding
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index e705241..9ffb05d 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -89,8 +89,8 @@
 
         1) Create two VMs on the same network, giving each a name
         2) SSH in to the first VM:
-          2.1) ping the other VM's internal IP
-          2.2) ping the other VM's hostname
+            - ping the other VM's internal IP
+            - ping the other VM's hostname
         """
         network = self.create_network(dns_domain='starwars.')
         self.setup_network_and_server(network=network, server_name='luke')
diff --git a/neutron_tempest_plugin/scenario/test_portsecurity.py b/neutron_tempest_plugin/scenario/test_portsecurity.py
index c90db08..db0a056 100644
--- a/neutron_tempest_plugin/scenario/test_portsecurity.py
+++ b/neutron_tempest_plugin/scenario/test_portsecurity.py
@@ -11,7 +11,9 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import testtools
 
+from tempest.common import utils
 from tempest.lib import decorators
 
 from neutron_tempest_plugin import config
@@ -24,15 +26,14 @@
     credentials = ['primary', 'admin']
     required_extensions = ['port-security']
 
-    @decorators.idempotent_id('61ab176e-d48b-42b7-b38a-1ba571ecc033')
-    def test_port_security_removed_added(self):
+    def _test_port_security_removed_added(self, use_stateless_sg):
         """Test connection works after port security has been removed
 
         Initial test that vm is accessible. Then port security is removed,
         checked connectivity. Port security is added back and checked
         connectivity again.
         """
-        self.setup_network_and_server()
+        self.setup_network_and_server(use_stateless_sg=use_stateless_sg)
         self.check_connectivity(self.fip['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
                                 self.keypair['private_key'])
@@ -51,3 +52,18 @@
         self.check_connectivity(self.fip['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
                                 self.keypair['private_key'])
+
+    @decorators.idempotent_id('61ab176e-d48b-42b7-b38a-1ba571ecc033')
+    def test_port_security_removed_added_stateful_sg(self):
+        self._test_port_security_removed_added(use_stateless_sg=False)
+
+    @decorators.idempotent_id('2f4005e1-cee1-40e5-adbd-b3e3cf218065')
+    @testtools.skipUnless(
+        utils.is_extension_enabled('stateful-security-group', 'network'),
+        "'stateful-security-group' API extension not available")
+    @testtools.skipIf(
+        CONF.neutron_plugin_options.firewall_driver in ['openvswitch', 'None'],
+        "Firewall driver other than 'openvswitch' is required to use "
+        "stateless security groups.")
+    def test_port_security_removed_added_stateless_sg(self):
+        self._test_port_security_removed_added(use_stateless_sg=True)
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 3d075b4..03156c7 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -1141,14 +1141,9 @@
             should_succeed=True)
 
 
-@testtools.skipIf(
-    CONF.neutron_plugin_options.firewall_driver in ['openvswitch', 'None'],
-    "Firewall driver other than 'openvswitch' is required to use "
-    "stateless security groups.")
-class StatelessSecGroupDualStackSlaacTest(BaseNetworkSecGroupTest):
+class StatelessSecGroupDualStackBase(BaseNetworkSecGroupTest):
     required_extensions = ['security-group', 'stateful-security-group']
     stateless_sg = True
-    ipv6_mode = 'slaac'
 
     def _get_port_cidrs(self, port):
         ips = []
@@ -1183,6 +1178,14 @@
                 for port_cidr in self._get_port_cidrs(port):
                     self.assertIn(port_cidr, configured_cidrs)
 
+
+@testtools.skipIf(
+    CONF.neutron_plugin_options.firewall_driver in ['openvswitch', 'None'],
+    "Firewall driver other than 'openvswitch' is required to use "
+    "stateless security groups.")
+class StatelessSecGroupDualStackSlaacTest(StatelessSecGroupDualStackBase):
+    ipv6_mode = 'slaac'
+
     @decorators.idempotent_id('e7d64384-ea6a-40aa-b454-854f0990153c')
     def test_default_sec_grp_scenarios(self):
         self._test_default_sec_grp_scenarios()
@@ -1193,9 +1196,7 @@
     "Firewall driver other than 'openvswitch' is required to use "
     "stateless security groups.")
 class StatelessSecGroupDualStackDHCPv6StatelessTest(
-        StatelessSecGroupDualStackSlaacTest):
-    required_extensions = ['security-group', 'stateful-security-group']
-    stateless_sg = True
+        StatelessSecGroupDualStackBase):
     ipv6_mode = 'dhcpv6-stateless'
 
     @decorators.idempotent_id('c61c127c-e08f-4ddf-87a3-58b3c86e5476')
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 0666297..d5a827e 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -846,6 +846,38 @@
         self.expected_success(204, resp.status)
         return service_client.ResponseBody(resp, body)
 
+    def list_default_security_group_rules(self, **kwargs):
+        uri = '%s/default-security-group-rules' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def get_default_security_group_rule(self, rule_id):
+        uri = '%s/default-security-group-rules/%s' % (self.uri_prefix,
+                                                      rule_id)
+        get_resp, get_resp_body = self.get(uri)
+        self.expected_success(200, get_resp.status)
+        body = jsonutils.loads(get_resp_body)
+        return service_client.ResponseBody(get_resp, body)
+
+    def create_default_security_group_rule(self, **kwargs):
+        post_body = {'default_security_group_rule': kwargs}
+        body = jsonutils.dumps(post_body)
+        uri = '%s/default-security-group-rules' % self.uri_prefix
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_default_security_group_rule(self, rule_id):
+        uri = '%s/default-security-group-rules/%s' % (self.uri_prefix, rule_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
     def list_ports(self, **kwargs):
         uri = '%s/ports' % self.uri_prefix
         if kwargs:
diff --git a/neutron_tempest_plugin/vpnaas/api/test_vpnaas.py b/neutron_tempest_plugin/vpnaas/api/test_vpnaas.py
index 953360e..129680a 100644
--- a/neutron_tempest_plugin/vpnaas/api/test_vpnaas.py
+++ b/neutron_tempest_plugin/vpnaas/api/test_vpnaas.py
@@ -14,10 +14,10 @@
 #    under the License.
 
 from neutron_lib.db import constants as db_const
+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 tempest import test
 
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.vpnaas.api import base_vpnaas as base
@@ -34,14 +34,15 @@
 
     Tests the following operations in the Neutron API using the REST client for
     Neutron:
-        List, Show, Create, Delete, and Update VPN Service
-        List, Show, Create, Delete, and Update IKE policy
-        List, Show, Create, Delete, and Update IPSec policy
+
+        * List, Show, Create, Delete, and Update VPN Service
+        * List, Show, Create, Delete, and Update IKE policy
+        * List, Show, Create, Delete, and Update IPSec policy
     """
 
     @classmethod
     def resource_setup(cls):
-        if not test.is_extension_enabled('vpnaas', 'network'):
+        if not utils.is_extension_enabled('vpnaas', 'network'):
             msg = "vpnaas extension not enabled."
             raise cls.skipException(msg)
         super(VPNaaSTestJSON, cls).resource_setup()
diff --git a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
index 92eed9e..30a8674 100644
--- a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
+++ b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
@@ -48,28 +48,44 @@
     'neutron_vpnaas_plugin_options'
 )
 
+# The VPNaaS drivers for OVN don't support IPv6 VMs
+CONF.register_opt(
+    cfg.BoolOpt('skip_6in4_tests',
+                default=False,
+                help='Whether to skip 6in4 test cases.'),
+    'neutron_vpnaas_plugin_options'
+)
+CONF.register_opt(
+    cfg.BoolOpt('skip_6in6_tests',
+                default=False,
+                help='Whether to skip 6in6 test cases.'),
+    'neutron_vpnaas_plugin_options'
+)
+
 
 class Vpnaas(base.BaseTempestTestCase):
     """Test the following topology
 
-          +-------------------+
-          | public            |
-          | network           |
-          |                   |
-          +-+---------------+-+
-            |               |
-            |               |
-    +-------+-+           +-+-------+
-    | LEFT    |           | RIGHT   |
-    | router  | <--VPN--> | router  |
-    |         |           |         |
-    +----+----+           +----+----+
-         |                     |
-    +----+----+           +----+----+
-    | LEFT    |           | RIGHT   |
-    | network |           | network |
-    |         |           |         |
-    +---------+           +---------+
+    .. code-block:: HTML
+
+              +-------------------+
+              | public            |
+              | network           |
+              |                   |
+              +-+---------------+-+
+                |               |
+                |               |
+        +-------+-+           +-+-------+
+        | LEFT    |           | RIGHT   |
+        | router  | <--VPN--> | router  |
+        |         |           |         |
+        +----+----+           +----+----+
+             |                     |
+        +----+----+           +----+----+
+        | LEFT    |           | RIGHT   |
+        | network |           | network |
+        |         |           |         |
+        +---------+           +---------+
     """
 
     credentials = ['primary', 'admin']
@@ -285,6 +301,9 @@
     @testtools.skipIf(
         CONF.neutron_vpnaas_plugin_options.skip_4in6_6in4_tests,
         'VPNaaS 6in4 test is skipped.')
+    @testtools.skipIf(
+        CONF.neutron_vpnaas_plugin_options.skip_6in4_tests,
+        'VPNaaS 6in4 test is skipped.')
     def test_vpnaas_6in4(self):
         self._test_vpnaas()
 
@@ -296,5 +315,8 @@
     @decorators.idempotent_id('8b503ffc-aeb0-4938-8dba-73c7323e276d')
     @testtools.skipUnless(CONF.network_feature_enabled.ipv6,
                           'IPv6 tests are disabled.')
+    @testtools.skipIf(
+        CONF.neutron_vpnaas_plugin_options.skip_6in6_tests,
+        'VPNaaS 6in6 test is skipped.')
     def test_vpnaas_6in6(self):
         self._test_vpnaas()
diff --git a/requirements.txt b/requirements.txt
index 34531e9..9423079 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,6 +16,5 @@
 tenacity>=3.2.1 # Apache-2.0
 ddt>=1.0.1 # MIT
 testtools>=2.2.0 # MIT
-testscenarios>=0.4 # Apache-2.0/BSD
 eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
 debtcollector>=1.2.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index c2fc078..2f7da5c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -40,8 +40,15 @@
 
 [testenv:docs]
 deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
+       -r{toxinidir}/requirements.txt
        -r{toxinidir}/doc/requirements.txt
-commands = sphinx-build -W -b html doc/source doc/build/html
+commands =
+  rm -rf doc/source/tests
+  sphinx-apidoc -f -o doc/source/tests neutron_tempest_plugin
+  rm -rf doc/build
+  sphinx-build -W -b html doc/source doc/build/html
+allowlist_externals =
+    rm
 
 [testenv:releasenotes]
 deps = {[testenv:docs]deps}
diff --git a/zuul.d/2023_1_jobs.yaml b/zuul.d/2023_1_jobs.yaml
index b9f293f..6bf7027 100644
--- a/zuul.d/2023_1_jobs.yaml
+++ b/zuul.d/2023_1_jobs.yaml
@@ -214,6 +214,9 @@
           (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_ovn) | join(',') }}"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
diff --git a/zuul.d/2023_2_jobs.yaml b/zuul.d/2023_2_jobs.yaml
index f5ece3d..87092a9 100644
--- a/zuul.d/2023_2_jobs.yaml
+++ b/zuul.d/2023_2_jobs.yaml
@@ -209,6 +209,9 @@
           (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_ovn) | join(',') }}"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
diff --git a/zuul.d/2024_1_jobs.yaml b/zuul.d/2024_1_jobs.yaml
new file mode 100644
index 0000000..022e439
--- /dev/null
+++ b/zuul.d/2024_1_jobs.yaml
@@ -0,0 +1,271 @@
+- job:
+    name: neutron-tempest-plugin-openvswitch-2024-1
+    parent: neutron-tempest-plugin-openvswitch
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_openvswitch: &api_extensions_openvswitch
+        - dhcp_agent_scheduler
+        - local_ip
+        - qos-bw-minimum-ingress
+      tempest_test_regex: "\
+          (^neutron_tempest_plugin.api)|\
+          (^neutron_tempest_plugin.scenario)|\
+          (^tempest.api.compute.servers.test_attach_interfaces)|\
+          (^tempest.api.compute.servers.test_multiple_create)"
+      network_available_features: &available_features
+        - ipv6_metadata
+      network_api_extensions_common: &api_extensions
+        - address-group
+        - address-scope
+        - agent
+        - allowed-address-pairs
+        - auto-allocated-topology
+        - availability_zone
+        - binding
+        - default-subnetpools
+        - dns-domain-ports
+        - dns-integration
+        - dns-integration-domain-keywords
+        - 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
+        - floating-ip-port-forwarding-detail
+        - floatingip-pools
+        - ip-substring-filtering
+        - l3-conntrack-helper
+        - l3-ext-ndp-proxy
+        - l3-flavors
+        - l3-ha
+        - l3-ndp-proxy
+        - l3_agent_scheduler
+        - metering
+        - multi-provider
+        - net-mtu
+        - net-mtu-writable
+        - network-ip-availability
+        - network_availability_zone
+        - network-segment-range
+        - pagination
+        - port-device-profile
+        - port-mac-address-regenerate
+        - port-resource-request
+        - port-resource-request-groups
+        - port-security
+        - port-security-groups-filtering
+        - project-id
+        - provider
+        - qos
+        - qos-fip
+        - quotas
+        - quota_details
+        - rbac-address-group
+        - rbac-address-scope
+        - rbac-policies
+        - rbac-security-groups
+        - rbac-subnetpool
+        - router
+        - router_availability_zone
+        - security-group
+        - security-groups-default-rules
+        - security-groups-normalized-cidr
+        - security-groups-remote-address-group
+        - segment
+        - service-type
+        - sorting
+        - standard-attr-description
+        - standard-attr-revisions
+        - standard-attr-segment
+        - standard-attr-tag
+        - standard-attr-timestamp
+        - stateful-security-group
+        - subnet_allocation
+        - subnet-dns-publish-fixed-ip
+        - subnet-service-types
+        - subnetpool-prefix-ops
+        - tag-ports-during-bulk-creation
+        - trunk
+        - trunk-details
+        - uplink-status-propagation
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_openvswitch) | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-openvswitch-iptables_hybrid-2024-1
+    parent: neutron-tempest-plugin-openvswitch-iptables_hybrid
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_openvswitch: *api_extensions_openvswitch
+      network_available_features: *available_features
+      tempest_test_regex: "\
+          (^neutron_tempest_plugin.api)|\
+          (^neutron_tempest_plugin.scenario)|\
+          (^tempest.api.compute.servers.test_attach_interfaces)|\
+          (^tempest.api.compute.servers.test_multiple_create)"
+      # TODO(slaweq): remove trunks subport_connectivity test from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
+      # TODO(akatz): remove established tcp session verification test when the
+      # bug https://bugzilla.redhat.com/show_bug.cgi?id=1965036 will be fixed
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.StatefulNetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.StatelessNetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)"
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_openvswitch) | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: "{{ network_available_features | join(',') }}"
+            neutron_plugin_options:
+              available_type_drivers: flat,vlan,local,vxlan
+              firewall_driver: iptables_hybrid
+
+- job:
+    name: neutron-tempest-plugin-openvswitch-enforce-scope-new-defaults-2024-1
+    parent: neutron-tempest-plugin-openvswitch-2024-1
+    override-checkout: stable/2024.1
+    vars:
+      devstack_localrc:
+        # Enabeling the scope and new defaults for services.
+        # NOTE: (gmann) We need to keep keystone scope check disable as
+        # services (except ironic) does not support the system scope and
+        # they need keystone to continue working with project scope. Until
+        # Keystone policies are changed to work for both system as well as
+        # for project scoped, we need to keep scope check disable for
+        # keystone.
+        NOVA_ENFORCE_SCOPE: true
+        GLANCE_ENFORCE_SCOPE: true
+        NEUTRON_ENFORCE_SCOPE: true
+
+- job:
+    name: neutron-tempest-plugin-linuxbridge-2024-1
+    parent: neutron-tempest-plugin-linuxbridge
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_linuxbridge:
+        - dhcp_agent_scheduler
+        - vlan-transparent
+      network_available_features: *available_features
+      tempest_test_regex: "\
+          (^neutron_tempest_plugin.api)|\
+          (^neutron_tempest_plugin.scenario)|\
+          (^tempest.api.compute.servers.test_attach_interfaces)|\
+          (^tempest.api.compute.servers.test_multiple_create)"
+      # TODO(eolivare): remove VLAN Transparency tests from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1907548 will be fixed
+      # TODO(slaweq): remove
+      # test_established_tcp_session_after_re_attachinging_sg from the
+      # exclude regex when bug https://bugs.launchpad.net/neutron/+bug/1936911
+      # will be fixed
+      # TODO(slaweq) remove test_floatingip_port_details from the exclude
+      # regex when bug https://bugs.launchpad.net/neutron/+bug/1799790 will be
+      # fixed
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_vlan_transparency.VlanTransparencyTest)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.StatefulNetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.StatelessNetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)|\
+          (^neutron_tempest_plugin.scenario.test_floatingip.FloatingIPPortDetailsTest.test_floatingip_port_details)"
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_linuxbridge) | join(',') }}"
+      devstack_local_conf:
+        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
+              firewall_driver: iptables
+
+- job:
+    name: neutron-tempest-plugin-ovn-2024-1
+    parent: neutron-tempest-plugin-ovn
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_ovn:
+        - vlan-transparent
+      tempest_test_regex: "\
+          (^neutron_tempest_plugin.api)|\
+          (^neutron_tempest_plugin.scenario)|\
+          (^tempest.api.compute.servers.test_attach_interfaces)|\
+          (^tempest.api.compute.servers.test_multiple_create)"
+      # TODO(jlibosva): Remove the NetworkWritableMtuTest test from the list
+      # once east/west fragmentation is supported in core OVN
+      tempest_exclude_regex: "\
+          (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_ovn) | join(',') }}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            network-feature-enabled:
+              available_features: ""
+            neutron_plugin_options:
+              available_type_drivers: local,flat,vlan,geneve
+              is_igmp_snooping_enabled: True
+              firewall_driver: ovn
+
+- job:
+    name: neutron-tempest-plugin-dvr-multinode-scenario-2024-1
+    parent: neutron-tempest-plugin-dvr-multinode-scenario
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_dvr:
+        - dhcp_agent_scheduler
+        - dvr
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_dvr) | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-designate-scenario-2024-1
+    parent: neutron-tempest-plugin-designate-scenario
+    override-checkout: stable/2024.1
+    vars:
+      network_api_extensions_common: *api_extensions
+
+- job:
+    name: neutron-tempest-plugin-sfc-2024-1
+    parent: neutron-tempest-plugin-sfc
+    override-checkout: stable/2024.1
+
+- job:
+    name: neutron-tempest-plugin-bgpvpn-bagpipe-2024-1
+    parent: neutron-tempest-plugin-bgpvpn-bagpipe
+    override-checkout: stable/2024.1
+
+- job:
+    name: neutron-tempest-plugin-dynamic-routing-2024-1
+    parent: neutron-tempest-plugin-dynamic-routing
+    override-checkout: stable/2024.1
+
+- job:
+    name: neutron-tempest-plugin-fwaas-2024-1
+    parent: neutron-tempest-plugin-fwaas
+    override-checkout: stable/2024.1
+
+- job:
+    name: neutron-tempest-plugin-vpnaas-2024-1
+    parent: neutron-tempest-plugin-vpnaas
+    override-checkout: stable/2024.1
+
+- job:
+    name: neutron-tempest-plugin-tap-as-a-service-2024-1
+    parent: neutron-tempest-plugin-tap-as-a-service
+    override-checkout: stable/2024.1
diff --git a/zuul.d/base-nested-switch.yaml b/zuul.d/base-nested-switch.yaml
index b4570fe..8533e07 100644
--- a/zuul.d/base-nested-switch.yaml
+++ b/zuul.d/base-nested-switch.yaml
@@ -24,7 +24,7 @@
     parent: neutron-tempest-plugin-base
     abstract: true
     branches:
-      regex: ^(stable/(train|ussuri|victoria|wallaby|xena|yoga|zed)).*$
+      regex: ^(unmaintained|stable/(victoria|wallaby|xena|yoga|zed)).*$
       negate: true
     # Comment nodeset and vars to switch back to non nested nodes
     nodeset: neutron-nested-virt-ubuntu-jammy
@@ -43,7 +43,7 @@
     name: neutron-tempest-plugin-base-nested-switch
     parent: neutron-tempest-plugin-base
     abstract: true
-    branches: ^stable/(yoga|zed)$
+    branches: ^(unmaintained|stable)/(yoga|zed)$
     # Comment nodeset and vars to switch back to non nested nodes
     nodeset: neutron-nested-virt-ubuntu-focal
     vars: *nested_virt_vars
@@ -53,4 +53,4 @@
     name: neutron-tempest-plugin-base-nested-switch
     parent: neutron-tempest-plugin-base
     abstract: true
-    branches: ^(stable/(train|ussuri|victoria|wallaby|xena)).*$
+    branches: ^(stable/(victoria|wallaby|xena)).*$
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 7e1aacb..d7b6732 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -114,6 +114,8 @@
         - router
         - router_availability_zone
         - security-group
+        - security-groups-default-rules
+        - security-groups-normalized-cidr
         - security-groups-remote-address-group
         - segment
         - service-type
@@ -421,10 +423,6 @@
     parent: neutron-tempest-plugin-openvswitch
     vars:
       devstack_localrc:
-        # Disabling the scope and new defaults for services to use old,
-        # deprecated policies
-        NOVA_ENFORCE_SCOPE: false
-        GLANCE_ENFORCE_SCOPE: false
         NEUTRON_ENFORCE_SCOPE: false
 
 
@@ -605,6 +603,7 @@
           (^neutron_tempest_plugin.scenario.test_mtu.NetworkWritableMtuTest)"
       devstack_localrc:
         Q_AGENT: ovn
+        OVN_AGENT_EXTENSIONS: 'metadata'
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_ovn) | join(',') }}"
         Q_ML2_PLUGIN_MECHANISM_DRIVERS: ovn,logger
         Q_ML2_PLUGIN_TYPE_DRIVERS: local,flat,vlan,geneve
@@ -626,7 +625,8 @@
       devstack_services:
         br-ex-tcpdump: true
         br-int-flows: true
-        q-ovn-metadata-agent: true
+        q-ovn-metadata-agent: false
+        q-ovn-agent: true
         ovn-controller: true
         ovn-northd: true
         ovs-vswitchd: true
@@ -648,6 +648,7 @@
         s-container: false
         s-object: false
         s-proxy: false
+      network_available_features: *available_features
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -660,7 +661,7 @@
         test-config:
           $TEMPEST_CONFIG:
             network-feature-enabled:
-              available_features: ""
+              available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: local,flat,vlan,geneve
               is_igmp_snooping_enabled: True
@@ -1344,6 +1345,66 @@
       - ^zuul.d/(?!(project)).*\.yaml
 
 - job:
+    name: neutron-tempest-plugin-vpnaas-ovn
+    parent: neutron-tempest-plugin-base
+    timeout: 3900
+    required-projects:
+      - openstack/neutron
+      - openstack/neutron-vpnaas
+      - openstack/neutron-tempest-plugin
+      - openstack/tempest
+    vars:
+      tempest_concurrency: 4
+      tempest_test_regex: ^neutron_tempest_plugin\.vpnaas
+      devstack_plugins:
+        neutron-vpnaas: https://opendev.org/openstack/neutron-vpnaas.git
+        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_vpnaas:
+        - vpnaas
+      devstack_localrc:
+        IPSEC_PACKAGE: strongswan
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_vpnaas) | join(',') }}"
+      devstack_services:
+        q-ovn-vpn-agent: true
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            neutron_vpnaas_plugin_options:
+              skip_6in4_tests: true
+              skip_6in6_tests: true
+
+    irrelevant-files:
+      - ^\.pylintrc$
+      - ^(test-|)requirements.txt$
+      - lower-constraints.txt
+      - ^releasenotes/.*$
+      - ^doc/.*$
+      - ^.*\.conf\.sample$
+      - ^setup.cfg$
+      - ^.*\.rst$
+      - ^neutron/locale/.*$
+      - ^neutron/tests/unit/.*$
+      - ^neutron/tests/fullstack/.*
+      - ^neutron/tests/functional/.*
+      - ^neutron_tempest_plugin/api/test_.*$
+      - ^neutron_tempest_plugin/scenario/admin/.*$
+      - ^neutron_tempest_plugin/scenario/test_.*$
+      - ^neutron_tempest_plugin/(bgpvpn|fwaas|neutron_dynamic_routing|sfc|tap_as_a_service).*$
+      - ^neutron_tempest_plugin/services/bgp/.*$
+      - ^tools/.*$
+      - ^tox.ini$
+      - ^plugin.spec$
+      - ^rally-jobs/.*$
+      - ^roles/.*functional.*$
+      - ^playbooks/.*dvr-multinode.*$
+      - ^playbooks/.*dynamic-routing.*$
+      - ^playbooks/.*functional.*$
+      - ^playbooks/.*linuxbridge.*$
+      - ^vagrant/.*$
+      - ^zuul.d/(?!(project)).*\.yaml
+
+- job:
     name: neutron-tempest-plugin-tap-as-a-service
     parent: neutron-tempest-plugin-base
     description: |
@@ -1410,7 +1471,6 @@
         mysql: true
         rabbit: true
         g-api: true
-        g-reg: true
         n-api: true
         n-cond: true
         n-cpu: true
@@ -1419,7 +1479,7 @@
         placement-api: true
         n-api-meta: true
         q-svc: true
-        quantum: true
+        neutron: true
         taas: true
         taas_openvswitch_agent: true
         tempest: true
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 5d3f973..35a3e7f 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -24,44 +24,6 @@
 
 
 - project-template:
-    name: neutron-tempest-plugin-jobs-train
-    check:
-      jobs:
-        - neutron-tempest-plugin-api-train
-        - neutron-tempest-plugin-scenario-linuxbridge-train
-        - neutron-tempest-plugin-scenario-openvswitch-train
-        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-train
-    gate:
-      jobs:
-        - neutron-tempest-plugin-api-train
-    #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-train
-
-
-- project-template:
-    name: neutron-tempest-plugin-jobs-ussuri
-    check:
-      jobs:
-        - neutron-tempest-plugin-api-ussuri
-        - neutron-tempest-plugin-scenario-linuxbridge-ussuri
-        - neutron-tempest-plugin-scenario-openvswitch-ussuri
-        - neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-ussuri
-        - neutron-tempest-plugin-scenario-ovn-ussuri
-        - neutron-tempest-plugin-designate-scenario-ussuri
-    gate:
-      jobs:
-        - neutron-tempest-plugin-api-ussuri
-    #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-ussuri
-
-
-- project-template:
     name: neutron-tempest-plugin-jobs-victoria
     check:
       jobs:
@@ -193,48 +155,68 @@
         - neutron-tempest-plugin-linuxbridge-2023-2
         - neutron-tempest-plugin-dvr-multinode-scenario-2023-2
 
+- project-template:
+    name: neutron-tempest-plugin-jobs-2024-1
+    check:
+      jobs:
+        - neutron-tempest-plugin-openvswitch-2024-1
+        - neutron-tempest-plugin-openvswitch-iptables_hybrid-2024-1
+        - neutron-tempest-plugin-ovn-2024-1
+        - neutron-tempest-plugin-designate-scenario-2024-1
+    gate:
+      jobs:
+        - neutron-tempest-plugin-ovn-2024-1
+    #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-linuxbridge-2024-1
+        - neutron-tempest-plugin-dvr-multinode-scenario-2024-1
+
 - project:
     templates:
       - build-openstack-docs-pti
       - neutron-tempest-plugin-jobs
-      - neutron-tempest-plugin-jobs-yoga
       - neutron-tempest-plugin-jobs-zed
       - neutron-tempest-plugin-jobs-2023-1
       - neutron-tempest-plugin-jobs-2023-2
+      - neutron-tempest-plugin-jobs-2024-1
       - check-requirements
       - tempest-plugin-jobs
       - release-notes-jobs-python3
     check:
       jobs:
         - neutron-tempest-plugin-sfc
-        - neutron-tempest-plugin-sfc-yoga
         - neutron-tempest-plugin-sfc-zed
         - neutron-tempest-plugin-sfc-2023-1
         - neutron-tempest-plugin-sfc-2023-2
+        - neutron-tempest-plugin-sfc-2024-1
         - neutron-tempest-plugin-bgpvpn-bagpipe
-        - neutron-tempest-plugin-bgpvpn-bagpipe-yoga
         - neutron-tempest-plugin-bgpvpn-bagpipe-zed
         - neutron-tempest-plugin-bgpvpn-bagpipe-2023-1
         - neutron-tempest-plugin-bgpvpn-bagpipe-2023-2
+        - neutron-tempest-plugin-bgpvpn-bagpipe-2024-1
         - neutron-tempest-plugin-dynamic-routing
-        - neutron-tempest-plugin-dynamic-routing-yoga
         - neutron-tempest-plugin-dynamic-routing-zed
         - neutron-tempest-plugin-dynamic-routing-2023-1
         - neutron-tempest-plugin-dynamic-routing-2023-2
+        - neutron-tempest-plugin-dynamic-routing-2024-1
         - neutron-tempest-plugin-fwaas
         - neutron-tempest-plugin-fwaas-zed
         - neutron-tempest-plugin-fwaas-2023-1
         - neutron-tempest-plugin-fwaas-2023-2
+        - neutron-tempest-plugin-fwaas-2024-1
         - neutron-tempest-plugin-vpnaas
-        - neutron-tempest-plugin-vpnaas-yoga
+        - neutron-tempest-plugin-vpnaas-ovn
         - neutron-tempest-plugin-vpnaas-zed
         - neutron-tempest-plugin-vpnaas-2023-1
         - neutron-tempest-plugin-vpnaas-2023-2
+        - neutron-tempest-plugin-vpnaas-2024-1
         - neutron-tempest-plugin-tap-as-a-service
-        - neutron-tempest-plugin-tap-as-a-service-yoga
         - neutron-tempest-plugin-tap-as-a-service-zed
         - neutron-tempest-plugin-tap-as-a-service-2023-1
         - neutron-tempest-plugin-tap-as-a-service-2023-2
+        - neutron-tempest-plugin-tap-as-a-service-2024-1
 
     gate:
       jobs:
@@ -242,3 +224,4 @@
         - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-dynamic-routing
         - neutron-tempest-plugin-fwaas
+        - neutron-tempest-plugin-vpnaas-ovn
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
deleted file mode 100644
index 159feb2..0000000
--- a/zuul.d/train_jobs.yaml
+++ /dev/null
@@ -1,266 +0,0 @@
-- job:
-    name: neutron-tempest-plugin-api-train
-    parent: neutron-tempest-plugin-base
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: &required-projects-train
-      - openstack/neutron
-      - name: openstack/neutron-tempest-plugin
-        override-checkout: 1.5.0
-      - openstack/tempest
-    vars:
-      devstack_services:
-        # Disable OVN services
-        br-ex-tcpdump: false
-        br-int-flows: false
-        ovn-controller: false
-        ovn-northd: false
-        ovs-vswitchd: false
-        ovsdb-server: false
-        q-ovn-metadata-agent: false
-        # Neutron services
-        q-agt: true
-        q-dhcp: true
-        q-l3: true
-        q-meta: true
-        q-metering: true
-      tempest_concurrency: 4
-      tempest_test_regex: ^neutron_tempest_plugin\.api
-      # 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-policies
-        - rbac-security-groups
-        - 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-service-types
-        - subnetpool-prefix-ops
-        - trunk
-        - trunk-details
-        - uplink-status-propagation
-      network_api_extensions_tempest:
-        - dvr
-      network_available_features: &available_features
-        -
-      devstack_localrc:
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-        NEUTRON_DEPLOY_MOD_WSGI: false
-        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
-        Q_AGENT: openvswitch
-        Q_ML2_TENANT_NETWORK_TYPE: vxlan
-        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
-        ML2_L3_PLUGIN: router
-      devstack_local_conf:
-        post-config:
-          /$NEUTRON_CORE_PLUGIN_CONF:
-            AGENT:
-              tunnel_types: gre,vxlan
-            ml2:
-              type_drivers: flat,geneve,vlan,gre,local,vxlan
-        test-config:
-          $TEMPEST_CONFIG:
-            neutron_plugin_options:
-              available_type_drivers: flat,geneve,vlan,gre,local,vxlan
-
-- job:
-    name: neutron-tempest-plugin-scenario-openvswitch-train
-    parent: neutron-tempest-plugin-openvswitch
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      devstack_localrc:
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived doesn't knows this option yet
-              keepalived_use_no_track: False
-        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-openvswitch-iptables_hybrid
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      devstack_localrc:
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived don't knows this option yet
-              keepalived_use_no_track: False
-        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-linuxbridge
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      devstack_localrc:
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived don't knows this option yet
-              keepalived_use_no_track: False
-        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
-    required-projects: *required-projects-train
-    vars:
-      network_api_extensions_common: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-sfc-train
-    parent: neutron-tempest-plugin-sfc
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      network_api_extensions_common: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-bgpvpn-bagpipe-train
-    parent: neutron-tempest-plugin-bgpvpn-bagpipe
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      network_api_extensions: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-fwaas-train
-    parent: neutron-tempest-plugin-fwaas-ussuri
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    required-projects: *required-projects-train
-    vars:
-      network_api_extensions_common: *api_extensions
diff --git a/zuul.d/ussuri_jobs.yaml b/zuul.d/ussuri_jobs.yaml
deleted file mode 100644
index 5abc741..0000000
--- a/zuul.d/ussuri_jobs.yaml
+++ /dev/null
@@ -1,356 +0,0 @@
-- job:
-    name: neutron-tempest-plugin-api-ussuri
-    parent: neutron-tempest-plugin-base
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: &required-projects-ussuri
-      - openstack/neutron
-      - name: openstack/neutron-tempest-plugin
-        override-checkout: 1.6.0
-      - openstack/tempest
-    vars:
-      devstack_services:
-        # Disable OVN services
-        br-ex-tcpdump: false
-        br-int-flows: false
-        ovn-controller: false
-        ovn-northd: false
-        ovs-vswitchd: false
-        ovsdb-server: false
-        q-ovn-metadata-agent: false
-        # Neutron services
-        q-agt: true
-        q-dhcp: true
-        q-l3: true
-        q-meta: true
-        q-metering: true
-      tempest_concurrency: 4
-      tempest_test_regex: ^neutron_tempest_plugin\.api
-      # 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
-        - subnet-service-types
-        - subnetpool-prefix-ops
-        - tag-ports-during-bulk-creation
-        - trunk
-        - trunk-details
-        - 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(',') }}"
-        Q_AGENT: openvswitch
-        Q_ML2_TENANT_NETWORK_TYPE: vxlan
-        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
-        ML2_L3_PLUGIN: router
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        post-config:
-          /$NEUTRON_CORE_PLUGIN_CONF:
-            AGENT:
-              tunnel_types: gre,vxlan
-            ml2:
-              type_drivers: flat,geneve,vlan,gre,local,vxlan
-        test-config:
-          $TEMPEST_CONFIG:
-            neutron_plugin_options:
-              available_type_drivers: flat,geneve,vlan,gre,local,vxlan
-
-
-- job:
-    name: neutron-tempest-plugin-scenario-openvswitch-ussuri
-    parent: neutron-tempest-plugin-openvswitch
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      devstack_localrc: &localrc_scenarios_common
-        NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived don't knows this option yet
-              keepalived_use_no_track: False
-        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-openvswitch-iptables_hybrid
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      # TODO(akatz): remove established tcp session verification test when the
-      # bug https://bugzilla.redhat.com/show_bug.cgi?id=1965036 will be fixed
-      tempest_exclude_regex: "\
-          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_established_tcp_session_after_re_attachinging_sg)"
-      devstack_localrc: *localrc_scenarios_common
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived don't knows this option yet
-              keepalived_use_no_track: False
-        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-linuxbridge
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      network_available_features: *available_features
-      devstack_localrc: *localrc_scenarios_common
-      devstack_local_conf:
-        post-config:
-          $NEUTRON_L3_CONF:
-            DEFAULT:
-              # NOTE(slaweq): on Bionic keepalived don't knows this option yet
-              keepalived_use_no_track: False
-        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-ovn
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      tempest_test_regex: "\
-          (^neutron_tempest_plugin.scenario)|\
-          (^tempest.api.compute.servers.test_attach_interfaces)|\
-          (^tempest.api.compute.servers.test_multiple_create)"
-      network_api_extensions: *api_extensions
-      # TODO(haleyb): Remove IPv6Test from blacklist when
-      # https://bugs.launchpad.net/neutron/+bug/1881558 is fixed.
-      # TODO(slaweq): Remove test_trunk_subport_lifecycle test from the
-      # 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_exclude_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:
-        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
-        OVN_BRANCH: "v20.03.0"
-        # NOTE(slaweq): IGMP Snooping requires OVN 20.12
-        OVN_IGMP_SNOOPING_ENABLE: False
-        # NOTE(bcafarel) guestmount binary not available on host OS
-        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
-        CUSTOMIZE_IMAGE: false
-      devstack_local_conf:
-        test-config:
-          $TEMPEST_CONFIG:
-            neutron_plugin_options:
-              is_igmp_snooping_enabled: False
-
-- 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
-    required-projects: *required-projects-ussuri
-    vars:
-      network_api_extensions_common: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-designate-scenario-ussuri
-    parent: neutron-tempest-plugin-designate-scenario
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects:
-      - openstack/neutron
-      - name: openstack/neutron-tempest-plugin
-        override-checkout: 1.6.0
-      - openstack/tempest
-      - name: openstack/designate-tempest-plugin
-        override-checkout: 0.7.0
-    vars:
-      network_api_extensions: *api_extensions
-      devstack_localrc: *localrc_scenarios_common
-
-- job:
-    name: neutron-tempest-plugin-sfc-ussuri
-    parent: neutron-tempest-plugin-sfc
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      network_api_extensions_common: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-bgpvpn-bagpipe-ussuri
-    parent: neutron-tempest-plugin-bgpvpn-bagpipe
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      network_api_extensions: *api_extensions
-
-- 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:
-      - openstack/neutron-fwaas
-      - openstack/neutron
-      - name: openstack/neutron-tempest-plugin
-        override-checkout: 1.6.0
-      - openstack/tempest
-    vars:
-      tempest_test_regex: ^neutron_tempest_plugin\.fwaas
-      devstack_plugins:
-        neutron-fwaas: https://opendev.org/openstack/neutron-fwaas.git
-        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
-      network_api_extensions_common: *api_extensions
-      network_api_extensions_fwaas:
-        - fwaas_v2
-      devstack_localrc:
-        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_fwaas) | join(',') }}"
-
-- job:
-    name: neutron-tempest-plugin-dynamic-routing-ussuri
-    parent: neutron-tempest-plugin-dynamic-routing
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      network_api_extensions_common: *api_extensions
-
-- job:
-    name: neutron-tempest-plugin-vpnaas-ussuri
-    parent: neutron-tempest-plugin-vpnaas
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/ussuri
-    required-projects: *required-projects-ussuri
-    vars:
-      network_api_extensions_common: *api_extensions
diff --git a/zuul.d/victoria_jobs.yaml b/zuul.d/victoria_jobs.yaml
index 1f611db..7e2549f 100644
--- a/zuul.d/victoria_jobs.yaml
+++ b/zuul.d/victoria_jobs.yaml
@@ -240,6 +240,9 @@
         CUSTOMIZE_IMAGE: false
         OVN_BRANCH: "v21.06.0"
         OVS_BRANCH: "a4b04276ab5934d087669ff2d191a23931335c87"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
diff --git a/zuul.d/wallaby_jobs.yaml b/zuul.d/wallaby_jobs.yaml
index 92a3e50..8a771b8 100644
--- a/zuul.d/wallaby_jobs.yaml
+++ b/zuul.d/wallaby_jobs.yaml
@@ -177,6 +177,9 @@
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         OVN_BRANCH: "v21.06.0"
         OVS_BRANCH: "a4b04276ab5934d087669ff2d191a23931335c87"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
diff --git a/zuul.d/xena_jobs.yaml b/zuul.d/xena_jobs.yaml
index 25d63a9..9f8e960 100644
--- a/zuul.d/xena_jobs.yaml
+++ b/zuul.d/xena_jobs.yaml
@@ -170,6 +170,9 @@
       network_api_extensions: *api_extensions
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
diff --git a/zuul.d/yoga_jobs.yaml b/zuul.d/yoga_jobs.yaml
index 04c9ddd..76cac3e 100644
--- a/zuul.d/yoga_jobs.yaml
+++ b/zuul.d/yoga_jobs.yaml
@@ -2,7 +2,14 @@
     name: neutron-tempest-plugin-api-yoga
     parent: neutron-tempest-plugin-base
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: &required-projects-yoga
+      - openstack/neutron
+      - name: openstack/neutron-tempest-plugin
+        # Move to 2.6.0 once released
+        # https://review.opendev.org/c/openstack/releases/+/908369
+        override-checkout: 2.5.0
+      - openstack/tempest
     vars:
       tempest_concurrency: 4
       tempest_test_regex: ^neutron_tempest_plugin\.api
@@ -97,7 +104,8 @@
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-yoga
     parent: neutron-tempest-plugin-openvswitch
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     nodeset: neutron-nested-virt-ubuntu-focal
     vars:
       tempest_test_regex: "\
@@ -117,7 +125,8 @@
 - job:
     name: neutron-tempest-plugin-scenario-openvswitch-iptables_hybrid-yoga
     parent: neutron-tempest-plugin-openvswitch-iptables_hybrid
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     nodeset: neutron-nested-virt-ubuntu-focal
     vars:
       tempest_test_regex: "\
@@ -137,7 +146,8 @@
 - job:
     name: neutron-tempest-plugin-scenario-linuxbridge-yoga
     parent: neutron-tempest-plugin-linuxbridge
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     nodeset: neutron-nested-virt-ubuntu-focal
     vars:
       tempest_test_regex: "\
@@ -157,7 +167,8 @@
 - job:
     name: neutron-tempest-plugin-scenario-ovn-yoga
     parent: neutron-tempest-plugin-ovn
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     nodeset: neutron-nested-virt-ubuntu-focal
     vars:
       tempest_test_regex: "\
@@ -169,6 +180,9 @@
         - vlan-transparent
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_ovn) | join(',') }}"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
@@ -179,14 +193,16 @@
     name: neutron-tempest-plugin-dvr-multinode-scenario-yoga
     parent: neutron-tempest-plugin-dvr-multinode-scenario
     nodeset: openstack-two-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions_common: *api_extensions
 
 - job:
     name: neutron-tempest-plugin-designate-scenario-yoga
     parent: neutron-tempest-plugin-designate-scenario
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     nodeset: neutron-nested-virt-ubuntu-focal
     vars:
       network_api_extensions_common: *api_extensions
@@ -195,7 +211,8 @@
     name: neutron-tempest-plugin-sfc-yoga
     parent: neutron-tempest-plugin-sfc
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions_common: *api_extensions
 
@@ -203,7 +220,8 @@
     name: neutron-tempest-plugin-bgpvpn-bagpipe-yoga
     parent: neutron-tempest-plugin-bgpvpn-bagpipe
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions: *api_extensions
 
@@ -211,7 +229,8 @@
     name: neutron-tempest-plugin-dynamic-routing-yoga
     parent: neutron-tempest-plugin-dynamic-routing
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions_common: *api_extensions
       devstack_localrc:
@@ -241,7 +260,8 @@
     name: neutron-tempest-plugin-vpnaas-yoga
     parent: neutron-tempest-plugin-vpnaas
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions_common: *api_extensions
 
@@ -249,6 +269,7 @@
     name: neutron-tempest-plugin-tap-as-a-service-yoga
     parent: neutron-tempest-plugin-tap-as-a-service
     nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
+    override-checkout: unmaintained/yoga
+    required-projects: *required-projects-yoga
     vars:
       network_api_extensions_common: *api_extensions
diff --git a/zuul.d/zed_jobs.yaml b/zuul.d/zed_jobs.yaml
index 8c70a66..7d8b2e1 100644
--- a/zuul.d/zed_jobs.yaml
+++ b/zuul.d/zed_jobs.yaml
@@ -183,6 +183,9 @@
         - vlan-transparent
       devstack_localrc:
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions + network_api_extensions_ovn) | join(',') }}"
+      devstack_services:
+        q-ovn-metadata-agent: true
+        q-ovn-agent: false
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG: