Merge "Move test_dhcp_port_status_active from neutron-tempest-plugin"
diff --git a/HACKING.rst b/HACKING.rst
index dc28e4e..17e2a49 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -322,7 +322,14 @@
 
 - If the execution of a set of tests is required to be serialized then locking
   can be used to perform this. See usage of ``LockFixture`` for examples of
-  using locking.
+  using locking. However, LockFixture only helps if you want to separate the
+  execution of two small sets of test cases. On the other hand, if you need to
+  run a set of tests separately from potentially all other tests then
+  ``LockFixture`` does not scale as you would need to take the lock in all the
+  other tests too. In this case, you can use the ``@serial`` decorator on top
+  of the test class holding the tests that need to run separately from the
+  potentially parallel test set. See more in :ref:`tempest_test_writing`.
+
 
 Sample Configuration File
 -------------------------
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 34df089..3626a3f 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -256,6 +256,33 @@
 worth checking the immediate parent for what is set to determine if your
 class needs to override that setting.
 
+Running some tests in serial
+----------------------------
+Tempest potentially runs test cases in parallel, depending on the configuration.
+However, sometimes you need to make sure that tests are not interfering with
+each other via OpenStack resources. Tempest creates separate projects for each
+test class to separate project based resources between test cases.
+
+If your tests use resources outside of projects, e.g. host aggregates then
+you might need to explicitly separate interfering test cases. If you only need
+to separate a small set of testcases from each other then you can use the
+``LockFixture``.
+
+However, in some cases a small set of tests needs to be run independently from
+the rest of the test cases. For example, some of the host aggregate and
+availability zone testing needs compute nodes without any running nova server
+to be able to move compute hosts between availability zones. But many tempest
+tests start one or more nova servers. In this scenario you can mark the small
+set of tests that needs to be independent from the rest with the ``@serial``
+class decorator. This will make sure that even if tempest is configured to run
+the tests in parallel the tests in the marked test class will always be executed
+separately from the rest of the test cases.
+
+Please note that due to test ordering optimization reasons test cases marked
+for ``@serial`` execution need to be put under ``tempest/serial_tests``
+directory. This will ensure that the serial tests will block the parallel tests
+in the least amount of time.
+
 Interacting with Credentials and Clients
 ========================================
 
diff --git a/requirements.txt b/requirements.txt
index a118856..6e66046 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,3 +22,4 @@
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
 defusedxml>=0.7.1 # PSFL
+fasteners>=0.16.0 # Apache-2.0
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index f302fa5..e569e53 100644
--- a/roles/run-tempest/tasks/main.yaml
+++ b/roles/run-tempest/tasks/main.yaml
@@ -25,11 +25,11 @@
     target_branch: "{{ zuul.override_checkout }}"
   when: zuul.override_checkout is defined
 
-- name: Use stable branch upper-constraints till stable/victoria
+- name: Use stable branch upper-constraints till stable/wallaby
   set_fact:
     # TOX_CONSTRAINTS_FILE is new name, UPPER_CONSTRAINTS_FILE is old one, best to set both
     tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': stable_constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': stable_constraints_file}) }}"
-  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train", "stable/ussuri", "stable/victoria"]
+  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train", "stable/ussuri", "stable/victoria", "stable/wallaby"]
 
 - name: Use Configured upper-constraints for non-master Tempest
   set_fact:
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
index 99d8e2a..2fcd053 100644
--- a/tempest/api/compute/admin/test_volume.py
+++ b/tempest/api/compute/admin/test_volume.py
@@ -72,6 +72,12 @@
 class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
     """Test attaching scsi volume to server"""
 
