Merge "Stop violating image disk_format rules"
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 20ace9e..33e75ff 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -454,6 +454,10 @@
 
   .. _2.86: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id79
 
+  * `2.96`_
+
+  .. _2.96: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-2024-1-caracal-and-2024-2-dalmatian
+
 * Volume
 
   * `3.3`_
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index c4631d8..fe98630 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -37,3 +37,4 @@
 * Python 3.9
 * Python 3.10
 * Python 3.11
+* Python 3.12
diff --git a/releasenotes/notes/change-volume-catalog_type-default-fbcb2be6ebc42818.yaml b/releasenotes/notes/change-volume-catalog_type-default-fbcb2be6ebc42818.yaml
new file mode 100644
index 0000000..a507bd7
--- /dev/null
+++ b/releasenotes/notes/change-volume-catalog_type-default-fbcb2be6ebc42818.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    The default for ``[volume] catalog_type``, which is used to determine the
+    service type to use to identify the block storage service in the service
+    catalog, has changed from ``volumev3`` to ``block-storage``.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 475a99c..633d90e 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
    :maxdepth: 1
 
    unreleased
+   v41.0.0
    v40.0.0
    v39.0.0
    v38.0.0
diff --git a/releasenotes/source/v41.0.0.rst b/releasenotes/source/v41.0.0.rst
new file mode 100644
index 0000000..6d79c4c
--- /dev/null
+++ b/releasenotes/source/v41.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v41.0.0 Release Notes
+=====================
+
+.. release-notes:: 41.0.0 Release Notes
+   :version: 41.0.0
diff --git a/requirements.txt b/requirements.txt
index b0df18b..d2f13a5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,7 +13,7 @@
 oslo.log>=3.36.0 # Apache-2.0
 stestr>=1.0.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=4.7.0 # Apache-2.0
+oslo.utils>=7.0.0 # Apache-2.0
 fixtures>=3.0.0 # Apache-2.0/BSD
 PyYAML>=3.12 # MIT
 python-subunit>=1.0.0 # Apache-2.0/BSD
diff --git a/setup.cfg b/setup.cfg
index bb1ced5..ca2b324 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,7 +6,6 @@
 author = OpenStack
 author_email = openstack-discuss@lists.openstack.org
 home_page = https://docs.openstack.org/tempest/latest/
-python_requires = >=3.8
 classifier =
     Intended Audience :: Information Technology
     Intended Audience :: System Administrators
@@ -19,6 +18,7 @@
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
     Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: Implementation :: CPython
 
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index c72b74e..e7e84d6 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -263,3 +263,22 @@
         servers = self.servers_client.list_servers(
             detail=True, **params)['servers']
         self.assertNotEmpty(servers)
+
+
+class ServersListShow296Test(base.BaseV2ComputeTest):
+    """Test compute server with microversion >= than 2.96
+
+    This test tests the Server APIs response schema for 2.96 microversion.
+    No specific assert or behaviour verification is needed.
+    """
+
+    min_microversion = '2.96'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('4eee1ffe-9e00-4c99-a431-0d3e0f323a8f')
+    def test_list_show_server_296(self):
+        server = self.create_test_server()
+        # Checking list API response schema.
+        self.servers_client.list_servers(detail=True)
+        # Checking show API response schema
+        self.servers_client.show_server(server['id'])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 0dd7c70..aaedba2 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -18,6 +18,7 @@
 
 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,11 +34,17 @@
         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)
+                        router_id, subnet_id, interface['port_id'])
         self.assertEqual(subnet_id, interface['subnet_id'])
         return interface
 
-    def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+    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'])
@@ -107,7 +114,7 @@
         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'])
+                        router['id'], subnet['id'], interface['port_id'])
         self.assertIn('subnet_id', interface.keys())
         self.assertIn('port_id', interface.keys())
         # Verify router id is equal to device id in port details
@@ -183,9 +190,10 @@
             next_cidr = next_cidr.next()
 
             # Add router interface with subnet id
-            self.create_router_interface(router['id'], subnet['id'])
+            interface = self.create_router_interface(router['id'],
+                                                     subnet['id'])
             self.addCleanup(self._remove_router_interface_with_subnet_id,
-                            router['id'], subnet['id'])
+                            router['id'], subnet['id'], interface['port_id'])
             cidr = netaddr.IPNetwork(subnet['cidr'])
             next_hop = str(cidr[2])
             destination = str(subnet['cidr'])
