Merge "Deprecate `volume_image_dep_tests`"
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..0adfebd 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -12,7 +12,6 @@
* 2024.2
* 2024.1
* 2023.2
-* 2023.1
For older OpenStack Release:
@@ -33,7 +32,7 @@
Tempest master supports the below python versions:
-* Python 3.8
* 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/notes/drop-python38-support-c0a696af00110602.yaml b/releasenotes/notes/drop-python38-support-c0a696af00110602.yaml
new file mode 100644
index 0000000..035f628
--- /dev/null
+++ b/releasenotes/notes/drop-python38-support-c0a696af00110602.yaml
@@ -0,0 +1,8 @@
+---
+prelude: >
+ Tempest dropped the Python 3.8 support.
+upgrade:
+ - |
+ Python 3.8 support has been dropped. Last release of Tempest
+ to support python 3.8 is Temepst 41.0.0. The minimum version
+ of Python now supported by Tempest is Python 3.9.
diff --git a/releasenotes/notes/end-of-support-of-2023-1-ddec1dac59700063.yaml b/releasenotes/notes/end-of-support-of-2023-1-ddec1dac59700063.yaml
new file mode 100644
index 0000000..d52b54e
--- /dev/null
+++ b/releasenotes/notes/end-of-support-of-2023-1-ddec1dac59700063.yaml
@@ -0,0 +1,12 @@
+---
+prelude: >
+ This is an intermediate release during the 2025.1 development cycle to
+ mark the end of support for 2023.1 release in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * 2024.2
+ * 2024.1
+ * 2023.2
+
+ Current development of Tempest is for OpenStack 2025.1 development
+ cycle.
diff --git a/releasenotes/notes/image-enforcement-config-0bc67791a40bac56.yaml b/releasenotes/notes/image-enforcement-config-0bc67791a40bac56.yaml
new file mode 100644
index 0000000..2bbc82b
--- /dev/null
+++ b/releasenotes/notes/image-enforcement-config-0bc67791a40bac56.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Add a new config option
+ `[image_feature_enabled]/image_format_enforcement` which tells tempest
+ that glance will do image format inspection and enforcement on upload. This
+ will disable tests that require glance to accept a bad image in order to
+ test another service (i.e. nova).
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/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..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
@@ -13,7 +10,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/roles/run-tempest/tasks/main.yaml b/roles/run-tempest/tasks/main.yaml
index 29409c0..15b1743 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 Wallaby
+- name: Use stable branch upper-constraints till 2023.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", "unmaintained/victoria", "unmaintained/wallaby"]
+ 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"]
- name: Use Configured upper-constraints for non-master Tempest
set_fact:
diff --git a/setup.cfg b/setup.cfg
index bb1ced5..67555f4 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
@@ -15,10 +14,10 @@
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.8
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/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_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 8984d1d..eddfd73 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -316,6 +316,7 @@
_, servers = compute.create_test_server(
self.os_primary, tenant_network=network,
validatable=True,
+ wait_until='ACTIVE',
validation_resources=validation_resources)
return servers[0]
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/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/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/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index e9c7dba..9309c76 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -536,6 +536,15 @@
for container_fmt in container_fmts
for disk_fmt in disk_fmts]
+ # NOTE(danms): This tests depends on being able to lie about image
+ # content. We can probably improve this in some way, but without a
+ # valid sample of each image format in each container format, there is
+ # no easy solution.
+ if CONF.image_feature_enabled.image_format_enforcement:
+ raise cls.skipException(
+ 'Image format enforcement prevents testing with '
+ 'bogus image data')
+
for (container_fmt, disk_fmt) in all_pairs[:6]:
LOG.debug("Creating an image "
"(Container format: %s, Disk format: %s).",
diff --git a/tempest/api/image/v2/test_images_formats.py b/tempest/api/image/v2/test_images_formats.py
index 48f1325..f0dec90 100644
--- a/tempest/api/image/v2/test_images_formats.py
+++ b/tempest/api/image/v2/test_images_formats.py
@@ -166,6 +166,12 @@
# a properly-formatted image for it, so skip it.
self.skipTest(
'Format %s not allowed by config' % self.imgdef['format'])
+ if CONF.image_feature_enabled.image_format_enforcement:
+ # If glance rejects bad images during upload, we cannot get them
+ # registered so that we can test nova.
+ self.skipTest(
+ 'Unable to test compute image formats if glance does not '
+ 'allow them to be uploaded')
# VMDK with footer was not supported by earlier service versions,
# so we need to tolerate it passing and failing (skip for the latter).
@@ -191,6 +197,12 @@
@decorators.idempotent_id('ffe21610-e801-4992-9b81-e2d646e2e2e9')
def test_compute_rejects_format_mismatch(self):
"""Make sure compute rejects any image with a format mismatch."""
+ if CONF.image_feature_enabled.image_format_enforcement:
+ # If glance rejects bad images during upload, we cannot get them
+ # registered so that we can test nova.
+ self.skipTest(
+ 'Unable to test compute image formats if glance does not '
+ 'allow them to be uploaded')
# Lying about the disk_format should always fail
override_fmt = (
self.imgdef['format'] in ('raw', 'gpt') and 'qcow2' or 'raw')
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index b4bfc61..8b4766c 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -48,12 +48,13 @@
@decorators.idempotent_id('f164801e-1dd8-4b8b-b5d3-cc3ac77cfaa5')
def test_dhcp_port_status_active(self):
- ports = self.admin_ports_client.list_ports(
+ dhcp_ports = self.admin_ports_client.list_ports(
+ device_owner='network:dhcp',
network_id=self.network['id'])['ports']
- for port in ports:
+ for dhcp_port in dhcp_ports:
waiters.wait_for_port_status(
client=self.admin_ports_client,
- port_id=port['id'],
+ port_id=dhcp_port['id'],
status='ACTIVE')
@decorators.idempotent_id('5032b1fe-eb42-4a64-8f3b-6e189d8b5c7d')
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 0dd7c70..fedf2f4 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -32,16 +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)
+ 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):
- 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()
@@ -106,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'])
+ 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
@@ -183,9 +179,11 @@
next_cidr = next_cidr.next()
# Add router interface with subnet id
- self.create_router_interface(router['id'], subnet['id'])
- self.addCleanup(self._remove_router_interface_with_subnet_id,
- router['id'], subnet['id'])
+ interface = self.create_router_interface(router['id'],
+ subnet['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 9675ec8..ec1a4c8 100644
--- a/tempest/common/utils/net_downtime.py
+++ b/tempest/common/utils/net_downtime.py
@@ -22,12 +22,38 @@
LOG = log.getLogger(__name__)
+PASSED = 'PASSED'
+FAILED = 'FAILED'
+METADATA_SCRIPT_PATH = '/tmp/metadata_meter_script.sh'
+METADATA_RESULTS_PATH = '/tmp/metadata_meter.log'
+METADATA_PID_PATH = '/tmp/metadata_meter.pid'
+# /proc/uptime is used because it include two decimals in cirros, while
+# `date +%s.%N` does not work in cirros (min granularity is seconds)
+METADATA_SCRIPT = """#!/bin/sh
+echo $$ > %(metadata_meter_pidfile)s
+old_time=$(cut -d" " -f1 /proc/uptime)
+while true; do
+ curl http://169.254.169.254/latest/meta-data/hostname 2>/dev/null | \
+grep -q `hostname`
+ result=$?
+ new_time=$(cut -d" " -f1 /proc/uptime)
+ runtime=$(awk -v new=$new_time -v old=$old_time "BEGIN {print new-old}")
+ old_time=$new_time
+ if [ $result -eq 0 ]; then
+ echo "PASSED $runtime"
+ else
+ echo "FAILED $runtime"
+ fi
+ sleep %(interval)s
+done
+"""
+
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):
@@ -35,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
@@ -57,7 +83,68 @@
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)
+
+
+class MetadataDowntimeMeter(fixtures.Fixture):
+ def __init__(self, ssh_client,
+ interval='0.2', script_path=METADATA_SCRIPT_PATH,
+ output_path=METADATA_RESULTS_PATH,
+ pidfile_path=METADATA_PID_PATH):
+ self.ssh_client = ssh_client
+ self.interval = interval
+ self.script_path = script_path
+ self.output_path = output_path
+ self.pidfile_path = pidfile_path
+ self.pid = None
+
+ def _setUp(self):
+ self.addCleanup(self.cleanup)
+ self.upload_metadata_script()
+ self.run_metadata_script()
+
+ def upload_metadata_script(self):
+ metadata_script = METADATA_SCRIPT % {
+ 'metadata_meter_pidfile': self.pidfile_path,
+ 'interval': self.interval}
+ echo_cmd = "echo '{}' > {}".format(
+ metadata_script, self.script_path)
+ 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)
+ 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,
+ self.output_path))
+ self.pid = self.ssh_client.exec_command(
+ 'cat {}'.format(self.pidfile_path)).strip()
+ LOG.debug('running metadata downtime meter script in background with '
+ 'PID = %s', self.pid)
+
+ def get_results(self):
+ output = self.ssh_client.exec_command(
+ 'cat {}'.format(self.output_path))
+ results = {}
+ results['successes'] = output.count(PASSED)
+ results['failures'] = output.count(FAILED)
+ downtime = {PASSED: 0.0, FAILED: 0.0}
+ for line in output.splitlines():
+ key, value = line.strip().split()
+ downtime[key] += float(value)
+
+ results['downtime'] = downtime
+ LOG.debug('metadata downtime meter results: %r', results)
+ return results
+
+ def cleanup(self):
+ if self.pid:
+ self.ssh_client.exec_command('kill {}'.format(self.pid))
+ LOG.debug('killed metadata downtime script with PID %s', self.pid)
+ else:
+ LOG.debug('No metadata downtime script found')
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 0d691b9..7719720 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -728,6 +728,11 @@
cfg.BoolOpt('image_conversion',
default=False,
help=('Is image_conversion enabled in glance.')),
+ cfg.BoolOpt('image_format_enforcement',
+ default=True,
+ help=('Indicates that image format is enforced by glance, '
+ 'such that we should not expect to be able to upload '
+ 'bad images for testing other services.')),
]
network_group = cfg.OptGroup(name='network',
@@ -964,6 +969,12 @@
"migration, in seconds. "
"When the measured downtime exceeds this value, an "
"exception is raised."),
+ cfg.FloatOpt('allowed_metadata_downtime',
+ default=6.0,
+ help="Allowed VM metadata connection downtime during live "
+ "migration, in seconds. "
+ "When the measured downtime exceeds this value, an "
+ "exception is raised."),
]
volume_group = cfg.OptGroup(name='volume',
@@ -978,7 +989,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='',
@@ -1112,7 +1123,7 @@
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')
]
@@ -1344,9 +1355,9 @@
The best use case is investigating used resources of one test.
A test can be run as follows:
- $ stestr run --pdb TEST_ID
+$ stestr run --pdb TEST_ID
or
- $ python -m testtools.run TEST_ID"""),
+$ python -m testtools.run TEST_ID"""),
cfg.StrOpt('resource_name_prefix',
default='tempest',
help="Define the prefix name for the resources created by "
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/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/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/common/rest_client.py b/tempest/lib/common/rest_client.py
index b656b7a..b360569 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -881,6 +881,11 @@
resp_body = self._parse_resp(resp_body)
raise exceptions.Gone(resp_body, resp=resp)
+ if resp.status == 406:
+ if parse_resp:
+ resp_body = self._parse_resp(resp_body)
+ raise exceptions.NotAcceptable(resp_body, resp=resp)
+
if resp.status == 409:
if parse_resp:
resp_body = self._parse_resp(resp_body)
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index dd7885e..0242de2 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -94,6 +94,11 @@
message = "Object not found"
+class NotAcceptable(ClientRestClientException):
+ status_code = 406
+ message = "Not Acceptable"
+
+
class Conflict(ClientRestClientException):
status_code = 409
message = "Conflict with state of target resource"
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/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_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 911ff42..f4ee98d 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -17,6 +17,7 @@
from oslo_log import log
from tempest.common import utils
+from tempest.common.utils.linux import remote_client
from tempest.common.utils import net_downtime
from tempest.common import waiters
from tempest import config
@@ -189,6 +190,12 @@
floating_ip['floating_ip_address'])
self.useFixture(downtime_meter)
+ metadata_downtime_meter = net_downtime.MetadataDowntimeMeter(
+ remote_client.RemoteClient(floating_ip['floating_ip_address'],
+ CONF.validation.image_ssh_user,
+ pkey=keypair['private_key']))
+ self.useFixture(metadata_downtime_meter)
+
migration_kwargs = {'host': None, 'block_migration': block_migration}
# check if microversion is less than 2.25 because of
@@ -230,6 +237,18 @@
"Downtime of {} seconds is higher than expected '{}'".format(
downtime, allowed_downtime))
+ metadata_downtime_results = metadata_downtime_meter.get_results()
+ self.assertGreater(metadata_downtime_results['successes'], 0)
+ LOG.debug("Metadata Downtime seconds measured = %r",
+ metadata_downtime_results['downtime'])
+ allowed_metadata_downtime = CONF.validation.allowed_metadata_downtime
+ metadata_downtime_failed = \
+ metadata_downtime_results['downtime']['FAILED']
+ self.assertLessEqual(
+ metadata_downtime_failed, allowed_metadata_downtime,
+ "Metadata downtime: {} seconds is higher than expected: {}".format(
+ metadata_downtime_failed, allowed_metadata_downtime))
+
def _test_server_connectivity_cold_migration_revert(self, source_host=None,
dest_host=None):
keypair = self.create_keypair(client=self.keypairs_client)
diff --git a/tempest/scenario/test_unified_limits.py b/tempest/scenario/test_unified_limits.py
index 6e194f9..7e8f8b2 100644
--- a/tempest/scenario/test_unified_limits.py
+++ b/tempest/scenario/test_unified_limits.py
@@ -32,6 +32,13 @@
credentials = ['primary', 'system_admin']
@classmethod
+ def skip_checks(cls):
+ super(ImageQuotaTest, cls).skip_checks()
+ if not CONF.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
def resource_setup(cls):
super(ImageQuotaTest, cls).resource_setup()
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/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/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/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/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 8077308..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
@@ -330,6 +333,7 @@
devstack_localrc:
CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
ENABLE_VOLUME_MULTIATTACH: true
+ GLANCE_ENFORCE_IMAGE_FORMAT: false
devstack_plugins:
neutron: https://opendev.org/openstack/neutron
devstack_services:
@@ -343,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
@@ -430,6 +455,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 +479,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 +524,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 +541,7 @@
branches:
- ^.*/2023.2
- ^.*/2024.1
+ - ^.*/2024.2
- master
- tempest-integrated-compute
- openstacksdk-functional-devstack:
@@ -542,6 +579,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 +603,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 +640,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 +663,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 +694,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 +717,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..2f21c2d 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -8,10 +8,10 @@
check:
jobs:
- openstack-tox-pep8
- - openstack-tox-py38
- 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
@@ -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:
@@ -37,9 +45,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-2024-1:
+ - tempest-full-2024-2:
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-2023-1:
+ - tempest-full-2023-2:
irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
irrelevant-files: *tempest-irrelevant-files
@@ -126,10 +134,10 @@
gate:
jobs:
- openstack-tox-pep8
- - openstack-tox-py38
- openstack-tox-py39
- openstack-tox-py310
- openstack-tox-py311
+ - openstack-tox-py312
- tempest-slow-py3:
irrelevant-files: *tempest-irrelevant-files
- neutron-ovs-grenade-multinode:
@@ -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
@@ -185,15 +201,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
periodic:
jobs:
- tempest-all
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index cb1330f..5785ec6 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
@@ -12,10 +18,10 @@
override-checkout: stable/2023.2
- job:
- name: tempest-full-2023-1
- parent: tempest-full-py3
+ name: tempest-full-2024-2-extra-tests
+ parent: tempest-extra-tests
nodeset: openstack-single-node-jammy
- override-checkout: stable/2023.1
+ override-checkout: stable/2024.2
- job:
name: tempest-full-2024-1-extra-tests
@@ -30,10 +36,10 @@
override-checkout: stable/2023.2
- job:
- name: tempest-full-2023-1-extra-tests
- parent: tempest-extra-tests
- nodeset: openstack-single-node-jammy
- override-checkout: stable/2023.1
+ 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
@@ -48,19 +54,14 @@
override-checkout: stable/2023.2
- job:
- name: tempest-slow-2023-1
- parent: tempest-slow-py3
- nodeset: openstack-two-node-jammy
- override-checkout: stable/2023.1
-
-- job:
name: tempest-full-py3
parent: devstack-tempest
# This job version is to use the 'full' tox env which
- # is available for stable/ussuri to stable/wallaby also.
+ # is available for unmaintained/victoria to unmaintained/xena also.
branches:
- ^.*/victoria
- ^.*/wallaby
+ - ^.*/xena
description: |
Base integration test with Neutron networking, horizon, swift enable,
and py3.
@@ -71,6 +72,10 @@
- openstack/horizon
vars:
tox_envlist: full
+ tempest_exclude_regex: "\
+ (DHCPAgentSchedulersTestJSON)|\
+ (AttachVolumeMultiAttachTest)|\
+ (UpdateMultiattachVolumeNegativeTest)"
devstack_localrc:
USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true
@@ -85,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:
@@ -109,11 +134,30 @@
name: tempest-multinode-full
parent: tempest-multinode-full-base
nodeset: openstack-two-node-focal
- # This job runs on Focal and on python2. This is for unmaintained/victoria to unmaintained/zed.
+ # This job runs on Focal and on python2. This is for unmaintained/victoria to unmaintained/xena.
branches:
- ^.*/victoria
- ^.*/wallaby
- ^.*/xena
+ vars:
+ tox_envlist: full
+ tempest_exclude_regex: "\
+ (DHCPAgentSchedulersTestJSON)|\
+ (AttachVolumeMultiAttachTest)|\
+ (UpdateMultiattachVolumeNegativeTest)"
+ devstack_localrc:
+ USE_PYTHON3: False
+ group-vars:
+ subnode:
+ devstack_localrc:
+ USE_PYTHON3: False
+
+- job:
+ name: tempest-multinode-full
+ parent: tempest-multinode-full-base
+ nodeset: openstack-two-node-focal
+ # This job runs on Focal and on python2. This is for unmaintained/yoga to unmaintained/zed.
+ branches:
- ^.*/yoga
- ^.*/zed
vars:
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