+    # NOTE(gibi): https://bugs.launchpad.net/nova/+bug/2002951/comments/5 shows
+    # that calling _create_image_with_custom_property can cause excessive
+    # memory usage in the test executor as it downloads a glance image in
+    # memory. This is causing gate failures so the test is disabled. One
+    # potential fix is to do a chunked data download / upload loop instead.
+    @decorators.skip_because(bug="2002951", condition=True)
     @decorators.idempotent_id('777e468f-17ca-4da4-b93d-b7dbf56c0494')
     def test_attach_scsi_disk_with_config_drive(self):
         """Test the attach/detach volume with config drive/scsi disk
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index 0b96d9e..a8a344a 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -90,7 +90,6 @@
 from tempest import clients
 from tempest.cmd import cleanup_service
 from tempest.common import credentials_factory as credentials
-from tempest.common import identity
 from tempest import config
 from tempest.lib import exceptions
 
@@ -140,11 +139,6 @@
         self.dry_run_data = {}
         self.json_data = {}
 
-        self.admin_id = ""
-        self.admin_role_id = ""
-        self.admin_project_id = ""
-        self._init_admin_ids()
-
         # available services
         self.project_associated_services = (
             cleanup_service.get_project_associated_cleanup_services())
@@ -227,26 +221,6 @@
             svc = service(self.admin_mgr, **kwargs)
             svc.run()
 
-    def _init_admin_ids(self):
-        pr_cl = self.admin_mgr.projects_client
-        rl_cl = self.admin_mgr.roles_v3_client
-        rla_cl = self.admin_mgr.role_assignments_client
-        us_cl = self.admin_mgr.users_v3_client
-
-        project = identity.get_project_by_name(pr_cl,
-                                               CONF.auth.admin_project_name)
-        self.admin_project_id = project['id']
-        user = identity.get_user_by_project(us_cl, rla_cl,
-                                            self.admin_project_id,
-                                            CONF.auth.admin_username)
-        self.admin_id = user['id']
-
-        roles = rl_cl.list_roles()['roles']
-        for role in roles:
-            if role['name'] == CONF.identity.admin_role:
-                self.admin_role_id = role['id']
-                break
-
     def get_parser(self, prog_name):
         parser = super(TempestCleanup, self).get_parser(prog_name)
         parser.add_argument('--init-saved-state', action="store_true",
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 9d9fab7..4fdf6a4 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -109,6 +109,15 @@
         LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
         return nic.strip().strip(":").split('@')[0].lower()
 
+    def get_nic_ip_addresses(self, nic_name, ip_version=None):
+        cmd = "ip "
+        if ip_version:
+            cmd += "-%s " % ip_version
+        cmd += "-o addr | awk '/%s/ {print $4}'" % nic_name
+        ip_addresses = self.exec_command(cmd)
+        LOG.debug('(get_nic_ip_address): Command result: %s', ip_addresses)
+        return ip_addresses.strip().split()
+
     def _get_dns_servers(self):
         cmd = 'cat /etc/resolv.conf'
         resolve_file = self.exec_command(cmd).strip().split('\n')
@@ -145,15 +154,20 @@
         cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
         self.exec_command(cmd)
 
+    def _renew_lease_dhcpcd(self, fixed_ip=None):
+        """Renews DHCP lease via dhcpcd client. """
+        cmd = "sudo /sbin/dhcpcd --rebind"
+        self.exec_command(cmd)
+
     def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
         """Wrapper method for renewing DHCP lease via given client
 
         Supporting:
         * udhcpc
         * dhclient