diff --git a/tempest/config.py b/tempest/config.py
index 471782b..bdc1dce 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -978,7 +978,7 @@
                help='Timeout in seconds to wait for a volume to become '
                     'available.'),
     cfg.StrOpt('catalog_type',
-               default='volumev3',
+               default='block-storage',
                help="Catalog type of the Volume Service"),
     cfg.StrOpt('region',
                default='',
diff --git a/tempest/lib/api_schema/response/compute/v2_96/__init__.py b/tempest/lib/api_schema/response/compute/v2_96/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_96/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_96/servers.py b/tempest/lib/api_schema/response/compute/v2_96/servers.py
new file mode 100644
index 0000000..7036a11
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_96/servers.py
@@ -0,0 +1,62 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_89 import servers as servers289
+
+
+###########################################################################
+#
+# 2.96:
+#
+# The attachment_id and bdm_uuid parameter is now returned in the response body
+# of the following calls:
+# The pinned_availability_zone parameter is now returned in the response body
+# of the following calls:
+#
+# - GET /servers/detail
+# - GET /servers/{server_id}
+###########################################################################
+
+get_server = copy.deepcopy(servers289.get_server)
+get_server['response_body']['properties']['server'][
+    'properties'].update(
+        {'pinned_availability_zone': {'type': ['string', 'null']}})
+
+list_servers_detail = copy.deepcopy(servers289.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'properties'].update(
+        {'pinned_availability_zone': {'type': ['string', 'null']}})
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.89***
+attach_volume = copy.deepcopy(servers289.attach_volume)
+show_volume_attachment = copy.deepcopy(servers289.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers289.list_volume_attachments)
+rebuild_server = copy.deepcopy(servers289.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers289.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers289.update_server)
+list_servers = copy.deepcopy(servers289.list_servers)
+show_server_diagnostics = copy.deepcopy(servers289.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers289.get_remote_consoles)
+list_tags = copy.deepcopy(servers289.list_tags)
+update_all_tags = copy.deepcopy(servers289.update_all_tags)
+delete_all_tags = copy.deepcopy(servers289.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers289.check_tag_existence)
+update_tag = copy.deepcopy(servers289.update_tag)
+delete_tag = copy.deepcopy(servers289.delete_tag)
+show_instance_action = copy.deepcopy(servers289.show_instance_action)
+create_backup = copy.deepcopy(servers289.create_backup)
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 8bdf98e..12fffdb 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -21,6 +21,7 @@
 from urllib import parse as urlparse
 
 from oslo_log import log as logging
+from oslo_utils import timeutils
 
 from tempest.lib import exceptions
 from tempest.lib.services.identity.v2 import token_client as json_v2id
@@ -419,8 +420,7 @@
     def is_expired(self, auth_data):
         _, access = auth_data
         expiry = self._parse_expiry_time(access['token']['expires'])
-        return (expiry - self.token_expiry_threshold <=
-                datetime.datetime.utcnow())
+        return (expiry - self.token_expiry_threshold <= timeutils.utcnow())
 
 
 class KeystoneV3AuthProvider(KeystoneAuthProvider):
@@ -595,8 +595,7 @@
     def is_expired(self, auth_data):
         _, access = auth_data
         expiry = self._parse_expiry_time(access['expires_at'])
-        return (expiry - self.token_expiry_threshold <=
-                datetime.datetime.utcnow())
+        return (expiry - self.token_expiry_threshold <= timeutils.utcnow())
 
 
 def is_identity_version_supported(identity_version):
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 1b93f91..e91c87a 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -45,6 +45,7 @@
 from tempest.lib.api_schema.response.compute.v2_8 import servers as schemav28
 from tempest.lib.api_schema.response.compute.v2_89 import servers as schemav289
 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
+from tempest.lib.api_schema.response.compute.v2_96 import servers as schemav296
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
@@ -75,7 +76,8 @@
         {'min': '2.73', 'max': '2.74', 'schema': schemav273},
         {'min': '2.75', 'max': '2.78', 'schema': schemav275},
         {'min': '2.79', 'max': '2.88', 'schema': schemav279},
-        {'min': '2.89', 'max': None, 'schema': schemav289}]
+        {'min': '2.89', 'max': '2.95', 'schema': schemav289},
+        {'min': '2.96', 'max': None, 'schema': schemav296}]
 
     def __init__(self, auth_provider, service, region,
                  enable_instance_password=True, **kwargs):
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index fa43e58..3df9f19 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -498,7 +498,7 @@
                 return ('token',
                         {'serviceCatalog': [{'type': 'compute'},
                                             {'type': 'image'},
-                                            {'type': 'volumev3'},
+                                            {'type': 'block-storage'},
                                             {'type': 'network'},
                                             {'type': 'object-store'}]})
 
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index 3edb122..4e5ec48 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -17,6 +17,7 @@
 import datetime
 
 import fixtures
+from oslo_utils import timeutils
 import testtools
 
 from tempest.lib import auth
@@ -509,15 +510,15 @@
         self._test_base_url_helper(expected, filters, ('token', auth_data))
 
     def test_token_not_expired(self):
