Merge "Break out novnc connection validation to a mixin"
diff --git a/doc/source/plugins/plugin.rst b/doc/source/plugins/plugin.rst
index 31aa134..573aa7d 100644
--- a/doc/source/plugins/plugin.rst
+++ b/doc/source/plugins/plugin.rst
@@ -29,6 +29,10 @@
 * tempest.config
 * tempest.test_discover.plugins
 * tempest.common.credentials_factory
+* tempest.common.compute
+* tempest.common.identity
+* tempest.common.image
+* tempest.common.object_storage
 * tempest.clients
 * tempest.test
 * tempest.scenario.manager
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index 9c03e7f..d4b7b4c 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -12,7 +12,6 @@
 * 2025.2
 * 2025.1
 * 2024.2
-* 2024.1
 
 For older OpenStack Release:
 
@@ -33,7 +32,7 @@
 
 Tempest master supports the below python versions:
 
-* Python 3.9
 * Python 3.10
 * Python 3.11
 * Python 3.12
+* Python 3.13
diff --git a/releasenotes/notes/add-alt-manager-dynamic-creds-f8f1007862ea5dfb.yaml b/releasenotes/notes/add-alt-manager-dynamic-creds-f8f1007862ea5dfb.yaml
new file mode 100644
index 0000000..bff36c7
--- /dev/null
+++ b/releasenotes/notes/add-alt-manager-dynamic-creds-f8f1007862ea5dfb.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add alt manager role to the dynamic credentials provider for project scope.
diff --git a/releasenotes/notes/drop-python-3-9-b8a25c06e4bc0787.yaml b/releasenotes/notes/drop-python-3-9-b8a25c06e4bc0787.yaml
new file mode 100644
index 0000000..f9488d7
--- /dev/null
+++ b/releasenotes/notes/drop-python-3-9-b8a25c06e4bc0787.yaml
@@ -0,0 +1,8 @@
+---
+prelude: >
+    Tempest dropped the support of python 3.9.
+upgrade:
+  - |
+    Python 3.9 support has been dropped. The last release of Tempest
+    to support python 3.9 is Temepst 45.0.0. The minimum version
+    of Python supported by Tempest is python 3.10.
diff --git a/releasenotes/notes/plugin-sytable-interface-18f865ba3a415c70.yaml b/releasenotes/notes/plugin-sytable-interface-18f865ba3a415c70.yaml
new file mode 100644
index 0000000..6d466c7
--- /dev/null
+++ b/releasenotes/notes/plugin-sytable-interface-18f865ba3a415c70.yaml
@@ -0,0 +1,9 @@
+---
+prelude: |
+    Tempest declares the following interface as a stable interface
+    to be used by the tempest plugins:
+
+    * tempest.common.compute
+    * tempest.common.identity
+    * tempest.common.image
+    * tempest.common.object_storage
diff --git a/releasenotes/notes/tempest-2024-2-release-e706f62c7e841bd0.yaml b/releasenotes/notes/tempest-2025-1-release-e706f62c7e841bd0.yaml
similarity index 98%
rename from releasenotes/notes/tempest-2024-2-release-e706f62c7e841bd0.yaml
rename to releasenotes/notes/tempest-2025-1-release-e706f62c7e841bd0.yaml
index 86af60c..24129cf 100644
--- a/releasenotes/notes/tempest-2024-2-release-e706f62c7e841bd0.yaml
+++ b/releasenotes/notes/tempest-2025-1-release-e706f62c7e841bd0.yaml
@@ -1,5 +1,5 @@
 ---
-prelude: >
+prelude: |
     This release is to tag Tempest for OpenStack 2025.1 release.
     This release marks the start of 2025.1 release support in Tempest.
     After this release, Tempest will support below OpenStack Releases:
diff --git a/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 15b1743..60402ee 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 2023.1
+- name: Use stable branch upper-constraints till 2024.1
   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/2023.1", "unmaintained/victoria", "unmaintained/wallaby", "unmaintained/xena", "unmaintained/yoga", "unmaintained/zed", "unmaintained/2023.1"]
+  when: target_branch in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein", "stable/train", "stable/ussuri", "stable/2023.1", "unmaintained/victoria", "unmaintained/wallaby", "unmaintained/xena", "unmaintained/yoga", "unmaintained/zed", "unmaintained/2023.1", "unmaintained/2024.1"]
 
 - name: Use Configured upper-constraints for non-master Tempest
   set_fact:
diff --git a/setup.cfg b/setup.cfg
index 67555f4..fa17801 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,10 +14,10 @@
     Operating System :: POSIX :: Linux
     Programming Language :: Python
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
+    Programming Language :: Python :: 3.13
     Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: Implementation :: CPython
 
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index bd2e7bf..526c8a7 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -58,7 +58,7 @@
         cls.subnets_client = cls.os_primary.subnets_client
         cls.ports_client = cls.os_primary.ports_client
         cls.trunks_client = cls.os_primary.trunks_client
-        cls.mgr_server_client = cls.admin_servers_client
+        cls.server_client = cls.admin_servers_client
 
     def _migrate_server_to(self, server_id, dest_host, volume_backed=False,
                            use_manager_client=False):
@@ -70,13 +70,19 @@
             block_migration = (CONF.compute_feature_enabled.
                                block_migration_for_live_migration and
                                not volume_backed)