+        * dhcpcd
         """
-        # TODO(yfried): add support for dhcpcd
-        supported_clients = ['udhcpc', 'dhclient']
+        supported_clients = ['udhcpc', 'dhclient', 'dhcpcd']
         if dhcp_client not in supported_clients:
             raise tempest.lib.exceptions.InvalidConfiguration(
                 '%s DHCP client unsupported' % dhcp_client)
diff --git a/tempest/config.py b/tempest/config.py
index d91fca4..00b394e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1200,13 +1200,12 @@
                help='Image container format'),
     cfg.DictOpt('img_properties', help='Glance image properties. '
                 'Use for custom images which require them'),
-    # TODO(yfried): add support for dhcpcd
     cfg.StrOpt('dhcp_client',
                default='udhcpc',
-               choices=["udhcpc", "dhclient", ""],
+               choices=["udhcpc", "dhclient", "dhcpcd", ""],
                help='DHCP client used by images to renew DCHP lease. '
                     'If left empty, update operation will be skipped. '
-                    'Supported clients: "udhcpc", "dhclient"'),
+                    'Supported clients: "udhcpc", "dhclient", "dhcpcd"'),
     cfg.StrOpt('protocol',
                default='icmp',
                choices=('icmp', 'tcp', 'udp'),
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index bb588be..7d54c1a 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -221,3 +221,10 @@
             # class is the caller
             owner.cleanup = owner.addClassResourceCleanup
             return MethodType(self.func, owner)
+
+
+def serial(cls):
+    """A decorator to mark a test class for serial execution"""
+    cls._serial = True
+    LOG.debug('marked %s for serial execution', cls.__name__)
+    return cls
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index cbe8c20..cbe4122 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -897,10 +897,17 @@
         self.check_remote_connectivity(ssh_client, dest=peer_address,
                                        nic=spoof_nic, should_succeed=True)
         # Set a mac address by making nic down temporary
+        spoof_ip_addresses = ssh_client.get_nic_ip_addresses(spoof_nic)
         cmd = ("sudo ip link set {nic} down;"
                "sudo ip link set dev {nic} address {mac};"
-               "sudo ip link set {nic} up").format(nic=spoof_nic,
-                                                   mac=spoof_mac)
+               "sudo ip link set {nic} up;"
+               "sudo ip address flush dev {nic};").format(nic=spoof_nic,
+                                                          mac=spoof_mac)
+        for ip_address in spoof_ip_addresses:
+            cmd += (
+                "sudo ip addr add {ip_address} dev {nic};"
+            ).format(ip_address=ip_address, nic=spoof_nic)
+
         ssh_client.exec_command(cmd)
 
         new_mac = ssh_client.get_mac_address(nic=spoof_nic)
diff --git a/tempest/serial_tests/__init__.py b/tempest/serial_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/serial_tests/__init__.py
diff --git a/tempest/serial_tests/api/__init__.py b/tempest/serial_tests/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/serial_tests/api/__init__.py
diff --git a/tempest/serial_tests/api/admin/__init__.py b/tempest/serial_tests/api/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/serial_tests/api/admin/__init__.py
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/serial_tests/api/admin/test_aggregates.py
similarity index 99%
rename from tempest/api/compute/admin/test_aggregates.py
rename to tempest/serial_tests/api/admin/test_aggregates.py
index a6c6535..2ca91aa 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/serial_tests/api/admin/test_aggregates.py
@@ -26,6 +26,7 @@
 CONF = config.CONF
 
 
+@decorators.serial
 class AggregatesAdminTestBase(base.BaseV2ComputeAdminTest):
     """Tests Aggregates API that require admin privileges"""
 
diff --git a/tempest/serial_tests/scenario/__init__.py b/tempest/serial_tests/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/serial_tests/scenario/__init__.py
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/serial_tests/scenario/test_aggregates_basic_ops.py
similarity index 99%
rename from tempest/scenario/test_aggregates_basic_ops.py
rename to tempest/serial_tests/scenario/test_aggregates_basic_ops.py
index 58e234f..ba31d84 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/serial_tests/scenario/test_aggregates_basic_ops.py
@@ -20,6 +20,7 @@
 from tempest.scenario import manager
 
 
+@decorators.serial
 class TestAggregatesBasicOps(manager.ScenarioTest):
     """Creates an aggregate within an availability zone
 
diff --git a/tempest/test.py b/tempest/test.py
index dba2695..d49458e 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -18,7 +18,9 @@
 import sys
 
 import debtcollector.moves
+from fasteners import process_lock
 import fixtures
+from oslo_concurrency import lockutils
 from oslo_log import log as logging
 import testtools
 
@@ -123,6 +125,23 @@
     # A way to adjust slow test classes
     TIMEOUT_SCALING_FACTOR = 1
 
