Merge "Expand accounst.yaml sample"
diff --git a/releasenotes/notes/image-wait-multiple-79c55305b584b1ba.yaml b/releasenotes/notes/image-wait-multiple-79c55305b584b1ba.yaml
new file mode 100644
index 0000000..6f63ebd
--- /dev/null
+++ b/releasenotes/notes/image-wait-multiple-79c55305b584b1ba.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    The wait_for_image_status() waiter now allows a list of status values
+    instead of just a string, and returns the state the image was in when we
+    stopped waiting.
diff --git a/requirements.txt b/requirements.txt
index d2f13a5..a1eff53 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,3 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
 jsonschema>=3.2.0 # MIT
diff --git a/tempest/api/compute/admin/test_servers_on_multinodes.py b/tempest/api/compute/admin/test_servers_on_multinodes.py
index b5ee9b1..c5d5b19 100644
--- a/tempest/api/compute/admin/test_servers_on_multinodes.py
+++ b/tempest/api/compute/admin/test_servers_on_multinodes.py
@@ -150,6 +150,15 @@
         compute.shelve_server(self.servers_client, server['id'],
                               force_shelve_offload=True)
 
+        # Work around https://bugs.launchpad.net/nova/+bug/2045785
+        # This can be removed when ^ is fixed.
+        def _check_server_host_is_none():
+            server_details = self.os_admin.servers_client.show_server(
+                server['id'])
+            self.assertIsNone(server_details['server']['OS-EXT-SRV-ATTR:host'])
+
+        self.wait_for(_check_server_host_is_none)
+
         self.os_admin.servers_client.unshelve_server(
             server['id'],
             body={'unshelve': {'host': host}}
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index c911039..10153bb 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -45,15 +45,6 @@
         try:
             self.validation_resources = self.get_class_validation_resources(
                 self.os_primary)
-            # _test_rebuild_server test compares ip address attached to the
-            # server before and after the rebuild, in order to avoid
-            # a situation when a newly created server doesn't have a floating
-            # ip attached at the beginning of the test_rebuild_server let's
-            # make sure right here the floating ip is attached
-            waiters.wait_for_server_floating_ip(
-                self.client,
-                self.client.show_server(self.server_id)['server'],
-                self.validation_resources['floating_ip'])
             waiters.wait_for_server_status(self.client,
                                            self.server_id, 'ACTIVE')
         except lib_exc.NotFound:
@@ -127,7 +118,7 @@
             self.assertGreater(new_boot_time, boot_time,
                                '%s > %s' % (new_boot_time, boot_time))
 
-    def _test_rebuild_server(self, server_id):
+    def _test_rebuild_server(self, server_id, **kwargs):
         # Get the IPs the server has before rebuilding it
         original_addresses = (self.client.show_server(server_id)['server']
                               ['addresses'])
@@ -166,11 +157,17 @@
             # 3.Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in
             #   ~/.ssh/ (if allowed).
             # 4.Plain username/password auth, if a password was given.
+
+            if 'validation_resources' in kwargs:
+                validation_resources = kwargs['validation_resources']
+            else:
+                validation_resources = self.validation_resources
+
             linux_client = remote_client.RemoteClient(
-                self.get_server_ip(rebuilt_server, self.validation_resources),
+                self.get_server_ip(rebuilt_server, validation_resources),
                 self.ssh_alt_user,
                 password,
-                self.validation_resources['keypair']['private_key'],
+                validation_resources['keypair']['private_key'],
                 server=rebuilt_server,
                 servers_client=self.client)
             linux_client.validate_authentication()
@@ -267,17 +264,32 @@
         The server should be rebuilt using the provided image and data.
         """
         tenant_network = self.get_tenant_network()
+        validation_resources = self.get_test_validation_resources(
+            self.os_primary)
         _, servers = compute.create_test_server(
             self.os_primary,
-            wait_until='ACTIVE',
+            wait_until='SSHABLE',
+            validatable=True,
+            validation_resources=validation_resources,
             tenant_network=tenant_network)
         server = servers[0]
+        # _test_rebuild_server test compares ip address attached to the
+        # server before and after the rebuild, in order to avoid
+        # a situation when a newly created server doesn't have a floating
+        # ip attached at the beginning of the test_rebuild_server let's
+        # make sure right here the floating ip is attached
+        waiters.wait_for_server_floating_ip(
+            self.client,
+            server,
+            validation_resources['floating_ip'])
 
         self.addCleanup(waiters.wait_for_server_termination,
                         self.client, server['id'])
         self.addCleanup(self.client.delete_server, server['id'])
 
-        self._test_rebuild_server(server_id=server['id'])
+        self._test_rebuild_server(
+            server_id=server['id'],
+            validation_resources=validation_resources)
 
     @decorators.idempotent_id('1499262a-9328-4eda-9068-db1ac57498d2')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
@@ -465,7 +477,9 @@
         self.attach_volume(server, volume)
 
         # run general rebuild test
-        self._test_rebuild_server(server_id=server['id'])
+        self._test_rebuild_server(
+            server_id=server['id'],
+            validation_resources=validation_resources)
 
         # make sure the volume is attached to the instance after rebuild
         vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index b5b3c5d..96218bb 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -128,7 +128,7 @@
         for g in user_groups:
             if 'membership_expires_at' in g:
                 self.assertIsNone(g['membership_expires_at'])
-                del(g['membership_expires_at'])
+                del g['membership_expires_at']
         self.assertEqual(sorted(groups, key=lambda k: k['name']),
                          sorted(user_groups, key=lambda k: k['name']))
         self.assertEqual(2, len(user_groups))
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 99742cc..5bbd50e 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -15,6 +15,8 @@
 
 import netaddr
 
+from tempest.common import utils as common_utils
+from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
 from tempest.lib.common.utils import data_utils
@@ -226,6 +228,18 @@
                 subnet_id=i['fixed_ips'][0]['subnet_id'])
         cls.routers_client.delete_router(router['id'])
 
+    def remove_router_interface(self, router_id, port_id, subnet_id=None):
+        # NOTE: with DVR and without a VM port, it is not possible to know
+        # what agent will host the router interface thus won't be bound.
+        if not common_utils.is_extension_enabled('dvr', 'network'):
+            waiters.wait_for_port_status(client=self.ports_client,
+                                         port_id=port_id, status='ACTIVE')
+        if subnet_id:
+            kwargs = {'subnet_id': subnet_id}
+        else:
+            kwargs = {'port_id': port_id}
+        self.routers_client.remove_router_interface(router_id, **kwargs)
+
 
 class BaseAdminNetworkTest(BaseNetworkTest):
 
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index aaedba2..fedf2f4 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -18,7 +18,6 @@
 
 from tempest.api.network import base
 from tempest.common import utils
-from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -33,22 +32,11 @@
     def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
         interface = self.routers_client.add_router_interface(
             router_id, subnet_id=subnet_id)
-        self.addCleanup(self._remove_router_interface_with_subnet_id,
-                        router_id, subnet_id, interface['port_id'])
+        self.addCleanup(self.remove_router_interface,
+                        router_id, interface['port_id'], subnet_id=subnet_id)
         self.assertEqual(subnet_id, interface['subnet_id'])
         return interface
 
-    def _remove_router_interface_with_subnet_id(self, router_id, subnet_id,
-                                                port_id):
-        # NOTE: with DVR and without a VM port, it is not possible to know
-        # what agent will host the router interface thus won't be bound.
-        if not utils.is_extension_enabled('dvr', 'network'):
-            waiters.wait_for_port_status(client=self.ports_client,
-                                         port_id=port_id, status='ACTIVE')
-        body = self.routers_client.remove_router_interface(router_id,
-                                                           subnet_id=subnet_id)
-        self.assertEqual(subnet_id, body['subnet_id'])
-
     @classmethod
     def skip_checks(cls):
         super(RoutersTest, cls).skip_checks()
@@ -113,8 +101,9 @@
         # Add router interface with subnet id
         interface = self.routers_client.add_router_interface(
             router['id'], subnet_id=subnet['id'])
-        self.addCleanup(self._remove_router_interface_with_subnet_id,
-                        router['id'], subnet['id'], interface['port_id'])
+        self.addCleanup(self.remove_router_interface,
+                        router['id'], interface['port_id'],
+                        subnet_id=subnet['id'])
         self.assertIn('subnet_id', interface.keys())
         self.assertIn('port_id', interface.keys())
         # Verify router id is equal to device id in port details
@@ -192,8 +181,9 @@
             # Add router interface with subnet id
             interface = self.create_router_interface(router['id'],
                                                      subnet['id'])
-            self.addCleanup(self._remove_router_interface_with_subnet_id,
-                            router['id'], subnet['id'], interface['port_id'])
+            self.addCleanup(self.remove_router_interface,
+                            router['id'], interface['port_id'],
+                            subnet_id=subnet['id'])
             cidr = netaddr.IPNetwork(subnet['cidr'])
             next_hop = str(cidr[2])
             destination = str(subnet['cidr'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 50ba977..299e0e9 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -77,8 +77,9 @@
         subnet02 = self.create_subnet(network02)
         interface = self.routers_client.add_router_interface(
             self.router['id'], subnet_id=subnet01['id'])
-        self.addCleanup(self.routers_client.remove_router_interface,
-                        self.router['id'], subnet_id=subnet01['id'])
+        self.addCleanup(self.remove_router_interface,
+                        self.router['id'], interface['port_id'],
+                        subnet_id=subnet01['id'])
         self.assertEqual(subnet01['id'], interface['subnet_id'])
         self.assertRaises(lib_exc.BadRequest,
                           self.routers_client.add_router_interface,
@@ -89,10 +90,11 @@
     @decorators.idempotent_id('04df80f9-224d-47f5-837a-bf23e33d1c20')
     def test_router_remove_interface_in_use_returns_409(self):
         """Test removing in-use interface from router"""
-        self.routers_client.add_router_interface(self.router['id'],
-                                                 subnet_id=self.subnet['id'])
-        self.addCleanup(self.routers_client.remove_router_interface,
-                        self.router['id'], subnet_id=self.subnet['id'])
+        interface = self.routers_client.add_router_interface(
+            self.router['id'], subnet_id=self.subnet['id'])
+        self.addCleanup(self.remove_router_interface,
+                        self.router['id'], interface['port_id'],
+                        subnet_id=self.subnet['id'])
         self.assertRaises(lib_exc.Conflict,
                           self.routers_client.delete_router,
                           self.router['id'])
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 5b17afb..0f032c6 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -143,6 +143,7 @@
 
         # Get encryption type
         encrypt_type_id = encryption_type['volume_type_id']
+        encryption_id = encryption_type['encryption_id']
         fetched_encryption_type = (
             self.admin_encryption_types_client.show_encryption_type(
                 encrypt_type_id))
@@ -157,7 +158,7 @@
                          'cipher': 'aes-xts-plain64',
                          'control_location': 'back-end'}
         self.admin_encryption_types_client.update_encryption_type(
-            encrypt_type_id, **update_kwargs)
+            encrypt_type_id, encryption_id, **update_kwargs)
         updated_encryption_type = (
             self.admin_encryption_types_client.show_encryption_type(
                 encrypt_type_id))
@@ -174,7 +175,7 @@
 
         # Delete encryption type
         self.admin_encryption_types_client.delete_encryption_type(
-            encrypt_type_id)
+            encrypt_type_id, encryption_id)
         self.admin_encryption_types_client.wait_for_resource_deletion(
             encrypt_type_id)
         deleted_encryption_type = (
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index dd18190..79cc09c 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -59,14 +59,14 @@
         output = self.exec_command(command)
         selected = []
         pos = None
-        for l in output.splitlines():
-            if pos is None and l.find("TYPE") > 0:
-                pos = l.find("TYPE")
+        for line in output.splitlines():
+            if pos is None and line.find("TYPE") > 0:
+                pos = line.find("TYPE")
                 # Show header line too
-                selected.append(l)
+                selected.append(line)
             # lsblk lists disk type in a column right-aligned with TYPE
-            elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk":
-                selected.append(l)
+            elif pos is not None and pos > 0 and line[pos:pos + 4] == "disk":
+                selected.append(line)
 
         if selected:
             return "\n".join(selected)
@@ -121,9 +121,9 @@
     def _get_dns_servers(self):
         cmd = 'cat /etc/resolv.conf'
         resolve_file = self.exec_command(cmd).strip().split('\n')
-        entries = (l.split() for l in resolve_file)
-        dns_servers = [l[1] for l in entries
-                       if len(l) and l[0] == 'nameserver']
+        entries = (line.split() for line in resolve_file)
+        dns_servers = [line[1] for line in entries
+                       if len(line) and line[0] == 'nameserver']
         return dns_servers
 
     def get_dns_servers(self, timeout=5):
diff --git a/tempest/common/utils/net_downtime.py b/tempest/common/utils/net_downtime.py
index 6f2a947..ec1a4c8 100644
--- a/tempest/common/utils/net_downtime.py
+++ b/tempest/common/utils/net_downtime.py
@@ -50,10 +50,10 @@
 
 
 class NetDowntimeMeter(fixtures.Fixture):
-    def __init__(self, dest_ip, interval='0.2'):
+    def __init__(self, dest_ip, interval=0.2):
         self.dest_ip = dest_ip
         # Note: for intervals lower than 0.2 ping requires root privileges
-        self.interval = interval
+        self.interval = float(interval)
         self.ping_process = None
 
     def _setUp(self):
@@ -61,18 +61,18 @@
 
     def start_background_pinger(self):
         cmd = ['ping', '-q', '-s1']
-        cmd.append('-i{}'.format(self.interval))
+        cmd.append('-i%g' % self.interval)
         cmd.append(self.dest_ip)
-        LOG.debug("Starting background pinger to '{}' with interval {}".format(
-            self.dest_ip, self.interval))
+        LOG.debug("Starting background pinger to '%s' with interval %g",
+                  self.dest_ip, self.interval)
         self.ping_process = subprocess.Popen(
             cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         self.addCleanup(self.cleanup)
 
     def cleanup(self):
         if self.ping_process and self.ping_process.poll() is None:
-            LOG.debug('Terminating background pinger with pid {}'.format(
-                self.ping_process.pid))
+            LOG.debug('Terminating background pinger with pid %d',
+                      self.ping_process.pid)
             self.ping_process.terminate()
         self.ping_process = None
 
@@ -83,7 +83,7 @@
         output = self.ping_process.stderr.readline().strip().decode('utf-8')
         if output and len(output.split()[0].split('/')) == 2:
             succ, total = output.split()[0].split('/')
-            return (int(total) - int(succ)) * float(self.interval)
+            return (int(total) - int(succ)) * self.interval
         else:
             LOG.warning('Unexpected output obtained from the pinger: %s',
                         output)
@@ -115,8 +115,9 @@
         chmod_cmd = 'chmod +x {}'.format(self.script_path)
         self.ssh_client.exec_command(';'.join((echo_cmd, chmod_cmd)))
         LOG.debug('script created: %s', self.script_path)
-        LOG.debug(self.ssh_client.exec_command(
-            'cat {}'.format(self.script_path)))
+        output = self.ssh_client.exec_command(
+            'cat {}'.format(self.script_path))
+        LOG.debug('script content: %s', output)
 
     def run_metadata_script(self):
         self.ssh_client.exec_command('{} > {} &'.format(self.script_path,
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 9e97f47..b4312b7 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -154,13 +154,21 @@
 
 
 def wait_for_image_status(client, image_id, status):
-    """Waits for an image to reach a given status.
+    """Waits for an image to reach a given status (or list of them).
 
     The client should have a show_image(image_id) method to get the image.
     The client should also have build_interval and build_timeout attributes.
+
+    status can be either a string or a list of strings that constitute a
+    terminal state that we will return.
     """
     show_image = client.show_image
 
+    if isinstance(status, str):
+        terminal_status = [status]
+    else:
+        terminal_status = status
+
     current_status = 'An unknown status'
     start = int(time.time())
     while int(time.time()) - start < client.build_timeout:
@@ -171,8 +179,8 @@
             image = image['image']
 
         current_status = image['status']
-        if current_status == status:
-            return
+        if current_status in terminal_status:
+            return current_status
         if current_status.lower() == 'killed':
             raise exceptions.ImageKilledException(image_id=image_id,
                                                   status=status)
@@ -184,7 +192,7 @@
     message = ('Image %(image_id)s failed to reach %(status)s state '
                '(current state %(current_status)s) within the required '
                'time (%(timeout)s s).' % {'image_id': image_id,
-                                          'status': status,
+                                          'status': ','.join(terminal_status),
                                           'current_status': current_status,
                                           'timeout': client.build_timeout})
     caller = test_utils.find_test_caller()
@@ -327,8 +335,7 @@
     # Check if image have last store location
     if len(available_stores) == 1:
         exc_cls = lib_exc.OtherRestClientException
-        message = ('Delete from last store location not allowed'
-                   % (image, image_store_deleted))
+        message = 'Delete from last store location not allowed'
         raise exc_cls(message)
     start = int(time.time())
     while int(time.time()) - start < client.build_timeout:
@@ -548,7 +555,7 @@
     interface_status = body['port_state']
     start = int(time.time())
 
-    while(interface_status != status):
+    while interface_status != status:
         time.sleep(client.build_interval)
         body = (client.show_interface(server_id, port_id)
                 ['interfaceAttachment'])
diff --git a/tempest/config.py b/tempest/config.py
index 36f0152..7719720 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1121,8 +1121,9 @@
                help='Volume types used for data volumes. Multiple volume '
                     'types can be assigned.'),
     cfg.BoolOpt('enable_volume_image_dep_tests',
+                deprecated_name='volume_image_dep_tests',
                 default=True,
-                help='Run tests for dependencies between images, volumes'
+                help='Run tests for dependencies between images, volumes '
                 'and instance snapshots')
 ]
 
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 1c9c55b..c81ec03 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -16,7 +16,6 @@
 import re
 
 from hacking import core