+        # Avoid changing self.server_client permanently because
+        # [compute_feature_enabled]live_migrate_back_and_forth might be
+        # set to True. If it is, the test will live migrate the server back to
+        # the source using the os-migrate-server:migrate_live:host API, which
+        # is not allowed for project manager by default policy.
+        server_client = self.server_client
         if use_manager_client:
-            self.mgr_server_client = self.os_project_manager.servers_client
+            server_client = self.os_project_manager.servers_client
             LOG.info("Using project manager for live migrating server: %s, "
                      "project manager user id: %s",
-                     server_id, self.mgr_server_client.user_id)
+                     server_id, server_client.user_id)
 
-        self.mgr_server_client.live_migrate_server(
+        server_client.live_migrate_server(
             server_id, host=dest_host, block_migration=block_migration,
             **kwargs)
 
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index 9ab75c5..2916de5 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -14,18 +14,32 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest import config
 from tempest.lib import decorators
 
 
+CONF = config.CONF
+
+
 class FlavorsV2TestJSON(base.BaseV2ComputeTest):
     """Tests Flavors"""
 
+    credentials = ['primary', 'project_reader']
+
+    @classmethod
+    def setup_clients(cls):
+        super(FlavorsV2TestJSON, cls).setup_clients()
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.flavors_client
+        else:
+            cls.reader_client = cls.flavors_client
+
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('e36c0eaa-dff5-4082-ad1f-3f9a80aa3f59')
     def test_list_flavors(self):
         """List of all flavors should contain the expected flavor"""
-        flavors = self.flavors_client.list_flavors()['flavors']
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavors = self.reader_client.list_flavors()['flavors']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'],
                              'name': flavor['name']}
         # description field is added to the response of list_flavors in 2.55
@@ -36,93 +50,93 @@
     @decorators.idempotent_id('6e85fde4-b3cd-4137-ab72-ed5f418e8c24')
     def test_list_flavors_with_detail(self):
         """Detailed list of all flavors should contain the expected flavor"""
-        flavors = self.flavors_client.list_flavors(detail=True)['flavors']
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavors = self.reader_client.list_flavors(detail=True)['flavors']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         self.assertIn(flavor, flavors)
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('1f12046b-753d-40d2-abb6-d8eb8b30cb2f')
     def test_get_flavor(self):
         """The expected flavor details should be returned"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         self.assertEqual(self.flavor_ref, flavor['id'])
 
     @decorators.idempotent_id('8d7691b3-6ed4-411a-abc9-2839a765adab')
     def test_list_flavors_limit_results(self):
         """Only the expected number of flavors should be returned"""
         params = {'limit': 1}
-        flavors = self.flavors_client.list_flavors(**params)['flavors']
+        flavors = self.reader_client.list_flavors(**params)['flavors']
         self.assertEqual(1, len(flavors))
 
     @decorators.idempotent_id('b26f6327-2886-467a-82be-cef7a27709cb')
     def test_list_flavors_detailed_limit_results(self):
         """Only the expected number of flavors(detailed) should be returned"""
         params = {'limit': 1}
-        flavors = self.flavors_client.list_flavors(detail=True,
-                                                   **params)['flavors']
+        flavors = self.reader_client.list_flavors(detail=True,
+                                                  **params)['flavors']
         self.assertEqual(1, len(flavors))
 
     @decorators.idempotent_id('e800f879-9828-4bd0-8eae-4f17189951fb')
     def test_list_flavors_using_marker(self):
         """The list of flavors should start from the provided marker"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'marker': flavor_id}
-        flavors = self.flavors_client.list_flavors(**params)['flavors']
+        flavors = self.reader_client.list_flavors(**params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
                          'The list of flavors did not start after the marker.')
 
     @decorators.idempotent_id('6db2f0c0-ddee-4162-9c84-0703d3dd1107')
     def test_list_flavors_detailed_using_marker(self):
         """The list of flavors should start from the provided marker"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'marker': flavor_id}
-        flavors = self.flavors_client.list_flavors(detail=True,
-                                                   **params)['flavors']
+        flavors = self.reader_client.list_flavors(detail=True,
+                                                  **params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id],
                          'The list of flavors did not start after the marker.')
 
     @decorators.idempotent_id('3df2743e-3034-4e57-a4cb-b6527f6eac79')
     def test_list_flavors_detailed_filter_by_min_disk(self):
         """The detailed list of flavors should be filtered by disk space"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'minDisk': flavor['disk'] + 1}