+    # An interprocess lock to implement serial test execution if requested.
+    # The serial test classes are the writers as only one of them can be
+    # executed. The rest of the test classes are the readers as many of them
+    # can be run in parallel.
+    # Only classes can be decorated with @serial decorator not individual test
+    # cases as tempest allows test class level resource setup which could
+    # interfere with serialized execution on test cases level. I.e. the class
+    # setup of one of the test cases could run before taking a test case level
+    # lock.
+    # We cannot init the lock here as external lock needs oslo configuration
+    # to be loaded first to get the lock_path
+    serial_rw_lock = None
+
+    # Defines if the tests in this class should be run without any parallelism
+    # Use the @serial decorator on your test class to indicate such requirement
+    _serial = False
+
     @classmethod
     def _reset_class(cls):
         cls.__setup_credentials_called = False
@@ -134,14 +153,33 @@
         cls._teardowns = []
 
     @classmethod
+    def is_serial_execution_requested(cls):
+        return cls._serial
+
+    @classmethod
     def setUpClass(cls):
         cls.__setupclass_called = True
+
+        if cls.serial_rw_lock is None:
+            path = os.path.join(
+                lockutils.get_lock_path(CONF), 'tempest-serial-rw-lock')
+            cls.serial_rw_lock = (
+                process_lock.InterProcessReaderWriterLock(path)
+            )
+
         # Reset state
         cls._reset_class()
         # It should never be overridden by descendants
         if hasattr(super(BaseTestCase, cls), 'setUpClass'):
             super(BaseTestCase, cls).setUpClass()
         try:
+            if cls.is_serial_execution_requested():
+                LOG.debug('%s taking the write lock', cls.__name__)
+                cls.serial_rw_lock.acquire_write_lock()
+                LOG.debug('%s took the write lock', cls.__name__)
+            else:
+                cls.serial_rw_lock.acquire_read_lock()
+
             cls.skip_checks()
 
             if not cls.__skip_checks_called:
@@ -184,35 +222,44 @@
         # If there was no exception during setup we shall re-raise the first
         # exception in teardown
         re_raise = (etype is None)
-        while cls._teardowns:
-            name, teardown = cls._teardowns.pop()
-            # Catch any exception in tearDown so we can re-raise the original
-            # exception at the end
-            try:
-                teardown()
-                if name == 'resources':
-                    if not cls.__resource_cleanup_called:
-                        raise RuntimeError(
-                            "resource_cleanup for %s did not call the "
-                            "super's resource_cleanup" % cls.__name__)
-            except Exception as te:
-                sys_exec_info = sys.exc_info()
-                tetype = sys_exec_info[0]
-                # TODO(andreaf): Resource cleanup is often implemented by
-                # storing an array of resources at class level, and cleaning
-                # them up during `resource_cleanup`.
-                # In case of failure during setup, some resource arrays might
-                # not be defined at all, in which case the cleanup code might
-                # trigger an AttributeError. In such cases we log
-                # AttributeError as info instead of exception. Once all
-                # cleanups are migrated to addClassResourceCleanup we can
-                # remove this.
-                if tetype is AttributeError and name == 'resources':
-                    LOG.info("tearDownClass of %s failed: %s", name, te)
-                else:
-                    LOG.exception("teardown of %s failed: %s", name, te)
-                if not etype:
-                    etype, value, trace = sys_exec_info
+        try:
+            while cls._teardowns:
+                name, teardown = cls._teardowns.pop()
+                # Catch any exception in tearDown so we can re-raise the
+                # original exception at the end
+                try:
+                    teardown()
+                    if name == 'resources':
+                        if not cls.__resource_cleanup_called:
+                            raise RuntimeError(
+                                "resource_cleanup for %s did not call the "
+                                "super's resource_cleanup" % cls.__name__)
+                except Exception as te:
+                    sys_exec_info = sys.exc_info()
+                    tetype = sys_exec_info[0]
+                    # TODO(andreaf): Resource cleanup is often implemented by
+                    # storing an array of resources at class level, and
+                    # cleaning them up during `resource_cleanup`.
+                    # In case of failure during setup, some resource arrays
+                    # might not be defined at all, in which case the cleanup
+                    # code might trigger an AttributeError. In such cases we
+                    # log AttributeError as info instead of exception. Once all
+                    # cleanups are migrated to addClassResourceCleanup we can
+                    # remove this.
+                    if tetype is AttributeError and name == 'resources':
+                        LOG.info("tearDownClass of %s failed: %s", name, te)
+                    else:
+                        LOG.exception("teardown of %s failed: %s", name, te)
+                    if not etype:
+                        etype, value, trace = sys_exec_info
+        finally:
+            if cls.is_serial_execution_requested():
+                LOG.debug('%s releasing the write lock', cls.__name__)
+                cls.serial_rw_lock.release_write_lock()
+                LOG.debug('%s released the write lock', cls.__name__)
+            else:
+                cls.serial_rw_lock.release_read_lock()
+
         # If exceptions were raised during teardown, and not before, re-raise
         # the first one
         if re_raise and etype is not None:
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index a19f20b..679d58b 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -25,7 +25,7 @@
     base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
     base_path = os.path.split(base_path)[0]
     # Load local tempest tests
