Merge "Bump hacking to 6.1.0"
diff --git a/.zuul.yaml b/.zuul.yaml
index 2b6b59c..cc74f88 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -21,13 +21,11 @@
- cinder-tempest-plugin-cbak-ceph
- cinder-tempest-plugin-cbak-s3
# As per the Tempest "Stable Branch Support Policy", Tempest will only
- # support the "Maintained" stable branches and not the "Extended Maintained"
- # branches. That is what we need to do for all tempest plugins. Only jobs
- # for the current releasable ("Maintained") stable branches should be listed
- # here.
+ # support the "Maintained" stable branches, so only jobs for the
+ # current stable branches should be listed here.
+ - cinder-tempest-plugin-basic-2024-1
- cinder-tempest-plugin-basic-2023-2
- cinder-tempest-plugin-basic-2023-1
- - cinder-tempest-plugin-basic-zed
- cinder-tempest-plugin-protection-functional
gate:
jobs:
@@ -38,9 +36,9 @@
- cinder-tempest-plugin-cbak-ceph
experimental:
jobs:
+ - cinder-tempest-plugin-cbak-ceph-2024-1
- cinder-tempest-plugin-cbak-ceph-2023-2
- cinder-tempest-plugin-cbak-ceph-2023-1
- - cinder-tempest-plugin-cbak-ceph-zed
- job:
name: cinder-tempest-plugin-protection-functional
@@ -256,6 +254,11 @@
Integration tests that runs with the ceph devstack plugin, py3
and enable the backup service.
vars:
+ # FIXME: change I29b1af0a4034decad to tempest added image format tests that
+ # cannot pass in this job because the image data takes a optimized path that
+ # bypasses nova's checks. Until the nova team decides on a strategy to handle
+ # this issue, we skip these tests.
+ tempest_exclude_regex: (tempest.api.image.v2.test_images_formats.ImagesFormatTest.test_compute_rejects)
configure_swap_size: 4096
devstack_local_conf:
test-config:
@@ -269,6 +272,12 @@
timeout: 10800
- job:
+ name: cinder-tempest-plugin-cbak-ceph-2024-1
+ parent: cinder-tempest-plugin-cbak-ceph
+ nodeset: openstack-single-node-jammy
+ override-checkout: stable/2024.1
+
+- job:
name: cinder-tempest-plugin-cbak-ceph-2023-2
parent: cinder-tempest-plugin-cbak-ceph
nodeset: openstack-single-node-jammy
@@ -280,12 +289,6 @@
nodeset: openstack-single-node-jammy
override-checkout: stable/2023.1
-- job:
- name: cinder-tempest-plugin-cbak-ceph-zed
- parent: cinder-tempest-plugin-cbak-ceph
- nodeset: openstack-single-node-focal
- override-checkout: stable/zed
-
# variant for pre-Ussuri branches (no volume revert for Ceph),
# should this job be used on those branches
- job:
@@ -416,6 +419,12 @@
- ^releasenotes/.*$
- job:
+ name: cinder-tempest-plugin-basic-2024-1
+ parent: cinder-tempest-plugin-basic
+ nodeset: openstack-single-node-jammy
+ override-checkout: stable/2024.1
+
+- job:
name: cinder-tempest-plugin-basic-2023-2
parent: cinder-tempest-plugin-basic
nodeset: openstack-single-node-jammy
@@ -426,9 +435,3 @@
parent: cinder-tempest-plugin-basic
nodeset: openstack-single-node-jammy
override-checkout: stable/2023.1
-
-- job:
- name: cinder-tempest-plugin-basic-zed
- parent: cinder-tempest-plugin-basic
- nodeset: openstack-single-node-focal
- override-checkout: stable/zed
diff --git a/cinder_tempest_plugin/api/volume/base.py b/cinder_tempest_plugin/api/volume/base.py
index 1fd82bf..c0f53bd 100644
--- a/cinder_tempest_plugin/api/volume/base.py
+++ b/cinder_tempest_plugin/api/volume/base.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import io
+
from tempest.common import compute
from tempest.common import waiters
from tempest import config
@@ -158,6 +160,29 @@
body['id'])
return body
+ @classmethod
+ def create_image_with_data(cls, **kwargs):
+ # we do this as a class method so we can use the
+ # addClassResourceCleanup functionality of tempest.test.BaseTestCase
+ images_client = cls.os_primary.image_client_v2
+ if 'min_disk' not in kwargs:
+ kwargs['min_disk'] = 1
+ response = images_client.create_image(**kwargs)
+ image_id = response['id']
+ cls.addClassResourceCleanup(
+ images_client.wait_for_resource_deletion, image_id)
+ cls.addClassResourceCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ images_client.delete_image, image_id)
+
+ # upload "data" to image
+ image_file = io.BytesIO(data_utils.random_bytes(size=1024))
+ images_client.store_image_file(image_id, image_file)
+
+ waiters.wait_for_image_status(images_client, image_id, 'active')
+ image = images_client.show_image(image_id)
+ return image
+
class BaseVolumeAdminTest(BaseVolumeTest):
"""Base test case class for all Volume Admin API tests."""
diff --git a/cinder_tempest_plugin/api/volume/test_create_from_image.py b/cinder_tempest_plugin/api/volume/test_create_from_image.py
index acb1943..f44f630 100644
--- a/cinder_tempest_plugin/api/volume/test_create_from_image.py
+++ b/cinder_tempest_plugin/api/volume/test_create_from_image.py
@@ -10,12 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import io
-
-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
from tempest.lib import decorators
from cinder_tempest_plugin.api.volume import base
@@ -32,29 +27,6 @@
if not CONF.service_available.glance:
raise cls.skipException("Glance service is disabled")
- @classmethod
- def create_image_with_data(cls, **kwargs):
- # we do this as a class method so we can use the
- # addClassResourceCleanup functionality of tempest.test.BaseTestCase
- images_client = cls.os_primary.image_client_v2
- if 'min_disk' not in kwargs:
- kwargs['min_disk'] = 1
- response = images_client.create_image(**kwargs)
- image_id = response['id']
- cls.addClassResourceCleanup(
- images_client.wait_for_resource_deletion, image_id)
- cls.addClassResourceCleanup(
- test_utils.call_and_ignore_notfound_exc,
- images_client.delete_image, image_id)
-
- # upload "data" to image
- image_file = io.BytesIO(data_utils.random_bytes(size=1024))
- images_client.store_image_file(image_id, image_file)
-
- waiters.wait_for_image_status(images_client, image_id, 'active')
- image = images_client.show_image(image_id)
- return image
-
@decorators.idempotent_id('6e9266ff-a917-4dd5-aa4a-c36e59e7a2a6')
def test_create_from_image_with_volume_type_image_property(self):
"""Verify that the cinder_img_volume_type image property works.
diff --git a/cinder_tempest_plugin/api/volume/test_volume_dependency.py b/cinder_tempest_plugin/api/volume/test_volume_dependency.py
index b204e84..5ea067f 100644
--- a/cinder_tempest_plugin/api/volume/test_volume_dependency.py
+++ b/cinder_tempest_plugin/api/volume/test_volume_dependency.py
@@ -13,8 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+
+from tempest.common import utils
+from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
+import testtools
from cinder_tempest_plugin.api.volume import base
@@ -119,3 +123,133 @@
self._delete_vol_and_wait(volume_1)
self._delete_vol_and_wait(volume_2)
self._delete_vol_and_wait(volume_3)
+
+
+class VolumeImageDependencyTests(base.BaseVolumeTest):
+ """Volume<->image dependency tests.
+
+ These tests perform clones to/from volumes and images,
+ deleting images/volumes that other volumes were cloned from.
+
+ Images and volumes are expected to be independent at the OpenStack
+ level, but in some configurations (i.e. when using Ceph as storage
+ for both Cinder and Glance) it was possible to end up with images
+ or volumes that could not be deleted. This was fixed for RBD in
+ Cinder 2024.1 change I009d0748f.
+
+ """
+
+ min_microversion = '3.40'
+
+ @classmethod
+ def del_image(cls, image_id):
+ images_client = cls.os_primary.image_client_v2
+ images_client.delete_image(image_id)
+ images_client.wait_for_resource_deletion(image_id)
+
+ @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests,
+ reason='Volume/image dependency tests not enabled.')
+ @utils.services('image', 'volume')
+ @decorators.idempotent_id('7a9fba78-2e4b-42b1-9898-bb4a60685320')
+ def test_image_volume_dependencies_1(self):
+ # image -> volume
+ image_args = {
+ 'disk_format': 'raw',
+ 'container_format': 'bare',
+ 'name': 'image-for-test-7a9fba78-2e4b-42b1-9898-bb4a60685320'
+ }
+ image = self.create_image_with_data(**image_args)
+
+ # create a volume from the image
+ vol_args = {'name': ('volume1-for-test'
+ '7a9fba78-2e4b-42b1-9898-bb4a60685320'),
+ 'imageRef': image['id']}
+ volume1 = self.create_volume(**vol_args)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume1['id'],
+ 'available')
+
+ self.volumes_client.delete_volume(volume1['id'])
+ self.volumes_client.wait_for_resource_deletion(volume1['id'])
+
+ self.del_image(image['id'])
+
+ @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests,
+ reason='Volume/image dependency tests not enabled.')
+ @utils.services('image', 'volume')
+ @decorators.idempotent_id('0e20bd6e-440f-41d8-9b5d-fc047ac00423')
+ def test_image_volume_dependencies_2(self):
+ # image -> volume -> volume
+
+ image_args = {
+ 'disk_format': 'raw',
+ 'container_format': 'bare',
+ 'name': 'image-for-test-0e20bd6e-440f-41d8-9b5d-fc047ac00423'
+ }
+ image = self.create_image_with_data(**image_args)
+
+ # create a volume from the image
+ vol_args = {'name': ('volume1-for-test'
+ '0e20bd6e-440f-41d8-9b5d-fc047ac00423'),
+ 'imageRef': image['id']}
+ volume1 = self.create_volume(**vol_args)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume1['id'],
+ 'available')
+
+ vol2_args = {'name': ('volume2-for-test-'
+ '0e20bd6e-440f-41d8-9b5d-fc047ac00423'),
+ 'source_volid': volume1['id']}
+ volume2 = self.create_volume(**vol2_args)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume2['id'],
+ 'available')
+
+ self.volumes_client.delete_volume(volume1['id'])
+ self.volumes_client.wait_for_resource_deletion(volume1['id'])
+
+ self.del_image(image['id'])
+
+ @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests,
+ reason='Volume/image dependency tests not enabled.')
+ @decorators.idempotent_id('e6050452-06bd-4c7f-9912-45178c83e379')
+ @utils.services('image', 'volume')
+ def test_image_volume_dependencies_3(self):
+ # image -> volume -> snap -> volume
+
+ image_args = {
+ 'disk_format': 'raw',
+ 'container_format': 'bare',
+ 'name': 'image-for-test-e6050452-06bd-4c7f-9912-45178c83e379'
+ }
+ image = self.create_image_with_data(**image_args)
+
+ # create a volume from the image
+ vol_args = {'name': ('volume1-for-test'
+ 'e6050452-06bd-4c7f-9912-45178c83e379'),
+ 'imageRef': image['id']}
+ volume1 = self.create_volume(**vol_args)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume1['id'],
+ 'available')
+
+ snapshot1 = self.create_snapshot(volume1['id'])
+
+ vol2_args = {'name': ('volume2-for-test-'
+ 'e6050452-06bd-4c7f-9912-45178c83e379'),
+ 'snapshot_id': snapshot1['id']}
+ volume2 = self.create_volume(**vol2_args)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume2['id'],
+ 'available')
+
+ self.snapshots_client.delete_snapshot(snapshot1['id'])
+ self.snapshots_client.wait_for_resource_deletion(snapshot1['id'])
+
+ self.volumes_client.delete_volume(volume2['id'])
+ self.volumes_client.wait_for_resource_deletion(volume2['id'])
+
+ self.del_image(image['id'])
+
+ self.volumes_client.delete_volume(volume1['id'])
+ self.volumes_client.wait_for_resource_deletion(volume1['id'])
diff --git a/cinder_tempest_plugin/config.py b/cinder_tempest_plugin/config.py
index 78dd6ea..53222b8 100644
--- a/cinder_tempest_plugin/config.py
+++ b/cinder_tempest_plugin/config.py
@@ -22,6 +22,9 @@
cfg.BoolOpt('volume_revert',
default=False,
help='Enable to run Cinder volume revert tests'),
+ cfg.BoolOpt('volume_image_dep_tests',
+ default=True,
+ help='Run tests for dependencies between images and volumes')
]
# The barbican service is discovered by config_tempest [1], and will appear
diff --git a/cinder_tempest_plugin/rbac/v3/test_snapshots.py b/cinder_tempest_plugin/rbac/v3/test_snapshots.py
index f11da42..14377a9 100644
--- a/cinder_tempest_plugin/rbac/v3/test_snapshots.py
+++ b/cinder_tempest_plugin/rbac/v3/test_snapshots.py
@@ -17,6 +17,8 @@
from tempest.lib import decorators
from tempest.lib import exceptions
+import testtools
+
from cinder_tempest_plugin.rbac.v3 import base as rbac_base
CONF = config.CONF
@@ -309,10 +311,15 @@
def test_force_delete_snapshot(self):
self._force_delete_snapshot(expected_status=exceptions.Forbidden)
+ # We don't need to skip the unmanage because the failure should happen at
+ # the API level, so it should fail regardless of the driver support for
+ # unmanaging snapshots.
@decorators.idempotent_id('35495666-b663-4c68-ba44-0695e30a6838')
def test_unmanage_snapshot(self):
self._unmanage_snapshot(expected_status=exceptions.Forbidden)
+ @testtools.skipUnless(CONF.volume_feature_enabled.manage_snapshot,
+ 'manage snapshots are disabled')
@decorators.idempotent_id('d2d1326d-fb47-4448-a1e1-2d1219d30fd5')
def test_manage_snapshot(self):
self._manage_snapshot(
@@ -362,10 +369,15 @@
def test_force_delete_snapshot(self):
self._force_delete_snapshot(expected_status=exceptions.Forbidden)
+ # We don't need to skip the unmanage because the failure should happen at
+ # the API level, so it should fail regardless of the driver support for
+ # unmanaging snapshots.
@decorators.idempotent_id('dd7da3da-68ef-42f5-af1d-29803a4a04fd')
def test_unmanage_snapshot(self):
self._unmanage_snapshot(expected_status=exceptions.Forbidden)
+ @testtools.skipUnless(CONF.volume_feature_enabled.manage_snapshot,
+ 'manage snapshots are disabled')
@decorators.idempotent_id('c2501d05-9bca-42d7-9ab5-c0d9133e762f')
def test_manage_snapshot(self):
self._manage_snapshot(
diff --git a/cinder_tempest_plugin/scenario/manager.py b/cinder_tempest_plugin/scenario/manager.py
index 8598ade..cffa044 100644
--- a/cinder_tempest_plugin/scenario/manager.py
+++ b/cinder_tempest_plugin/scenario/manager.py
@@ -110,7 +110,6 @@
(mount_path, filename))
md5 = ssh_client.exec_command(
'sudo md5sum -b %s/%s|cut -c 1-32' % (mount_path, filename))
- ssh_client.exec_command('sudo sync')
return md5
def get_md5_from_file(self, instance, instance_ip, filename,
diff --git a/cinder_tempest_plugin/scenario/test_snapshots.py b/cinder_tempest_plugin/scenario/test_snapshots.py
index f376954..02cd6bd 100644
--- a/cinder_tempest_plugin/scenario/test_snapshots.py
+++ b/cinder_tempest_plugin/scenario/test_snapshots.py
@@ -14,10 +14,16 @@
# under the License.
from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
from tempest.lib import decorators
+import testtools
+
from cinder_tempest_plugin.scenario import manager
+CONF = config.CONF
+
class SnapshotDataIntegrityTests(manager.ScenarioTest):
@@ -121,3 +127,33 @@
self.assertEqual(count_snap, i)
self.assertEqual(file_map[i], md5_file)
+
+
+class SnapshotDependencyTests(manager.ScenarioTest):
+ @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests,
+ 'dependency tests not enabled')
+ @decorators.idempotent_id('e7028f52-f6d4-479c-8809-6f6cf96cfe0f')
+ @utils.services('image', 'volume')
+ def test_snapshot_removal(self):
+ volume_1 = self.create_volume()
+
+ snapshot_1 = self.create_volume_snapshot(volume_1['id'], force=True)
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snapshot_1['id'], 'available')
+
+ clone_kwargs = {'snapshot_id': snapshot_1['id'],
+ 'size': volume_1['size']}
+ volume_2 = self.volumes_client.create_volume(**clone_kwargs)['volume']
+
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, volume_2['id'], 'available')
+ volume_2 = self.volumes_client.show_volume(volume_2['id'])['volume']
+
+ self.snapshots_client.delete_snapshot(snapshot_1['id'])
+ self.snapshots_client.wait_for_resource_deletion(snapshot_1['id'])
+
+ self.volumes_client.delete_volume(volume_1['id'])
+ self.volumes_client.wait_for_resource_deletion(volume_1['id'])
+
+ self.volumes_client.delete_volume(volume_2['id'])
+ self.volumes_client.wait_for_resource_deletion(volume_2['id'])
diff --git a/setup.cfg b/setup.cfg
index f224c5c..3d74cb9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,6 +20,7 @@
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
+ Programming Language :: Python :: 3.11
[files]
packages =