-        flavors = self.flavors_client.list_flavors(detail=True,
-                                                   **params)['flavors']
+        flavors = self.reader_client.list_flavors(detail=True,
+                                                  **params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
 
     @decorators.idempotent_id('09fe7509-b4ee-4b34-bf8b-39532dc47292')
     def test_list_flavors_detailed_filter_by_min_ram(self):
         """The detailed list of flavors should be filtered by RAM"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'minRam': flavor['ram'] + 1}
-        flavors = self.flavors_client.list_flavors(detail=True,
-                                                   **params)['flavors']
+        flavors = self.reader_client.list_flavors(detail=True,
+                                                  **params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
 
     @decorators.idempotent_id('10645a4d-96f5-443f-831b-730711e11dd4')
     def test_list_flavors_filter_by_min_disk(self):
         """The list of flavors should be filtered by disk space"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'minDisk': flavor['disk'] + 1}
-        flavors = self.flavors_client.list_flavors(**params)['flavors']
+        flavors = self.reader_client.list_flavors(**params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
 
     @decorators.idempotent_id('935cf550-e7c8-4da6-8002-00f92d5edfaa')
     def test_list_flavors_filter_by_min_ram(self):
         """The list of flavors should be filtered by RAM"""
-        flavor = self.flavors_client.show_flavor(self.flavor_ref)['flavor']
+        flavor = self.reader_client.show_flavor(self.flavor_ref)['flavor']
         flavor_id = flavor['id']
 
         params = {'minRam': flavor['ram'] + 1}
-        flavors = self.flavors_client.list_flavors(**params)['flavors']
+        flavors = self.reader_client.list_flavors(**params)['flavors']
         self.assertEmpty([i for i in flavors if i['id'] == flavor_id])
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index efd9cdd..682ca58 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -28,6 +28,16 @@
 
 class FlavorsV2NegativeTest(base.BaseV2ComputeTest):
 
+    credentials = ['primary', 'project_reader']
+
+    @classmethod
+    def setup_clients(cls):
+        super(FlavorsV2NegativeTest, cls).setup_clients()
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.flavors_client
+        else:
+            cls.reader_client = cls.flavors_client
+
     @decorators.attr(type=['negative'])
     @utils.services('image')
     @decorators.idempotent_id('90f0d93a-91c1-450c-91e6-07d18172cefe')
@@ -38,7 +48,7 @@
         Try to create server with flavor of insufficient ram size from
         that image
         """
-        flavor = self.flavors_client.show_flavor(
+        flavor = self.reader_client.show_flavor(
             CONF.compute.flavor_ref)['flavor']
         min_img_ram = flavor['ram'] + 1
         size = random.randint(1024, 4096)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 0b39b8a..1454deb 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -33,7 +33,7 @@
 
     This is to create server booted from image and with disk_config 'AUTO'
     """
-
+    credentials = ['primary', 'project_reader']
     disk_config = 'AUTO'
     volume_backed = False
 
@@ -46,6 +46,10 @@
     def setup_clients(cls):
         super(ServersTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.client
 
     @classmethod
     def resource_setup(cls):
@@ -71,7 +75,8 @@
             disk_config=disk_config,
             adminPass=cls.password,
             volume_backed=cls.volume_backed)
-        cls.server = cls.client.show_server(server_initial['id'])['server']
+        cls.server = cls.reader_client.show_server(
+            server_initial['id'])['server']
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('5de47127-9977-400a-936f-abcfbec1218f')
@@ -95,7 +100,7 @@
     @decorators.idempotent_id('9a438d88-10c6-4bcd-8b5b-5b6e25e1346f')
     def test_list_servers(self):
         """The created server should be in the list of all servers"""
-        body = self.client.list_servers()
+        body = self.reader_client.list_servers()
         servers = body['servers']
         found = [i for i in servers if i['id'] == self.server['id']]
         self.assertNotEmpty(found)
@@ -103,7 +108,7 @@
     @decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
     def test_list_servers_with_detail(self):
         """The created server should be in the detailed list of all servers"""
-        body = self.client.list_servers(detail=True)
+        body = self.reader_client.list_servers(detail=True)
         servers = body['servers']
         found = [i for i in servers if i['id'] == self.server['id']]
         self.assertNotEmpty(found)
@@ -193,6 +198,8 @@
     server hostname with dashes. This test verifies the same.
     """
 
+    credentials = ['primary', 'project_reader']
+
     @classmethod
     def setup_credentials(cls):
         cls.prepare_instance_network()
@@ -202,6 +209,10 @@
     def setup_clients(cls):
         super(ServersTestFqdnHostnames, cls).setup_clients()
         cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.client
 
     @decorators.idempotent_id('622066d2-39fc-4c09-9eeb-35903c114a0a')
     @testtools.skipUnless(
@@ -250,6 +261,7 @@
     more than 64 characters
     """
 
+    credentials = ['primary', 'project_reader']
     min_microversion = '2.94'
 
     @classmethod
@@ -261,6 +273,10 @@
     def setup_clients(cls):
         super(ServersV294TestFqdnHostnames, cls).setup_clients()
         cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.client
 
     @classmethod
     def resource_setup(cls):
@@ -279,7 +295,8 @@
             accessIPv4=cls.accessIPv4,
             adminPass=cls.password,
             hostname=cls.hostname)
-        cls.server = cls.client.show_server(cls.test_server['id'])['server']
+        cls.server = cls.reader_client.show_server(
+            cls.test_server['id'])['server']
 
     def verify_metadata_hostname(self, md_json):
         md_dict = json.loads(md_json)
diff --git a/tempest/api/compute/servers/test_create_server_multi_nic.py b/tempest/api/compute/servers/test_create_server_multi_nic.py
index 1cbb976..828ee32 100644
--- a/tempest/api/compute/servers/test_create_server_multi_nic.py
+++ b/tempest/api/compute/servers/test_create_server_multi_nic.py
@@ -48,6 +48,8 @@
 class ServersTestMultiNic(base.BaseV2ComputeTest):
     """Test multiple networks in servers"""
 
+    credentials = ['primary', 'project_reader']
+
     @classmethod
     def skip_checks(cls):
         super(ServersTestMultiNic, cls).skip_checks()
@@ -62,7 +64,10 @@
     @classmethod
     def setup_clients(cls):
         super(ServersTestMultiNic, cls).setup_clients()
-        cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.servers_client
         cls.networks_client = cls.os_primary.networks_client
         cls.subnets_client = cls.os_primary.subnets_client
 
@@ -107,7 +112,7 @@
         # we're OK.
         self.addCleanup(self.delete_server, server_multi_nics['id'])
 
-        addresses = (self.client.list_addresses(server_multi_nics['id'])
+        addresses = (self.reader_client.list_addresses(server_multi_nics['id'])
                      ['addresses'])
 
         # We can't predict the ip addresses assigned to the server on networks.
@@ -142,7 +147,7 @@
             networks=networks, wait_until='ACTIVE')
         self.addCleanup(self.delete_server, server_multi_nics['id'])
 
-        addresses = (self.client.list_addresses(server_multi_nics['id'])
+        addresses = (self.reader_client.list_addresses(server_multi_nics['id'])
                      ['addresses'])
 
         addr = [addresses[net1['network']['name']][0]['addr'],
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 7873296..45f3348 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -28,6 +28,8 @@
 class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
     """Test listing servers filtered by specified attribute"""
 
+    credentials = ['primary', 'project_reader']
+
     @classmethod
     def setup_credentials(cls):
         cls.set_network_resources(network=True, subnet=True, dhcp=True)
@@ -37,6 +39,10 @@
     def setup_clients(cls):
         super(ListServerFiltersTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.client
 
     @classmethod
     def resource_setup(cls):
@@ -80,7 +86,7 @@
     def test_list_servers_filter_by_image(self):
         """Filter the list of servers by image"""
         params = {'image': self.image_ref}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -91,7 +97,7 @@
     def test_list_servers_filter_by_flavor(self):
         """Filter the list of servers by flavor"""
         params = {'flavor': self.flavor_ref_alt}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -102,7 +108,7 @@
     def test_list_servers_filter_by_server_name(self):
         """Filter the list of servers by server name"""
         params = {'name': self.s1_name}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -113,7 +119,7 @@
     def test_list_servers_filter_by_active_status(self):
         """Filter the list of servers by server active status"""
         params = {'status': 'active'}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -127,7 +133,7 @@
         self.client.stop_server(self.s1['id'])
         waiters.wait_for_server_status(self.client, self.s1['id'],
                                        'SHUTOFF')
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         self.client.start_server(self.s1['id'])
         waiters.wait_for_server_status(self.client, self.s1['id'],
                                        'ACTIVE')
@@ -144,7 +150,7 @@
         Verify only the expected number of servers are returned (one server)
         """
         params = {'limit': 1}
-        servers = self.client.list_servers(**params)
+        servers = self.reader_client.list_servers(**params)
         self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
 
     @decorators.idempotent_id('b1495414-2d93-414c-8019-849afe8d319e')
@@ -154,7 +160,7 @@
         Verify only the expected number of servers are returned (no server)
         """
         params = {'limit': 0}
-        servers = self.client.list_servers(**params)
+        servers = self.reader_client.list_servers(**params)
         self.assertEmpty(servers['servers'])
 
     @decorators.idempotent_id('37791bbd-90c0-4de0-831e-5f38cba9c6b3')
@@ -164,8 +170,8 @@
         Verify only the expected number of servers are returned (all servers)
         """
         params = {'limit': 100000}
-        servers = self.client.list_servers(**params)
-        all_servers = self.client.list_servers()
+        servers = self.reader_client.list_servers(**params)
+        all_servers = self.reader_client.list_servers()
         self.assertEqual(len([x for x in all_servers['servers'] if 'id' in x]),
                          len([x for x in servers['servers'] if 'id' in x]))
 
@@ -175,7 +181,7 @@
     def test_list_servers_detailed_filter_by_image(self):
         """"Filter the detailed list of servers by image"""
         params = {'image': self.image_ref}
-        body = self.client.list_servers(detail=True, **params)
+        body = self.reader_client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -186,7 +192,7 @@
     def test_list_servers_detailed_filter_by_flavor(self):
         """Filter the detailed list of servers by flavor"""
         params = {'flavor': self.flavor_ref_alt}
-        body = self.client.list_servers(detail=True, **params)
+        body = self.reader_client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -197,7 +203,7 @@
     def test_list_servers_detailed_filter_by_server_name(self):
         """Filter the detailed list of servers by server name"""
         params = {'name': self.s1_name}
-        body = self.client.list_servers(detail=True, **params)
+        body = self.reader_client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -208,7 +214,7 @@
     def test_list_servers_detailed_filter_by_server_status(self):
         """Filter the detailed list of servers by server status"""
         params = {'status': 'active'}
-        body = self.client.list_servers(detail=True, **params)
+        body = self.reader_client.list_servers(detail=True, **params)
         servers = body['servers']
         test_ids = [s['id'] for s in (self.s1, self.s2, self.s3)]
 
@@ -223,7 +229,7 @@
         """Filter the list of servers by part of server name"""
         # List all servers that contains '-instance' in name
         params = {'name': '-instance'}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -234,7 +240,7 @@
         part_name = self.s1_name[6:-1]
 
         params = {'name': part_name}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -248,7 +254,7 @@
         regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
         for regex in regexes:
             params = {'name': regex}
-            body = self.client.list_servers(**params)
+            body = self.reader_client.list_servers(**params)
             servers = body['servers']
 
             self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -259,7 +265,7 @@
         part_name = self.s1_name[-10:]
 
         params = {'name': part_name}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -279,22 +285,22 @@
         # so here look for the longest server ip, and filter by that ip,
         # so as to ensure only one server is returned.
         ip_list = {}
-        self.s1 = self.client.show_server(self.s1['id'])['server']
+        self.s1 = self.reader_client.show_server(self.s1['id'])['server']
         # Get first ip address in spite of v4 or v6
         ip_addr = self.s1['addresses'][self.fixed_network_name][0]['addr']
         ip_list[ip_addr] = self.s1['id']
 
-        self.s2 = self.client.show_server(self.s2['id'])['server']
+        self.s2 = self.reader_client.show_server(self.s2['id'])['server']
         ip_addr = self.s2['addresses'][self.fixed_network_name][0]['addr']
         ip_list[ip_addr] = self.s2['id']
 
-        self.s3 = self.client.show_server(self.s3['id'])['server']
+        self.s3 = self.reader_client.show_server(self.s3['id'])['server']
         ip_addr = self.s3['addresses'][self.fixed_network_name][0]['addr']
         ip_list[ip_addr] = self.s3['id']
 
         longest_ip = max([[len(ip), ip] for ip in ip_list])[1]
         params = {'ip': longest_ip}
-        body = self.client.list_servers(**params)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(ip_list[longest_ip], map(lambda x: x['id'], servers))
@@ -311,7 +317,7 @@
         # query addresses of the 3 servers
         addrs = []
         for s in [self.s1, self.s2, self.s3]:
-            s_show = self.client.show_server(s['id'])['server']
+            s_show = self.reader_client.show_server(s['id'])['server']
             addr_spec = s_show['addresses'][self.fixed_network_name][0]
             addrs.append(addr_spec['addr'])
         # find common part of the 3 ip addresses
@@ -329,8 +335,8 @@
         else:
             params = {'ip6': prefix}
         # capture all servers in case something goes wrong
-        all_servers = self.client.list_servers(detail=True)
-        body = self.client.list_servers(**params)
+        all_servers = self.reader_client.list_servers(detail=True)
+        body = self.reader_client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers),
@@ -350,5 +356,5 @@
         Verify only the expected number of servers are returned (one server)
         """
         params = {'limit': 1}
-        servers = self.client.list_servers(detail=True, **params)
+        servers = self.reader_client.list_servers(detail=True, **params)
         self.assertEqual(1, len(servers['servers']))
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 3d55696..140e5c3 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -15,19 +15,28 @@
 
 from tempest.api.compute import base
 from tempest.common import waiters
+from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
 
+CONF = config.CONF
+
+
 class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
     """Negative tests of listing servers"""
 
+    credentials = ['primary', 'project_reader']
     create_default_network = True
 
     @classmethod
     def setup_clients(cls):
         super(ListServersNegativeTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
+        if CONF.enforce_scope.nova:
+            cls.reader_client = cls.os_project_reader.servers_client
+        else:
+            cls.reader_client = cls.client
 
     @classmethod
     def resource_setup(cls):
@@ -49,7 +58,7 @@
     def test_list_servers_with_a_deleted_server(self):
         """Test that deleted servers do not show by default in list servers"""
         # List servers and verify server not returned
-        body = self.client.list_servers()
+        body = self.reader_client.list_servers()
         servers = body['servers']
         actual = [srv for srv in servers
                   if srv['id'] == self.deleted_id]
@@ -59,7 +68,7 @@
     @decorators.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
     def test_list_servers_by_non_existing_image(self):
         """Test listing servers for a non existing image returns empty list"""
-        body = self.client.list_servers(image='non_existing_image')
+        body = self.reader_client.list_servers(image='non_existing_image')
         servers = body['servers']
         self.assertEmpty(servers)
 
@@ -67,7 +76,7 @@
     @decorators.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
     def test_list_servers_by_non_existing_flavor(self):
         """Test listing servers by non existing flavor returns empty list"""
-        body = self.client.list_servers(flavor='non_existing_flavor')
+        body = self.reader_client.list_servers(flavor='non_existing_flavor')
         servers = body['servers']
         self.assertEmpty(servers)
 
@@ -80,7 +89,7 @@
         list.
         """
 
-        body = self.client.list_servers(name='non_existing_server_name')
+        body = self.reader_client.list_servers(name='non_existing_server_name')
         servers = body['servers']
         self.assertEmpty(servers)
 
@@ -95,12 +104,14 @@
         """
 
         if self.is_requested_microversion_compatible('2.37'):
-            body = self.client.list_servers(status='non_existing_status')
+            body = self.reader_client.list_servers(
+                status='non_existing_status')
             servers = body['servers']
             self.assertEmpty(servers)
         else:
-            self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
-                              status='non_existing_status')
+            self.assertRaises(
+                lib_exc.BadRequest, self.reader_client.list_servers,
+                status='non_existing_status')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
@@ -112,24 +123,24 @@
         """
 
         # Gather the complete list of servers in the project for reference
-        full_list = self.client.list_servers()['servers']
+        full_list = self.reader_client.list_servers()['servers']
         # List servers by specifying a greater value for limit
         limit = len(full_list) + 100
-        body = self.client.list_servers(limit=limit)
+        body = self.reader_client.list_servers(limit=limit)
         self.assertEqual(len(full_list), len(body['servers']))
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('679bc053-5e70-4514-9800-3dfab1a380a6')
     def test_list_servers_by_limits_pass_string(self):
         """Test listing servers by non-integer limit should fail"""
-        self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+        self.assertRaises(lib_exc.BadRequest, self.reader_client.list_servers,
                           limit='testing')
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('62610dd9-4713-4ee0-8beb-fd2c1aa7f950')
     def test_list_servers_by_limits_pass_negative_value(self):
         """Test listing servers by negative limit should fail"""
-        self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+        self.assertRaises(lib_exc.BadRequest, self.reader_client.list_servers,
                           limit=-1)
 
     @decorators.attr(type=['negative'])
@@ -137,7 +148,7 @@
     def test_list_servers_by_changes_since_invalid_date(self):
         """Test listing servers by invalid changes-since format should fail"""
         params = {'changes-since': '2011/01/01'}
-        self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+        self.assertRaises(lib_exc.BadRequest, self.reader_client.list_servers,
                           **params)
 
     @decorators.attr(type=['negative'])
@@ -154,14 +165,14 @@
         # {'status': 'ACTIVE'} along with changes-since as filter.
         changes_since = {'changes-since': '2051-01-01T12:34:00Z',
                          'status': 'ACTIVE'}
-        body = self.client.list_servers(**changes_since)
+        body = self.reader_client.list_servers(**changes_since)
         self.assertEmpty(body['servers'])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570')
     def test_list_servers_detail_server_is_deleted(self):
         """Test listing servers detail should not contain deleted server"""
-        body = self.client.list_servers(detail=True)
+        body = self.reader_client.list_servers(detail=True)
         servers = body['servers']
         actual = [srv for srv in servers
                   if srv['id'] == self.deleted_id]
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index d6c0324..9f96385 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -282,6 +282,7 @@
         """
         block_device_mapping_v2 = [{
             "boot_index": "0",
+            "delete_on_termination": "true",
             "source_type": "blank",
             "volume_size": CONF.volume.volume_size,
             "destination_type": "volume"}]
@@ -300,6 +301,7 @@
         """
         block_device_mapping_v2 = [{
             "boot_index": "0",
+            "delete_on_termination": "true",
             "source_type": "image",
             "volume_size": CONF.volume.volume_size,
             "uuid": CONF.compute.image_ref,
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 251cf8e..e19038e 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -213,6 +213,12 @@
     volume_max_microversion = 'latest'
 
     @classmethod
+    def skip_checks(cls):
+        super(VolumeRetypeMultiattachTest, cls).skip_checks()
+        if not CONF.compute_feature_enabled.volume_multiattach:
+            raise cls.skipException('Volume multi-attach is not available.')
+
+    @classmethod
     def resource_setup(cls):
         super(VolumeRetypeMultiattachTest, cls).resource_setup()
         extra_specs_src = {"multiattach": '<is> True'}
diff --git a/tempest/lib/api_schema/response/volume/volume_types.py b/tempest/lib/api_schema/response/volume/volume_types.py
index 51b3a72..4d09bcd 100644
--- a/tempest/lib/api_schema/response/volume/volume_types.py
+++ b/tempest/lib/api_schema/response/volume/volume_types.py
@@ -31,8 +31,7 @@
         'qos_specs_id': {'type': ['string', 'null'], 'format': 'uuid'}
     },
     'additionalProperties': False,
-    'required': ['name', 'is_public', 'description', 'id',
-                 'os-volume-type-access:is_public']
+    'required': ['name', 'is_public', 'description', 'id']
 }
 
 show_volume_type = {
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 93b9586..9be7c5e 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -100,6 +100,10 @@
         return
 
     @abc.abstractmethod
+    def get_project_alt_manager_creds(self):
+        return
+
+    @abc.abstractmethod
     def get_project_member_creds(self):
         return
 
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 1815dc6..11e7215 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -427,7 +427,8 @@
                 elif credential_type in [['admin'], ['alt_admin']]:
                     credentials = self._create_creds(
                         admin=True, scope=scope, project_id=project_id)
-                elif credential_type in [['alt_member'], ['alt_reader']]:
+                elif credential_type in [['alt_manager'], ['alt_member'],
+                                         ['alt_reader']]:
                     cred_type = credential_type[0][4:]
                     if isinstance(cred_type, str):
                         cred_type = [cred_type]
@@ -511,6 +512,9 @@
     def get_project_manager_creds(self):
         return self.get_credentials(['manager'], scope='project')
 
+    def get_project_alt_manager_creds(self):
+        return self.get_credentials(['alt_manager'], scope='project')
+
     def get_project_member_creds(self):
         return self.get_credentials(['member'], scope='project')
 
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 3ba7db1..f3a84d6 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -392,6 +392,10 @@
         self._creds['project_manager'] = project_manager
         return project_manager
 
+    def get_project_alt_manager_creds(self):
+        # TODO(msava):Implement alt manager hash.
+        return
+
     def get_project_member_creds(self):
         if self._creds.get('project_member'):
             return self._creds.get('project_member')
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index 4122db3..b62d854 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -248,6 +248,7 @@
         creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
         if test_alt_creds:
             admin_func = creds.get_project_alt_admin_creds
+            manager_func = creds.get_project_alt_manager_creds
             member_func = creds.get_project_alt_member_creds
             reader_func = creds.get_project_alt_reader_creds
         else:
@@ -290,11 +291,8 @@
         # Now request for the project manager creds which should not create new
         # project instead should use the project_id of member_creds already
         # created project.
-        # TODO(gmaan): test test_alt_creds also once alt project
-        # manager is available.
-        if not test_alt_creds:
-            self._request_and_check_second_creds(
-                creds, manager_func, member_creds, show_mock, sm_count=3)
+        self._request_and_check_second_creds(
+            creds, manager_func, member_creds, show_mock, sm_count=3)
 
     def test_creds_within_same_project(self):
         self._creds_within_same_project()
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 7fb9bb3..f6f3588 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -407,7 +407,7 @@
             def get_identity_version(cls):
                 return identity_version
 
-        with testtools.ExpectedException(testtools.testcase.TestSkipped):
+        with testtools.ExpectedException(unittest.SkipTest):
             NeedAdmin().skip_checks()
         mock_iaa.assert_called_once_with('identity_version')
 
@@ -417,7 +417,7 @@
         class NeedV2(self.parent_test):
             identity_version = 'v2'
 
-        with testtools.ExpectedException(testtools.testcase.TestSkipped):
+        with testtools.ExpectedException(unittest.SkipTest):
             NeedV2().skip_checks()
 
     def test_skip_checks_identity_v3_not_available(self):
@@ -426,7 +426,7 @@
         class NeedV3(self.parent_test):
             identity_version = 'v3'
 
-        with testtools.ExpectedException(testtools.testcase.TestSkipped):
+        with testtools.ExpectedException(unittest.SkipTest):
             NeedV3().skip_checks()
 
     def test_setup_credentials_all(self):
diff --git a/tools/format.sh b/tools/format.sh
index ef5cc92..9685cdf 100755
--- a/tools/format.sh
+++ b/tools/format.sh
@@ -15,7 +15,7 @@
 
 # isort is not compatible with the default flake8 (H306), maybe flake8-isort
 # isort -rc -sl -fss ../tempest ../setup.py
-$AUTOPEP8 --exit-code --max-line-length=79 --experimental --in-place \
+$AUTOPEP8 --exit-code --max-line-length=79 --in-place \
           -r ../tempest ../setup.py
 ERROR=$?
 
diff --git a/tox.ini b/tox.ini
index 0fbc252..634b72f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py39,bashate,pip-check-reqs
+envlist = pep8,py,bashate,pip-check-reqs
 minversion = 3.18.0
 
 [tempestenv]
@@ -389,12 +389,14 @@
     {[testenv]deps}
     autopep8>=2.1.0
 commands =
-    autopep8 --exit-code --max-line-length=79 --experimental --diff -r tempest setup.py
+    autopep8 --exit-code --max-line-length=79 --diff -r tempest setup.py
     flake8 {posargs}
     check-uuid
 
 [testenv:autopep8]
 deps = autopep8>=2.1.0
+allowlist_externals =
+    {toxinidir}/tools/format.sh
 commands =
     {toxinidir}/tools/format.sh
 
@@ -473,8 +475,8 @@
     pip-extra-reqs -d --ignore-file=tempest/tests/* tempest
     pip-missing-reqs -d --ignore-file=tempest/tests/* tempest
 
-
 [testenv:bindep]
+skip_install = true
 # Do not install any requirements. We want this to be fast and work even if
 # system dependencies are missing, since it's used to tell you what system
 # dependencies are missing! This also means that bindep must be installed
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 8e0a9f3..7880bd8 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -452,6 +452,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-networking
@@ -479,6 +480,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         # Do not run it on ussuri until below issue is fixed
@@ -523,6 +525,7 @@
               - ^.*/2024.1
               - ^.*/2024.2
               - ^.*/2025.1
+              - ^.*/2025.2
               - master
         - tempest-integrated-compute
         # Do not run it on ussuri until below issue is fixed
@@ -541,6 +544,7 @@
               - ^.*/2024.1
               - ^.*/2024.2
               - ^.*/2025.1
+              - ^.*/2025.2
               - master
         - tempest-integrated-compute
         - openstacksdk-functional-devstack:
@@ -584,6 +588,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-placement
@@ -611,6 +616,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         # Do not run it on ussuri until below issue is fixed
@@ -651,6 +657,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-storage
@@ -677,6 +684,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-storage
@@ -711,6 +719,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-object-storage
@@ -737,6 +746,7 @@
         # but if project feel that is not required to run for non SLURP releases then they can opt to make it non-voting or remove it.
         - grenade-skip-level-always:
             branches:
+              - ^.*/2025.2
               - ^.*/2025.1
               - master
         - tempest-integrated-object-storage
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 9c9bc61..d9c7352 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -8,10 +8,10 @@
     check:
       jobs:
         - openstack-tox-pep8
-        - openstack-tox-py39
         - openstack-tox-py310
         - openstack-tox-py311
         - openstack-tox-py312
+        - openstack-tox-py313
         - tempest-full-py3:
             # Define list of irrelevant files to use everywhere else
             irrelevant-files: &tempest-irrelevant-files
@@ -37,9 +37,9 @@
         # if things are working in latest and oldest it will work in between
         # stable branches also. If anything is breaking we will be catching
         # those in respective stable branch gate.
-        - tempest-full-2025-1:
+        - tempest-full-2025-2:
             irrelevant-files: *tempest-irrelevant-files
-        - tempest-full-2024-1:
+        - tempest-full-2024-2:
             irrelevant-files: *tempest-irrelevant-files
         - tempest-multinode-full-py3:
             irrelevant-files: *tempest-irrelevant-files
@@ -113,22 +113,20 @@
         - neutron-ovs-tempest-dvr:
             voting: false
             irrelevant-files: *tempest-irrelevant-files
-        - interop-tempest-consistency:
-            irrelevant-files: *tempest-irrelevant-files
         - tempest-full-test-account-py3:
             voting: false
             irrelevant-files: *tempest-irrelevant-files
-        - ironic-tempest-bios-ipmi-direct-tinyipa:
+        - ironic-tempest-bios-ipmi-direct:
             irrelevant-files: *tempest-irrelevant-files
         - openstack-tox-bashate:
             irrelevant-files: *tempest-irrelevant-files-2
     gate:
       jobs:
         - openstack-tox-pep8
-        - openstack-tox-py39
         - openstack-tox-py310
         - openstack-tox-py311
         - openstack-tox-py312
+        - openstack-tox-py313
         - tempest-slow-py3:
             irrelevant-files: *tempest-irrelevant-files
         - neutron-ovs-grenade-multinode:
@@ -151,7 +149,7 @@
             irrelevant-files: *tempest-irrelevant-files
         - nova-live-migration:
             irrelevant-files: *tempest-irrelevant-files
-        - ironic-tempest-bios-ipmi-direct-tinyipa:
+        - ironic-tempest-bios-ipmi-direct:
             irrelevant-files: *tempest-irrelevant-files
     experimental:
       jobs:
@@ -184,30 +182,31 @@
             irrelevant-files: *tempest-irrelevant-files
         # Run stable releases jobs except those are running in check
         # pipeline already
-        - tempest-full-2024-2
-        - tempest-multinode-2025-1
-        - tempest-multinode-2024-2
-        - tempest-multinode-2024-1
-        - tempest-slow-2025-1
-        - tempest-slow-2024-2
-        - tempest-slow-2024-1
-        - tempest-full-2025-1-extra-tests
-        - tempest-full-2024-2-extra-tests
-        - tempest-full-2024-1-extra-tests
-    periodic-stable:
-      jobs:
         - tempest-full-2025-1
         - tempest-full-2024-2
-        - tempest-full-2024-1
+        - tempest-multinode-2025-2
         - tempest-multinode-2025-1
         - tempest-multinode-2024-2
-        - tempest-multinode-2024-1
+        - tempest-slow-2025-2
         - tempest-slow-2025-1
         - tempest-slow-2024-2
-        - tempest-slow-2024-1
+        - tempest-full-2025-2-extra-tests
         - tempest-full-2025-1-extra-tests
         - tempest-full-2024-2-extra-tests
-        - tempest-full-2024-1-extra-tests
+    periodic-stable:
+      jobs:
+        - tempest-full-2025-2
+        - tempest-full-2025-1
+        - tempest-full-2024-2
+        - tempest-multinode-2025-2
+        - tempest-multinode-2025-1
+        - tempest-multinode-2024-2
+        - tempest-slow-2025-2
+        - tempest-slow-2025-1
+        - tempest-slow-2024-2
+        - tempest-full-2025-2-extra-tests
+        - tempest-full-2025-1-extra-tests
+        - tempest-full-2024-2-extra-tests
     periodic:
       jobs:
         - tempest-all
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index 27e65b9..6d702fe 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -1,4 +1,11 @@
 # NOTE(gmann): This file includes all stable release jobs definition.
+
+- job:
+    name: tempest-full-2025-2
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-noble
+    override-checkout: stable/2025.2
+
 - job:
     name: tempest-full-2025-1
     parent: tempest-full-py3
@@ -12,10 +19,10 @@
     override-checkout: stable/2024.2
 
 - job:
-    name: tempest-full-2024-1
-    parent: tempest-full-py3
-    nodeset: openstack-single-node-jammy
-    override-checkout: stable/2024.1
+    name: tempest-full-2025-2-extra-tests
+    parent: tempest-extra-tests
+    nodeset: openstack-single-node-noble
+    override-checkout: stable/2025.2
 
 - job:
     name: tempest-full-2025-1-extra-tests
@@ -30,10 +37,10 @@
     override-checkout: stable/2024.2
 
 - job:
-    name: tempest-full-2024-1-extra-tests
-    parent: tempest-extra-tests
-    nodeset: openstack-single-node-jammy
-    override-checkout: stable/2024.1
+    name: tempest-multinode-2025-2
+    parent: tempest-multinode-full-py3
+    nodeset: openstack-two-node-noble
+    override-checkout: stable/2025.2
 
 - job:
     name: tempest-multinode-2025-1
@@ -48,10 +55,10 @@
     override-checkout: stable/2024.2
 
 - job:
-    name: tempest-multinode-2024-1
-    parent: tempest-multinode-full-py3
-    nodeset: openstack-two-node-jammy
-    override-checkout: stable/2024.1
+    name: tempest-slow-2025-2
+    parent: tempest-slow-py3
+    nodeset: openstack-two-node-noble
+    override-checkout: stable/2025.2
 
 - job:
     name: tempest-slow-2025-1
@@ -66,12 +73,6 @@
     override-checkout: stable/2024.2
 
 - job:
-    name: tempest-slow-2024-1
-    parent: tempest-slow-py3
-    nodeset: openstack-two-node-jammy
-    override-checkout: stable/2024.1
-
-- job:
     name: tempest-full-py3
     parent: devstack-tempest
     # This job version is to use the 'full' tox env which