-    for test_dir in ['api', 'scenario']:
+    for test_dir in ['api', 'scenario', 'serial_tests']:
         full_test_dir = os.path.join(base_path, 'tempest', test_dir)
         if not pattern:
             suite.addTests(loader.discover(full_test_dir,
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index cbb81e2..26e8079 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -17,12 +17,14 @@
 import unittest
 from unittest import mock
 
+from oslo_concurrency import lockutils
 from oslo_config import cfg
 import testtools
 
 from tempest import clients
 from tempest import config
 from tempest.lib.common import validation_resources as vr
+from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.compute import base_compute_client
 from tempest.lib.services.placement import base_placement_client
@@ -33,6 +35,8 @@
 from tempest.tests.lib import fake_credentials
 from tempest.tests.lib.services import registry_fixture
 
+CONF = config.CONF
+
 
 class LoggingTestResult(testtools.TestResult):
 
@@ -594,6 +598,52 @@
             str(log[0][2]['traceback']).replace('\n', ' '),
             RuntimeError.__name__ + ': .* ' + OverridesSetup.__name__)
 
+    @mock.patch.object(test.process_lock, 'InterProcessReaderWriterLock')
+    def test_serial_execution_if_requested(self, mock_lock):
+
+        @decorators.serial
+        class SerialTests(self.parent_test):
+            pass
+
+        class ParallelTests(self.parent_test):
+            pass
+
+        @decorators.serial
+        class SerialTests2(self.parent_test):
+            pass
+
+        suite = unittest.TestSuite(
+            (SerialTests(), ParallelTests(), SerialTests2()))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+
+        expected_lock_path = os.path.join(
+            lockutils.get_lock_path(CONF), 'tempest-serial-rw-lock')
+
+        # We except that each test class has a lock with the _same_ external
+        # path so that if they would run by different processes they would
+        # still use the same lock
+        # Also we expect that each serial class takes and releases the
+        # write-lock while each non-serial class takes and releases the
+        # read-lock.
+        self.assertEqual(
+            [
+                mock.call(expected_lock_path),
+                mock.call().acquire_write_lock(),
+                mock.call().release_write_lock(),
+
+                mock.call(expected_lock_path),
+                mock.call().acquire_read_lock(),
+                mock.call().release_read_lock(),
+
+                mock.call(expected_lock_path),
+                mock.call().acquire_write_lock(),
+                mock.call().release_write_lock(),
+            ],
+            mock_lock.mock_calls
+        )
+
 
 class TestTempestBaseTestClassFixtures(base.TestCase):
 
@@ -750,6 +800,11 @@
 
 class TestAPIMicroversionTest1(test.BaseTestCase):
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.useFixture(fake_config.ConfigFixture())
+        config.TempestConfigPrivate = fake_config.FakePrivate
+
     @classmethod
     def resource_setup(cls):
         super(TestAPIMicroversionTest1, cls).resource_setup()
@@ -812,6 +867,11 @@
 
 class TestAPIMicroversionTest2(test.BaseTestCase):
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.useFixture(fake_config.ConfigFixture())
+        config.TempestConfigPrivate = fake_config.FakePrivate
+
     @classmethod
     def resource_setup(cls):
         super(TestAPIMicroversionTest2, cls).resource_setup()
diff --git a/tox.ini b/tox.ini
index c784293..618f9e0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -124,7 +124,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' {posargs}
 
 [testenv:full-parallel]
 envdir = .tox/tempest
@@ -135,7 +135,7 @@
 # The regex below is used to select all tempest scenario and including the non slow api tests
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(^tempest\.scenario.*)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
+    tempest run --regex '(^tempest\.scenario.*)|(^tempest\.serial_tests)|(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
 
 [testenv:api-microversion-tests]
 envdir = .tox/tempest
@@ -160,7 +160,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' --exclude-list ./tools/tempest-integrated-gate-networking-exclude-list.txt {posargs}
 
 [testenv:integrated-compute]
 envdir = .tox/tempest
@@ -173,7 +173,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' --exclude-list ./tools/tempest-integrated-gate-compute-exclude-list.txt {posargs}
 
 [testenv:integrated-placement]
 envdir = .tox/tempest
@@ -186,7 +186,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' --exclude-list ./tools/tempest-integrated-gate-placement-exclude-list.txt {posargs}
 
 [testenv:integrated-storage]
 envdir = .tox/tempest
@@ -199,7 +199,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' --exclude-list ./tools/tempest-integrated-gate-storage-exclude-list.txt {posargs}
 
 [testenv:integrated-object-storage]
 envdir = .tox/tempest
@@ -212,7 +212,7 @@
 commands =
     find . -type f -name "*.pyc" -delete
     tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
-    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)|(^tempest\.serial_tests)' --exclude-list ./tools/tempest-integrated-gate-object-storage-exclude-list.txt {posargs}
 
 [testenv:full-serial]
 envdir = .tox/tempest
