Tests for volume<->image dependencies
Detect problems related to Cinder volumes not allowing
Glance images to be deleted.
This adds the option
CONF.volume_feature_enabled.volume_image_dep_tests.
This defaults to True because it is expected to pass on all
configurations other than pre-Caracal RBD backends.
Change-Id: I5fee23951958fc0031b59ce437a963c4cea28529
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