-import pycodestyle
 
 
 PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
@@ -40,22 +39,22 @@
 
 
 @core.flake8ext
-def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
+def import_no_clients_in_api_and_scenario_tests(logical_line, filename):
     """Check for client imports from tempest/api & tempest/scenario tests
 
     T102: Cannot import OpenStack python clients
     """
 
     if "tempest/api" in filename or "tempest/scenario" in filename:
-        res = PYTHON_CLIENT_RE.match(physical_line)
+        res = PYTHON_CLIENT_RE.match(logical_line)
         if res:
-            return (physical_line.find(res.group(1)),
+            return (logical_line.find(res.group(1)),
                     ("T102: python clients import not allowed"
                      " in tempest/api/* or tempest/scenario/* tests"))
 
 
 @core.flake8ext
-def scenario_tests_need_service_tags(physical_line, filename,
+def scenario_tests_need_service_tags(logical_line, filename,
                                      previous_logical):
     """Check that scenario tests have service tags
 
@@ -63,28 +62,28 @@
     """
 
     if 'tempest/scenario/' in filename and '/test_' in filename:
-        if TEST_DEFINITION.match(physical_line):
+        if TEST_DEFINITION.match(logical_line):
             if not SCENARIO_DECORATOR.match(previous_logical):
-                return (physical_line.find('def'),
+                return (logical_line.find('def'),
                         "T104: Scenario tests require a service decorator")
 
 
 @core.flake8ext
-def no_setup_teardown_class_for_tests(physical_line, filename):
+def no_setup_teardown_class_for_tests(logical_line, filename, noqa):
 
-    if pycodestyle.noqa(physical_line):
+    if noqa:
         return
 
     if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
         return
 
-    if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
-        return (physical_line.find('def'),
+    if SETUP_TEARDOWN_CLASS_DEFINITION.match(logical_line):
+        return (logical_line.find('def'),
                 "T105: (setUp|tearDown)Class can not be used in tests")
 
 
 @core.flake8ext
-def service_tags_not_in_module_path(physical_line, filename):
+def service_tags_not_in_module_path(logical_line, filename):
     """Check that a service tag isn't in the module path
 
     A service tag should only be added if the service name isn't already in
@@ -96,14 +95,14 @@
     # created for services like heat which would cause false negatives for
     # those tests, so just exclude the scenario tests.
     if 'tempest/scenario' not in filename:
-        matches = SCENARIO_DECORATOR.match(physical_line)
+        matches = SCENARIO_DECORATOR.match(logical_line)
         if matches:
             services = matches.group(1).split(',')
             for service in services:
                 service_name = service.strip().strip("'")
                 modulepath = os.path.split(filename)[0]
                 if service_name in modulepath:
-                    return (physical_line.find(service_name),
+                    return (logical_line.find(service_name),
                             "T107: service tag should not be in path")
 
 
@@ -140,28 +139,27 @@
                "decorators.skip_because from tempest.lib")
 
 
-def _common_service_clients_check(logical_line, physical_line, filename):
+def _common_service_clients_check(logical_line, filename, noqa):
+    if noqa:
+        return False
+
     if not re.match('tempest/(lib/)?services/.*', filename):
         return False
 
-    if not METHOD.match(physical_line):
-        return False
-
-    if pycodestyle.noqa(physical_line):
+    if not METHOD.match(logical_line):
         return False
 
     return True
 
 
 @core.flake8ext
-def get_resources_on_service_clients(physical_line, logical_line, filename,
-                                     line_number, lines):
+def get_resources_on_service_clients(logical_line, filename,
+                                     line_number, lines, noqa):
     """Check that service client names of GET should be consistent
 
     T110
     """
-    if not _common_service_clients_check(logical_line, physical_line,
-                                         filename):
+    if not _common_service_clients_check(logical_line, filename, noqa):
         return
 
     for line in lines[line_number:]:
@@ -182,14 +180,13 @@
 
 
 @core.flake8ext
-def delete_resources_on_service_clients(physical_line, logical_line, filename,
-                                        line_number, lines):
+def delete_resources_on_service_clients(logical_line, filename,
+                                        line_number, lines, noqa):
     """Check that service client names of DELETE should be consistent
 
     T111
     """
-    if not _common_service_clients_check(logical_line, physical_line,
-                                         filename):
+    if not _common_service_clients_check(logical_line, filename, noqa):
         return
 
     for line in lines[line_number:]:
@@ -262,7 +259,7 @@
             'oslo_config' in logical_line):
         msg = ('T114: tempest.lib can not have any dependency on tempest '
                'config.')
-        yield(0, msg)
+        yield (0, msg)
 
 
 @core.flake8ext
@@ -281,7 +278,7 @@
 
     if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
         msg = 'T115: All admin tests should exist under admin path.'
-        yield(0, msg)
+        yield (0, msg)
 
 
 @core.flake8ext
@@ -293,11 +290,11 @@
     result = EX_ATTRIBUTE.search(logical_line)
     msg = ("[T116] Unsupported 'message' Exception attribute in PY3")
     if result:
-        yield(0, msg)
+        yield (0, msg)
 
 
 @core.flake8ext
-def negative_test_attribute_always_applied_to_negative_tests(physical_line,
+def negative_test_attribute_always_applied_to_negative_tests(logical_line,
                                                              filename):
     """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
 
@@ -307,13 +304,13 @@
 
     if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
 
-        if NEGATIVE_TEST_DECORATOR.match(physical_line):
+        if NEGATIVE_TEST_DECORATOR.match(logical_line):
             _HAVE_NEGATIVE_DECORATOR = True
             return
 
-        if TEST_DEFINITION.match(physical_line):
+        if TEST_DEFINITION.match(logical_line):
             if not _HAVE_NEGATIVE_DECORATOR:
-                return (
+                yield (
                     0, "T117: Must apply `@decorators.attr(type=['negative'])`"
                        " to all negative API tests"
                 )
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index d163968..5bdcecd 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -60,6 +60,14 @@
             retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingProxyHttp, self).request(method, url, retries=retry,
                                                   *args, **new_kwargs)
+
+        # Clearing the pool is necessary to free memory that holds certificates
+        # loaded by the HTTPConnection class in urllib3. This line can be
+        # removed once we require a newer version of urllib3 (e.g., 2.2.3) that
+        # does not retain certificates in memory for each HTTPConnection
+        # managed by the PoolManager.
+        self.clear()
+
         if not kwargs.get('preload_content', True):
             # This means we asked urllib3 for streaming content, so we
             # need to return the raw response and not read any data yet
@@ -114,6 +122,14 @@
             retry = urllib3.util.Retry(redirect=False)
         r = super(ClosingHttp, self).request(method, url, retries=retry,
                                              *args, **new_kwargs)
+
+        # Clearing the pool is necessary to free memory that holds certificates
+        # loaded by the HTTPConnection class in urllib3. This line can be
+        # removed once we require a newer version of urllib3 (e.g., 2.2.3) that
+        # does not retain certificates in memory for each HTTPConnection
+        # managed by the PoolManager.
+        self.clear()
+
         if not kwargs.get('preload_content', True):
             # This means we asked urllib3 for streaming content, so we
             # need to return the raw response and not read any data yet
diff --git a/tempest/lib/services/object_storage/account_client.py b/tempest/lib/services/object_storage/account_client.py
index d7ce526..7bf0dcd 100644
--- a/tempest/lib/services/object_storage/account_client.py
+++ b/tempest/lib/services/object_storage/account_client.py
@@ -39,11 +39,13 @@
         headers = {}
         if create_update_metadata:
             for key in create_update_metadata:
-                metadata_header_name = create_update_metadata_prefix + key
+                metadata_header_name = create_update_metadata_prefix + \
+                    key.replace('_', '-')
                 headers[metadata_header_name] = create_update_metadata[key]
         if delete_metadata:
             for key in delete_metadata:
-                headers[delete_metadata_prefix + key] = delete_metadata[key]
+                headers[delete_metadata_prefix + key.replace(
+                    '_', '-')] = delete_metadata[key]
 
         resp, body = self.post('', headers=headers, body=None)
         self.expected_success([200, 204], resp.status)
diff --git a/tempest/lib/services/object_storage/container_client.py b/tempest/lib/services/object_storage/container_client.py
index 47edf70..641c084 100644
--- a/tempest/lib/services/object_storage/container_client.py
+++ b/tempest/lib/services/object_storage/container_client.py
@@ -41,7 +41,12 @@
         """
         url = str(container_name)
 
-        resp, body = self.put(url, body=None, headers=headers)
+        new_headers = {}
+        for key in headers:
+            new_key = key.replace('_', '-')
+            new_headers[new_key] = headers[key]
+
+        resp, body = self.put(url, body=None, headers=new_headers)
         self.expected_success([201, 202, 204], resp.status)
         return resp, body
 
@@ -74,11 +79,13 @@
         headers = {}
         if create_update_metadata:
             for key in create_update_metadata:
-                metadata_header_name = create_update_metadata_prefix + key
+                metadata_header_name = create_update_metadata_prefix + \
+                    key.replace('_', '-')
                 headers[metadata_header_name] = create_update_metadata[key]
         if delete_metadata:
             for key in delete_metadata:
-                headers[delete_metadata_prefix + key] = delete_metadata[key]
+                headers[delete_metadata_prefix + key.replace(
+                    '_', '-')] = delete_metadata[key]
 
         resp, body = self.post(url, headers=headers, body=None)
         self.expected_success(204, resp.status)
diff --git a/tempest/lib/services/volume/v3/encryption_types_client.py b/tempest/lib/services/volume/v3/encryption_types_client.py
index 7cced57..f6f2fd5 100644
--- a/tempest/lib/services/volume/v3/encryption_types_client.py
+++ b/tempest/lib/services/volume/v3/encryption_types_client.py
@@ -69,21 +69,21 @@
         self.validate_response(schema.create_encryption_type, resp, body)
         return rest_client.ResponseBody(resp, body)
 
-    def delete_encryption_type(self, volume_type_id):
+    def delete_encryption_type(self, volume_type_id, encryption_id):
         """Delete the encryption type for the specified volume-type."""
         resp, body = self.delete(
-            "/types/%s/encryption/provider" % volume_type_id)
+            "/types/%s/encryption/%s" % (volume_type_id, encryption_id))
         self.validate_response(schema.delete_encryption_type, resp, body)
         return rest_client.ResponseBody(resp, body)
 
-    def update_encryption_type(self, volume_type_id, **kwargs):
+    def update_encryption_type(self, volume_type_id, encryption_id, **kwargs):
         """Update an encryption type for an existing volume type.
 
         For a full list of available parameters, please refer to the official
         API reference:
         https://docs.openstack.org/api-ref/block-storage/v3/index.html#update-an-encryption-type
         """
-        url = "/types/%s/encryption/provider" % volume_type_id
+        url = "/types/%s/encryption/%s" % (volume_type_id, encryption_id)
         put_body = json.dumps({'encryption': kwargs})
         resp, body = self.put(url, put_body)
         body = json.loads(body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 01c42c8..57bc2e9 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -851,7 +851,7 @@
                     kernel_img_path = os.path.join(extract_dir, fname)
                 elif re.search(r'(.*-initrd.*|ari-.*/image$)', fname):
                     ramdisk_img_path = os.path.join(extract_dir, fname)
-                elif re.search(f'(.*\\.img$|ami-.*/image$)', fname):
+                elif re.search(r'(.*\\.img$|ami-.*/image$)', fname):
                     img_path = os.path.join(extract_dir, fname)
             # Create the kernel image.
             kparams = {
@@ -923,6 +923,19 @@
         if not isinstance(exc, lib_exc.SSHTimeout):
             LOG.debug('Network information on a devstack host')
 
+    def get_snapshot_id(self, bdms):
+        if isinstance(bdms, str):
+            bdms = json.loads(bdms)
+        snapshot_id = None
+        for bdm in bdms:
+            # Look for the block device mapping that actually has a
+            # snapshot. If the server has ephemeral or swap disk, their
+            # block device mappings will be present with snapshot_id = None
+            if 'snapshot_id' in bdm and bdm['snapshot_id'] is not None:
+                snapshot_id = bdm['snapshot_id']
+                break
+        return snapshot_id
+
     def create_server_snapshot(self, server, name=None, **kwargs):
         """Creates server snapshot"""
         # Glance client
@@ -949,20 +962,19 @@
         snapshot_image = _image_client.show_image(image_id)
         image_props = snapshot_image
 
-        bdm = image_props.get('block_device_mapping')
-        if bdm:
-            bdm = json.loads(bdm)
-            if bdm and 'snapshot_id' in bdm[0]:
-                snapshot_id = bdm[0]['snapshot_id']
-                self.addCleanup(
-                    self.snapshots_client.wait_for_resource_deletion,
-                    snapshot_id)
-                self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                                self.snapshots_client.delete_snapshot,
-                                snapshot_id)
-                waiters.wait_for_volume_resource_status(self.snapshots_client,
-                                                        snapshot_id,
-                                                        'available')
+        bdms = image_props.get('block_device_mapping')
+        if bdms:
+            snapshot_id = self.get_snapshot_id(bdms)
+            self.assertIsNotNone(snapshot_id)
+            self.addCleanup(
+                self.snapshots_client.wait_for_resource_deletion,
+                snapshot_id)
+            self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                            self.snapshots_client.delete_snapshot,
+                            snapshot_id)
+            waiters.wait_for_volume_resource_status(
+                self.snapshots_client, snapshot_id, 'available')
+
         image_name = snapshot_image['name']
         self.assertEqual(name, image_name)
         LOG.debug("Created snapshot image %s for server %s",
@@ -1187,14 +1199,15 @@
         except (KeyError, IndexError):
             return None
 
-    def associate_floating_ip(self, floating_ip, server):
+    def associate_floating_ip(self, floating_ip, server, ip_addr=None,
+                              **kwargs):
         """Associate floating ip to server
 
         This wrapper utility attaches the floating_ip for
         the respective port_id of server
         """
-        port_id, _ = self.get_server_port_id_and_ip4(server)
-        kwargs = dict(port_id=port_id)
+        port_id, _ = self.get_server_port_id_and_ip4(server, ip_addr=ip_addr)
+        kwargs.update({"port_id": port_id})
         floating_ip = self.floating_ips_client.update_floatingip(
             floating_ip['id'], **kwargs)['floatingip']
         self.assertEqual(port_id, floating_ip['port_id'])
@@ -1561,8 +1574,8 @@
             floating_ip = (self.floating_ips_client.
                            show_floatingip(floatingip_id)['floatingip'])
             if status == floating_ip['status']:
-                LOG.info("FloatingIP: {fp} is at status: {st}"
-                         .format(fp=floating_ip, st=status))
+                LOG.info("FloatingIP: %s is at status: %s",
+                         floating_ip, status)
             return status == floating_ip['status']
 
         if not test_utils.call_until_true(refresh,
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 5e28ecd..febc2f6 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -11,7 +11,6 @@
 #    under the License.
 
 from oslo_log import log as logging
-from oslo_serialization import jsonutils as json
 import testtools
 
 from tempest.common import utils
@@ -245,8 +244,7 @@
         bdms = image.get('block_device_mapping')
         if not bdms:
             bdms = image['properties']['block_device_mapping']
-        bdms = json.loads(bdms)
-        snapshot_id = bdms[0]['snapshot_id']
+        snapshot_id = self.get_snapshot_id(bdms)
         self._delete_snapshot(snapshot_id)
 
         # Now, delete the first server which will also delete the first
diff --git a/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
index 7218224..8164ea6 100644
--- a/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_encryption_types_client.py
@@ -106,6 +106,7 @@
             'tempest.lib.common.rest_client.RestClient.delete',
             {},
             volume_type_id="cbc36478b0bd8e67e89",
+            encryption_id="test_id",
             status=202)
 
     def test_update_encryption_type_with_str_body(self):
@@ -119,4 +120,5 @@
             self.client.update_encryption_type,
             'tempest.lib.common.rest_client.RestClient.put',
             self.FAKE_UPDATE_ENCRYPTION_TYPE,
-            bytes_body, volume_type_id="cbc36478b0bd8e67e89")
+            bytes_body, volume_type_id="cbc36478b0bd8e67e89",
+            encryption_id="test_id")
diff --git a/tempest/tests/test_hacking.py b/tempest/tests/test_hacking.py
index 464e66a..3f603e8 100644
--- a/tempest/tests/test_hacking.py
+++ b/tempest/tests/test_hacking.py
@@ -51,25 +51,34 @@
 
     def test_no_setup_teardown_class_for_tests(self):
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def setUpClass(cls):", './tempest/tests/fake_test.py'))
+            "  def setUpClass(cls):", './tempest/tests/fake_test.py', False))
         self.assertIsNone(checks.no_setup_teardown_class_for_tests(
-            "  def setUpClass(cls): # noqa", './tempest/tests/fake_test.py'))
+            "  def setUpClass(cls):", './tempest/tests/fake_test.py',
+            True))
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def setUpClass(cls):", './tempest/api/fake_test.py'))
+            "  def setUpClass(cls):", './tempest/api/fake_test.py',
+            False))
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def setUpClass(cls):", './tempest/scenario/fake_test.py'))
+            "  def setUpClass(cls):", './tempest/scenario/fake_test.py',
+            False))
         self.assertFalse(checks.no_setup_teardown_class_for_tests(
-            "  def setUpClass(cls):", './tempest/test.py'))
+            "  def setUpClass(cls):", './tempest/test.py',
+            False))
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def tearDownClass(cls):", './tempest/tests/fake_test.py'))
+            "  def tearDownClass(cls):", './tempest/tests/fake_test.py',
+            False))
         self.assertIsNone(checks.no_setup_teardown_class_for_tests(
-            "  def tearDownClass(cls): # noqa", './tempest/tests/fake_test.py'))
+            "  def tearDownClass(cls):", './tempest/tests/fake_test.py',
+            True))
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def tearDownClass(cls):", './tempest/api/fake_test.py'))
+            "  def tearDownClass(cls):", './tempest/api/fake_test.py',
+            False))
         self.assertTrue(checks.no_setup_teardown_class_for_tests(
-            "  def tearDownClass(cls):", './tempest/scenario/fake_test.py'))
+            "  def tearDownClass(cls):", './tempest/scenario/fake_test.py',
+            False))
         self.assertFalse(checks.no_setup_teardown_class_for_tests(
-            "  def tearDownClass(cls):", './tempest/test.py'))
+            "  def tearDownClass(cls):", './tempest/test.py',
+            False))
 
     def test_import_no_clients_in_api_and_scenario_tests(self):
         for client in checks.PYTHON_CLIENTS:
@@ -198,22 +207,26 @@
             # arbitrarily many decorators. These insert decorators above the
             # @decorators.attr(type=['negative']) decorator.
             for decorator in other_decorators:
-                self.assertIsNone(check(" %s" % decorator, filename))
+                self.assertFalse(
+                    list(check(" %s" % decorator, filename)))
         if with_negative_decorator:
-            self.assertIsNone(
-                check("@decorators.attr(type=['negative'])", filename))
+            self.assertFalse(
+                list(check("@decorators.attr(type=['negative'])", filename)))
         if with_other_decorators:
             # Include multiple decorators to verify that this check works with
             # arbitrarily many decorators. These insert decorators between
             # the test and the @decorators.attr(type=['negative']) decorator.
             for decorator in other_decorators:
-                self.assertIsNone(check(" %s" % decorator, filename))
-        final_result = check(" def test_some_negative_case", filename)
+                self.assertFalse(
+                    list(check(" %s" % decorator, filename)))
+        final_result = list(check(" def test_some_negative_case", filename))
         if expected_success:
-            self.assertIsNone(final_result)
+            self.assertFalse(final_result)
         else:
-            self.assertIsInstance(final_result, tuple)
-            self.assertFalse(final_result[0])
+            self.assertEqual(1, len(final_result))
+            self.assertIsInstance(final_result[0], tuple)
+            self.assertEqual(0, final_result[0][0])
+            self.assertTrue(final_result[0][1])
 
     def test_no_negatve_test_attribute_applied_to_negative_test(self):
         # Check negative filename, negative decorator passes
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 80825a4..7fb9bb3 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -303,7 +303,7 @@
         # [0]: test, err, details [1] -> exc_info
         # Type, Exception, traceback [1] -> MultipleException
         found_exc = log[0][1][1]
-        self.assertTrue(isinstance(found_exc, testtools.MultipleExceptions))
+        self.assertIsInstance(found_exc, testtools.MultipleExceptions)
         self.assertEqual(2, len(found_exc.args))
         # Each arg is exc_info - match messages and order
         self.assertIn('mock3 resource', str(found_exc.args[0][1]))
@@ -332,7 +332,7 @@
         # [0]: test, err, details [1] -> exc_info
         # Type, Exception, traceback [1] -> RuntimeError
         found_exc = log[0][1][1]
-        self.assertTrue(isinstance(found_exc, RuntimeError))
+        self.assertIsInstance(found_exc, RuntimeError)
         self.assertIn(BadResourceCleanup.__name__, str(found_exc))
 
     def test_super_skip_checks_not_invoked(self):
diff --git a/test-requirements.txt b/test-requirements.txt
index 17fa9f1..b925921 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,8 +1,4 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
-hacking>=3.0.1,<3.1.0;python_version>='3.5' # Apache-2.0
+hacking>=7.0.0,<7.1.0
 coverage!=4.4,>=4.0 # Apache-2.0
 oslotest>=3.2.0 # Apache-2.0
-pycodestyle>=2.0.0,<2.6.0 # MIT
-flake8-import-order==0.11 # LGPLv3
+flake8-import-order>=0.18.0,<0.19.0 # LGPLv3
diff --git a/tools/tempest-extra-tests-list.txt b/tools/tempest-extra-tests-list.txt
index 9c88109..03cf7e9 100644
--- a/tools/tempest-extra-tests-list.txt
+++ b/tools/tempest-extra-tests-list.txt
@@ -16,5 +16,10 @@
 tempest.api.image.admin
 tempest.api.network.admin
 
+# This also run cinder-tempest-plugin tests so that we can avoid any
+# breaking change to plugins (cinder-tempest-plugins uses most of the
+# Tempest interface) but we can add more plugins tests here if needed.
+cinder_tempest_plugin
+
 # All negative tests
 negative
diff --git a/tox.ini b/tox.ini
index e3c8fcf..0fbc252 100644
--- a/tox.ini
+++ b/tox.ini
@@ -387,14 +387,14 @@
 [testenv:pep8]
 deps =
     {[testenv]deps}
-    autopep8
+    autopep8>=2.1.0
 commands =
     autopep8 --exit-code --max-line-length=79 --experimental --diff -r tempest setup.py
     flake8 {posargs}
     check-uuid
 
 [testenv:autopep8]
-deps = autopep8
+deps = autopep8>=2.1.0
 commands =
     {toxinidir}/tools/format.sh
 
@@ -411,7 +411,8 @@
 # E129 skipped because it is too limiting when combined with other rules
 # W504 skipped because it is overeager and unnecessary
 # H405 skipped because it arbitrarily forces doctring "title" lines
-ignore = E125,E123,E129,W504,H405
+# I201 and I202 skipped because the rule does not allow new line between 3rd party modules and own modules
+ignore = E125,E123,E129,W504,H405,I201,I202,T117
 show-source = True
 exclude = .git,.venv,.tox,dist,doc,*egg,build
 enable-extensions = H106,H203,H904
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index fb08297..47b7812 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -42,11 +42,18 @@
     description: |
       This job runs the extra tests mentioned in
       tools/tempest-extra-tests-list.txt.
+    # NOTE(gmann): We need c-t-p as this job run c-t-p tests also.
+    required-projects:
+      - opendev.org/openstack/cinder-tempest-plugin
     vars:
       tox_envlist: extra-tests
+      tempest_plugins:
+        - cinder-tempest-plugin
       run_tempest_cleanup: true
       run_tempest_cleanup_resource_list: true
       run_tempest_dry_cleanup: true
+      devstack_localrc:
+        CINDER_ENFORCE_SCOPE: true
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
@@ -255,10 +262,10 @@
 - job:
     name: tempest-multinode-full-py3
     parent: tempest-multinode-full-base
-    nodeset: openstack-two-node-jammy
-    # This job runs on ubuntu Jammy and after unmaintained/zed.
+    nodeset: openstack-two-node-noble
+    # This job runs on ubuntu Noble from 2025.1 onwards.
     branches:
-      regex: ^.*/(victoria|wallaby|xena|yoga|zed)$
+      regex: ^.*/(victoria|wallaby|xena|yoga|zed|2023.1|2023.2|2024.1|2024.2)$
       negate: true
     vars:
       # NOTE(gmann): Default concurrency is higher (number of cpu -2) which
@@ -267,8 +274,6 @@
       # oom issue, setting the concurrency to 4 in this job.
       tempest_concurrency: 4
       tempest_set_src_dest_host: true
-      devstack_localrc:
-        USE_PYTHON3: true
       devstack_plugins:
         neutron: https://opendev.org/openstack/neutron
       devstack_services:
@@ -277,8 +282,6 @@
         br-int-flows: true
     group-vars:
       subnode:
-        devstack_localrc:
-          USE_PYTHON3: true
         devstack_services:
           br-ex-tcpdump: true
           br-int-flows: true
@@ -344,6 +347,27 @@
         devstack_localrc:
           ENABLE_VOLUME_MULTIATTACH: true
 
+# TODO(gmann): As per the 2025.1 testing runtime, we need to run at least
+# one job set on Jammy. These jammy job can be removed in the next
+# cycle(2025.2).
+- job:
+    name: tempest-full-ubuntu-jammy
+    description: This is tempest-full python3 job on Ubuntu Jammy(22.04)
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-jammy
+
+- job:
+    name: tempest-multinode-full-ubuntu-jammy
+    description: This is tempest-multinode-full-py3 python3 job on Ubuntu Jammy(22.04)
+    parent: tempest-multinode-full-py3
+    nodeset: openstack-two-node-jammy
+
+- job:
+    name: tempest-extra-tests-ubuntu-jammy
+    description: This is tempest-extra-tests python3 job on Ubuntu Jammy(22.04)
+    parent: tempest-extra-tests
+    nodeset: openstack-single-node-jammy
+
 - job:
     name: tempest-cinder-v2-api
     parent: devstack-tempest
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index a7641a6..2f21c2d 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -27,6 +27,14 @@
               - ^.gitignore$
               - ^.gitreview$
               - ^.mailmap$
+        # NOTE(gmann): Running jobs on Jammy as per the additional testing
+        # for 2025.1 cycle and these can be removed in 2025.2 cycle.
+        - tempest-full-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-multinode-full-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-extra-tests-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
         - tempest-extra-tests:
             irrelevant-files: *tempest-irrelevant-files
         - glance-multistore-cinder-import:
@@ -154,6 +162,14 @@
             irrelevant-files: *tempest-irrelevant-files
         - ironic-tempest-bios-ipmi-direct-tinyipa:
             irrelevant-files: *tempest-irrelevant-files
+        # NOTE(gmann): Running jobs on Jammy as per the additional testing
+        # for 2025.1 cycle and these can be removed in 2025.2 cycle.
+        - tempest-full-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-multinode-full-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
+        - tempest-extra-tests-ubuntu-jammy:
+            irrelevant-files: *tempest-irrelevant-files
     experimental:
       jobs:
         - nova-multi-cell
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index efa771e..5785ec6 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -90,6 +90,26 @@
 - job:
     name: tempest-multinode-full-py3
     parent: tempest-multinode-full
+    nodeset: openstack-two-node-jammy
+    # This job runs on Jammy and supposed to run until 2024.2.
+    branches:
+      - ^.*/2023.1
+      - ^.*/2023.2
+      - ^.*/2024.1
+      - ^.*/2024.2
+    vars:
+      devstack_plugins:
+        neutron: https://opendev.org/openstack/neutron
+      devstack_services:
+        neutron-trunk: true
+    group-vars:
+      subnode:
+        devstack_localrc:
+          USE_PYTHON3: true
+
+- job:
+    name: tempest-multinode-full-py3
+    parent: tempest-multinode-full
     nodeset: openstack-two-node-focal
     # This job runs on Focal and supposed to run until unmaintained/zed.
     branches:
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index 296682e..deb4157 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -76,7 +76,7 @@
     parent: tox
     description: |
       Run tempest plugin sanity check script using tox.
-    nodeset: ubuntu-jammy
+    nodeset: ubuntu-noble
     vars:
       tox_envlist: plugin-sanity-check
     timeout: 5000