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