-        expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+        expiry_data = timeutils.utcnow() + datetime.timedelta(days=1)
         self._verify_expiry(expiry_data=expiry_data, should_be_expired=False)
 
     def test_token_expired(self):
-        expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
+        expiry_data = timeutils.utcnow() - datetime.timedelta(hours=1)
         self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
 
     def test_token_not_expired_to_be_renewed(self):
-        expiry_data = (datetime.datetime.utcnow() +
+        expiry_data = (timeutils.utcnow() +
                        self.auth_provider.token_expiry_threshold / 2)
         self._verify_expiry(expiry_data=expiry_data, should_be_expired=True)
 
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 8077308..56c65c0 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -430,6 +430,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-networking
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
@@ -449,6 +454,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
         # and job is broken up to wallaby branch due to the issue
@@ -489,6 +499,7 @@
             branches:
               - ^.*/2023.2
               - ^.*/2024.1
+              - ^.*/2024.2
               - master
         - tempest-integrated-compute
         # Do not run it on ussuri until below issue is fixed
@@ -505,6 +516,7 @@
             branches:
               - ^.*/2023.2
               - ^.*/2024.1
+              - ^.*/2024.2
               - master
         - tempest-integrated-compute
         - openstacksdk-functional-devstack:
@@ -542,6 +554,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-placement
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
@@ -561,6 +578,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
         # and job is broken up to wallaby branch due to the issue
@@ -593,6 +615,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
@@ -611,6 +638,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
@@ -637,6 +669,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-object-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
@@ -655,6 +692,11 @@
         - grenade-skip-level:
             branches:
               - ^.*/2024.1
+        # on current master 2025.1(SLURP) grenade-skip-level-always is voting
+        # which test stable/2024.1 to 2025.1 upgrade.
+        - grenade-skip-level-always:
+            branches:
+              - master
         - tempest-integrated-object-storage
         # Do not run it on ussuri until below issue is fixed
         # https://storyboard.openstack.org/#!/story/2010057
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index e284487..661c8d0 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -12,6 +12,7 @@
         - openstack-tox-py39
         - openstack-tox-py310
         - openstack-tox-py311
+        - openstack-tox-py312
         - tempest-full-py3:
             # Define list of irrelevant files to use everywhere else
             irrelevant-files: &tempest-irrelevant-files
@@ -37,7 +38,7 @@
         # 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-2024-1:
+        - tempest-full-2024-2:
             irrelevant-files: *tempest-irrelevant-files
         - tempest-full-2023-1:
             irrelevant-files: *tempest-irrelevant-files
@@ -130,6 +131,7 @@
         - openstack-tox-py39
         - openstack-tox-py310
         - openstack-tox-py311
+        - openstack-tox-py312
         - tempest-slow-py3:
             irrelevant-files: *tempest-irrelevant-files
         - neutron-ovs-grenade-multinode:
@@ -185,12 +187,15 @@
             irrelevant-files: *tempest-irrelevant-files
     periodic-stable:
       jobs:
+        - tempest-full-2024-2
         - tempest-full-2024-1
         - tempest-full-2023-2
         - tempest-full-2023-1
+        - tempest-slow-2024-2
         - tempest-slow-2024-1
         - tempest-slow-2023-2
         - tempest-slow-2023-1
+        - tempest-full-2024-2-extra-tests
         - tempest-full-2024-1-extra-tests
         - tempest-full-2023-2-extra-tests
         - tempest-full-2023-1-extra-tests
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index cb1330f..6bf804a 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -1,5 +1,11 @@
 # NOTE(gmann): This file includes all stable release jobs definition.
 - job:
+    name: tempest-full-2024-2
+    parent: tempest-full-py3
+    nodeset: openstack-single-node-jammy
+    override-checkout: stable/2024.2
+
+- job:
     name: tempest-full-2024-1
     parent: tempest-full-py3
     nodeset: openstack-single-node-jammy
@@ -18,6 +24,12 @@
     override-checkout: stable/2023.1
 
 - job:
+    name: tempest-full-2024-2-extra-tests
+    parent: tempest-extra-tests
+    nodeset: openstack-single-node-jammy
+    override-checkout: stable/2024.2
+
+- job:
     name: tempest-full-2024-1-extra-tests
     parent: tempest-extra-tests
     nodeset: openstack-single-node-jammy
@@ -36,6 +48,12 @@
     override-checkout: stable/2023.1
 
 - job:
+    name: tempest-slow-2024-2
+    parent: tempest-slow-py3
+    nodeset: openstack-two-node-jammy
+    override-checkout: stable/2024.2
+
+- job:
     name: tempest-slow-2024-1
     parent: tempest-slow-py3
     nodeset: openstack-two-node-jammy