@@ -225,7 +225,7 @@
 # FIXME: We can replace it with the `--exclude-regex` option to exclude tests now.
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
+    tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|serial_tests))' {posargs}
 
 [testenv:scenario]
 envdir = .tox/tempest
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 5adf89e..f379041 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -383,16 +383,20 @@
         - tempest-integrated-networking
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
     gate:
       jobs:
         - grenade
         - tempest-integrated-networking
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
 
 - project-template:
     name: integrated-gate-compute
@@ -416,13 +420,15 @@
             branches: ^stable/(wallaby|xena|yoga).*$
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
     gate:
       jobs:
         - tempest-integrated-compute
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
     periodic-weekly:
       jobs:
         # centos-9-stream is tested from zed release onwards
@@ -444,6 +450,8 @@
         - tempest-integrated-placement
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
             branches: ^(?!stable/ussuri).*$
     gate:
@@ -452,8 +460,10 @@
         - tempest-integrated-placement
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
 
 - project-template:
     name: integrated-gate-storage
@@ -470,16 +480,20 @@
         - tempest-integrated-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
     gate:
       jobs:
         - grenade
         - tempest-integrated-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
 
 - project-template:
     name: integrated-gate-object-storage
@@ -494,13 +508,17 @@
         - tempest-integrated-object-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$
     gate:
       jobs:
         - grenade
         - tempest-integrated-object-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
+        # and job is broken on wallaby branch due to the issue
+        # described in https://review.opendev.org/872341
         - openstacksdk-functional-devstack:
-            branches: ^(?!stable/ussuri).*$
+            branches: ^(?!stable/(ussuri|wallaby)).*$