Merge tag '1.14.0' into debian/dalmatian
cinder-tempest-plugin 1.14.0 release
meta:version: 1.14.0
meta:diff-start: -
meta:series: dalmatian
meta:branch: master
meta:release-type: release
meta:pypi: no
meta:first: no
meta:release:Author: Brian Rosmaita <rosmaita.fossdev@gmail.com>
meta:release:Commit: Brian Rosmaita <rosmaita.fossdev@gmail.com>
meta:release:Change-Id: I81a91c3ca656f9cd4abf8360726bf65e2991f3f4
meta:release:Workflow+1: Elod Illes <elod.illes@est.tech>
meta:release:Code-Review+2: Jens Harbott <frickler@offenerstapel.de>
meta:release:Code-Review+2: Elod Illes <elod.illes@est.tech>
meta:release:Code-Review+1: Jon Bernard <jobernar@redhat.com>
Thomas Goirand
1 year, 8 months ago
| 20 | 20 | - cinder-tempest-plugin-cbak-ceph |
| 21 | 21 | - cinder-tempest-plugin-cbak-s3 |
| 22 | 22 | # As per the Tempest "Stable Branch Support Policy", Tempest will only |
| 23 | # support the "Maintained" stable branches and not the "Extended Maintained" | |
| 24 | # branches. That is what we need to do for all tempest plugins. Only jobs | |
| 25 | # for the current releasable ("Maintained") stable branches should be listed | |
| 26 | # here. | |
| 23 | # support the "Maintained" stable branches, so only jobs for the | |
| 24 | # current stable branches should be listed here. | |
| 25 | - cinder-tempest-plugin-basic-2024-1 | |
| 27 | 26 | - cinder-tempest-plugin-basic-2023-2 |
| 28 | 27 | - cinder-tempest-plugin-basic-2023-1 |
| 29 | - cinder-tempest-plugin-basic-zed | |
| 30 | 28 | - cinder-tempest-plugin-protection-functional |
| 31 | 29 | gate: |
| 32 | 30 | jobs: |
| 37 | 35 | - cinder-tempest-plugin-cbak-ceph |
| 38 | 36 | experimental: |
| 39 | 37 | jobs: |
| 38 | - cinder-tempest-plugin-cbak-ceph-2024-1 | |
| 40 | 39 | - cinder-tempest-plugin-cbak-ceph-2023-2 |
| 41 | 40 | - cinder-tempest-plugin-cbak-ceph-2023-1 |
| 42 | - cinder-tempest-plugin-cbak-ceph-zed | |
| 43 | 41 | |
| 44 | 42 | - job: |
| 45 | 43 | name: cinder-tempest-plugin-protection-functional |
| 268 | 266 | timeout: 10800 |
| 269 | 267 | |
| 270 | 268 | - job: |
| 269 | name: cinder-tempest-plugin-cbak-ceph-2024-1 | |
| 270 | parent: cinder-tempest-plugin-cbak-ceph | |
| 271 | nodeset: openstack-single-node-jammy | |
| 272 | override-checkout: stable/2024.1 | |
| 273 | ||
| 274 | - job: | |
| 271 | 275 | name: cinder-tempest-plugin-cbak-ceph-2023-2 |
| 272 | 276 | parent: cinder-tempest-plugin-cbak-ceph |
| 273 | 277 | nodeset: openstack-single-node-jammy |
| 278 | 282 | parent: cinder-tempest-plugin-cbak-ceph |
| 279 | 283 | nodeset: openstack-single-node-jammy |
| 280 | 284 | override-checkout: stable/2023.1 |
| 281 | ||
| 282 | - job: | |
| 283 | name: cinder-tempest-plugin-cbak-ceph-zed | |
| 284 | parent: cinder-tempest-plugin-cbak-ceph | |
| 285 | nodeset: openstack-single-node-focal | |
| 286 | override-checkout: stable/zed | |
| 287 | 285 | |
| 288 | 286 | # variant for pre-Ussuri branches (no volume revert for Ceph), |
| 289 | 287 | # should this job be used on those branches |
| 415 | 413 | - ^releasenotes/.*$ |
| 416 | 414 | |
| 417 | 415 | - job: |
| 416 | name: cinder-tempest-plugin-basic-2024-1 | |
| 417 | parent: cinder-tempest-plugin-basic | |
| 418 | nodeset: openstack-single-node-jammy | |
| 419 | override-checkout: stable/2024.1 | |
| 420 | ||
| 421 | - job: | |
| 418 | 422 | name: cinder-tempest-plugin-basic-2023-2 |
| 419 | 423 | parent: cinder-tempest-plugin-basic |
| 420 | 424 | nodeset: openstack-single-node-jammy |
| 425 | 429 | parent: cinder-tempest-plugin-basic |
| 426 | 430 | nodeset: openstack-single-node-jammy |
| 427 | 431 | override-checkout: stable/2023.1 |
| 428 | ||
| 429 | - job: | |
| 430 | name: cinder-tempest-plugin-basic-zed | |
| 431 | parent: cinder-tempest-plugin-basic | |
| 432 | nodeset: openstack-single-node-focal | |
| 433 | override-checkout: stable/zed | |
| 12 | 12 | # License for the specific language governing permissions and limitations |
| 13 | 13 | # under the License. |
| 14 | 14 | |
| 15 | import io | |
| 16 | ||
| 15 | 17 | from tempest.common import compute |
| 16 | 18 | from tempest.common import waiters |
| 17 | 19 | from tempest import config |
| 156 | 158 | self.os_primary.servers_client.delete_server, |
| 157 | 159 | body['id']) |
| 158 | 160 | return body |
| 161 | ||
| 162 | @classmethod | |
| 163 | def create_image_with_data(cls, **kwargs): | |
| 164 | # we do this as a class method so we can use the | |
| 165 | # addClassResourceCleanup functionality of tempest.test.BaseTestCase | |
| 166 | images_client = cls.os_primary.image_client_v2 | |
| 167 | if 'min_disk' not in kwargs: | |
| 168 | kwargs['min_disk'] = 1 | |
| 169 | response = images_client.create_image(**kwargs) | |
| 170 | image_id = response['id'] | |
| 171 | cls.addClassResourceCleanup( | |
| 172 | images_client.wait_for_resource_deletion, image_id) | |
| 173 | cls.addClassResourceCleanup( | |
| 174 | test_utils.call_and_ignore_notfound_exc, | |
| 175 | images_client.delete_image, image_id) | |
| 176 | ||
| 177 | # upload "data" to image | |
| 178 | image_file = io.BytesIO(data_utils.random_bytes(size=1024)) | |
| 179 | images_client.store_image_file(image_id, image_file) | |
| 180 | ||
| 181 | waiters.wait_for_image_status(images_client, image_id, 'active') | |
| 182 | image = images_client.show_image(image_id) | |
| 183 | return image | |
| 159 | 184 | |
| 160 | 185 | |
| 161 | 186 | class BaseVolumeAdminTest(BaseVolumeTest): |
| 9 | 9 | # License for the specific language governing permissions and limitations |
| 10 | 10 | # under the License. |
| 11 | 11 | |
| 12 | import io | |
| 13 | ||
| 14 | from tempest.common import waiters | |
| 15 | 12 | from tempest import config |
| 16 | from tempest.lib.common.utils import data_utils | |
| 17 | from tempest.lib.common.utils import test_utils | |
| 18 | 13 | from tempest.lib import decorators |
| 19 | 14 | |
| 20 | 15 | from cinder_tempest_plugin.api.volume import base |
| 30 | 25 | super(VolumeAndVolumeTypeFromImageTest, cls).skip_checks() |
| 31 | 26 | if not CONF.service_available.glance: |
| 32 | 27 | raise cls.skipException("Glance service is disabled") |
| 33 | ||
| 34 | @classmethod | |
| 35 | def create_image_with_data(cls, **kwargs): | |
| 36 | # we do this as a class method so we can use the | |
| 37 | # addClassResourceCleanup functionality of tempest.test.BaseTestCase | |
| 38 | images_client = cls.os_primary.image_client_v2 | |
| 39 | if 'min_disk' not in kwargs: | |
| 40 | kwargs['min_disk'] = 1 | |
| 41 | response = images_client.create_image(**kwargs) | |
| 42 | image_id = response['id'] | |
| 43 | cls.addClassResourceCleanup( | |
| 44 | images_client.wait_for_resource_deletion, image_id) | |
| 45 | cls.addClassResourceCleanup( | |
| 46 | test_utils.call_and_ignore_notfound_exc, | |
| 47 | images_client.delete_image, image_id) | |
| 48 | ||
| 49 | # upload "data" to image | |
| 50 | image_file = io.BytesIO(data_utils.random_bytes(size=1024)) | |
| 51 | images_client.store_image_file(image_id, image_file) | |
| 52 | ||
| 53 | waiters.wait_for_image_status(images_client, image_id, 'active') | |
| 54 | image = images_client.show_image(image_id) | |
| 55 | return image | |
| 56 | 28 | |
| 57 | 29 | @decorators.idempotent_id('6e9266ff-a917-4dd5-aa4a-c36e59e7a2a6') |
| 58 | 30 | def test_create_from_image_with_volume_type_image_property(self): |
| 12 | 12 | # License for the specific language governing permissions and limitations |
| 13 | 13 | # under the License. |
| 14 | 14 | |
| 15 | ||
| 16 | from tempest.common import utils | |
| 17 | from tempest.common import waiters | |
| 15 | 18 | from tempest import config |
| 16 | 19 | from tempest.lib import decorators |
| 20 | import testtools | |
| 17 | 21 | |
| 18 | 22 | from cinder_tempest_plugin.api.volume import base |
| 19 | 23 | |
| 118 | 122 | self._delete_vol_and_wait(volume_1) |
| 119 | 123 | self._delete_vol_and_wait(volume_2) |
| 120 | 124 | self._delete_vol_and_wait(volume_3) |
| 125 | ||
| 126 | ||
| 127 | class VolumeImageDependencyTests(base.BaseVolumeTest): | |
| 128 | """Volume<->image dependency tests. | |
| 129 | ||
| 130 | These tests perform clones to/from volumes and images, | |
| 131 | deleting images/volumes that other volumes were cloned from. | |
| 132 | ||
| 133 | Images and volumes are expected to be independent at the OpenStack | |
| 134 | level, but in some configurations (i.e. when using Ceph as storage | |
| 135 | for both Cinder and Glance) it was possible to end up with images | |
| 136 | or volumes that could not be deleted. This was fixed for RBD in | |
| 137 | Cinder 2024.1 change I009d0748f. | |
| 138 | ||
| 139 | """ | |
| 140 | ||
| 141 | min_microversion = '3.40' | |
| 142 | ||
| 143 | @classmethod | |
| 144 | def del_image(cls, image_id): | |
| 145 | images_client = cls.os_primary.image_client_v2 | |
| 146 | images_client.delete_image(image_id) | |
| 147 | images_client.wait_for_resource_deletion(image_id) | |
| 148 | ||
| 149 | @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests, | |
| 150 | reason='Volume/image dependency tests not enabled.') | |
| 151 | @utils.services('image', 'volume') | |
| 152 | @decorators.idempotent_id('7a9fba78-2e4b-42b1-9898-bb4a60685320') | |
| 153 | def test_image_volume_dependencies_1(self): | |
| 154 | # image -> volume | |
| 155 | image_args = { | |
| 156 | 'disk_format': 'raw', | |
| 157 | 'container_format': 'bare', | |
| 158 | 'name': 'image-for-test-7a9fba78-2e4b-42b1-9898-bb4a60685320' | |
| 159 | } | |
| 160 | image = self.create_image_with_data(**image_args) | |
| 161 | ||
| 162 | # create a volume from the image | |
| 163 | vol_args = {'name': ('volume1-for-test' | |
| 164 | '7a9fba78-2e4b-42b1-9898-bb4a60685320'), | |
| 165 | 'imageRef': image['id']} | |
| 166 | volume1 = self.create_volume(**vol_args) | |
| 167 | waiters.wait_for_volume_resource_status(self.volumes_client, | |
| 168 | volume1['id'], | |
| 169 | 'available') | |
| 170 | ||
| 171 | self.volumes_client.delete_volume(volume1['id']) | |
| 172 | self.volumes_client.wait_for_resource_deletion(volume1['id']) | |
| 173 | ||
| 174 | self.del_image(image['id']) | |
| 175 | ||
| 176 | @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests, | |
| 177 | reason='Volume/image dependency tests not enabled.') | |
| 178 | @utils.services('image', 'volume') | |
| 179 | @decorators.idempotent_id('0e20bd6e-440f-41d8-9b5d-fc047ac00423') | |
| 180 | def test_image_volume_dependencies_2(self): | |
| 181 | # image -> volume -> volume | |
| 182 | ||
| 183 | image_args = { | |
| 184 | 'disk_format': 'raw', | |
| 185 | 'container_format': 'bare', | |
| 186 | 'name': 'image-for-test-0e20bd6e-440f-41d8-9b5d-fc047ac00423' | |
| 187 | } | |
| 188 | image = self.create_image_with_data(**image_args) | |
| 189 | ||
| 190 | # create a volume from the image | |
| 191 | vol_args = {'name': ('volume1-for-test' | |
| 192 | '0e20bd6e-440f-41d8-9b5d-fc047ac00423'), | |
| 193 | 'imageRef': image['id']} | |
| 194 | volume1 = self.create_volume(**vol_args) | |
| 195 | waiters.wait_for_volume_resource_status(self.volumes_client, | |
| 196 | volume1['id'], | |
| 197 | 'available') | |
| 198 | ||
| 199 | vol2_args = {'name': ('volume2-for-test-' | |
| 200 | '0e20bd6e-440f-41d8-9b5d-fc047ac00423'), | |
| 201 | 'source_volid': volume1['id']} | |
| 202 | volume2 = self.create_volume(**vol2_args) | |
| 203 | waiters.wait_for_volume_resource_status(self.volumes_client, | |
| 204 | volume2['id'], | |
| 205 | 'available') | |
| 206 | ||
| 207 | self.volumes_client.delete_volume(volume1['id']) | |
| 208 | self.volumes_client.wait_for_resource_deletion(volume1['id']) | |
| 209 | ||
| 210 | self.del_image(image['id']) | |
| 211 | ||
| 212 | @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests, | |
| 213 | reason='Volume/image dependency tests not enabled.') | |
| 214 | @decorators.idempotent_id('e6050452-06bd-4c7f-9912-45178c83e379') | |
| 215 | @utils.services('image', 'volume') | |
| 216 | def test_image_volume_dependencies_3(self): | |
| 217 | # image -> volume -> snap -> volume | |
| 218 | ||
| 219 | image_args = { | |
| 220 | 'disk_format': 'raw', | |
| 221 | 'container_format': 'bare', | |
| 222 | 'name': 'image-for-test-e6050452-06bd-4c7f-9912-45178c83e379' | |
| 223 | } | |
| 224 | image = self.create_image_with_data(**image_args) | |
| 225 | ||
| 226 | # create a volume from the image | |
| 227 | vol_args = {'name': ('volume1-for-test' | |
| 228 | 'e6050452-06bd-4c7f-9912-45178c83e379'), | |
| 229 | 'imageRef': image['id']} | |
| 230 | volume1 = self.create_volume(**vol_args) | |
| 231 | waiters.wait_for_volume_resource_status(self.volumes_client, | |
| 232 | volume1['id'], | |
| 233 | 'available') | |
| 234 | ||
| 235 | snapshot1 = self.create_snapshot(volume1['id']) | |
| 236 | ||
| 237 | vol2_args = {'name': ('volume2-for-test-' | |
| 238 | 'e6050452-06bd-4c7f-9912-45178c83e379'), | |
| 239 | 'snapshot_id': snapshot1['id']} | |
| 240 | volume2 = self.create_volume(**vol2_args) | |
| 241 | waiters.wait_for_volume_resource_status(self.volumes_client, | |
| 242 | volume2['id'], | |
| 243 | 'available') | |
| 244 | ||
| 245 | self.snapshots_client.delete_snapshot(snapshot1['id']) | |
| 246 | self.snapshots_client.wait_for_resource_deletion(snapshot1['id']) | |
| 247 | ||
| 248 | self.volumes_client.delete_volume(volume2['id']) | |
| 249 | self.volumes_client.wait_for_resource_deletion(volume2['id']) | |
| 250 | ||
| 251 | self.del_image(image['id']) | |
| 252 | ||
| 253 | self.volumes_client.delete_volume(volume1['id']) | |
| 254 | self.volumes_client.wait_for_resource_deletion(volume1['id']) | |
| 21 | 21 | cfg.BoolOpt('volume_revert', |
| 22 | 22 | default=False, |
| 23 | 23 | help='Enable to run Cinder volume revert tests'), |
| 24 | cfg.BoolOpt('volume_image_dep_tests', | |
| 25 | default=True, | |
| 26 | help='Run tests for dependencies between images and volumes') | |
| 24 | 27 | ] |
| 25 | 28 | |
| 26 | 29 | # The barbican service is discovered by config_tempest [1], and will appear |
| 109 | 109 | (mount_path, filename)) |
| 110 | 110 | md5 = ssh_client.exec_command( |
| 111 | 111 | 'sudo md5sum -b %s/%s|cut -c 1-32' % (mount_path, filename)) |
| 112 | ssh_client.exec_command('sudo sync') | |
| 113 | 112 | return md5 |
| 114 | 113 | |
| 115 | 114 | def get_md5_from_file(self, instance, instance_ip, filename, |
| 13 | 13 | # under the License. |
| 14 | 14 | |
| 15 | 15 | from tempest.common import utils |
| 16 | from tempest.common import waiters | |
| 17 | from tempest import config | |
| 16 | 18 | from tempest.lib import decorators |
| 17 | 19 | |
| 20 | import testtools | |
| 21 | ||
| 18 | 22 | from cinder_tempest_plugin.scenario import manager |
| 23 | ||
| 24 | CONF = config.CONF | |
| 19 | 25 | |
| 20 | 26 | |
| 21 | 27 | class SnapshotDataIntegrityTests(manager.ScenarioTest): |
| 120 | 126 | |
| 121 | 127 | self.assertEqual(count_snap, i) |
| 122 | 128 | self.assertEqual(file_map[i], md5_file) |
| 129 | ||
| 130 | ||
| 131 | class SnapshotDependencyTests(manager.ScenarioTest): | |
| 132 | @testtools.skipUnless(CONF.volume_feature_enabled.volume_image_dep_tests, | |
| 133 | 'dependency tests not enabled') | |
| 134 | @decorators.idempotent_id('e7028f52-f6d4-479c-8809-6f6cf96cfe0f') | |
| 135 | @utils.services('image', 'volume') | |
| 136 | def test_snapshot_removal(self): | |
| 137 | volume_1 = self.create_volume() | |
| 138 | ||
| 139 | snapshot_1 = self.create_volume_snapshot(volume_1['id'], force=True) | |
| 140 | waiters.wait_for_volume_resource_status( | |
| 141 | self.snapshots_client, snapshot_1['id'], 'available') | |
| 142 | ||
| 143 | clone_kwargs = {'snapshot_id': snapshot_1['id'], | |
| 144 | 'size': volume_1['size']} | |
| 145 | volume_2 = self.volumes_client.create_volume(**clone_kwargs)['volume'] | |
| 146 | ||
| 147 | waiters.wait_for_volume_resource_status( | |
| 148 | self.volumes_client, volume_2['id'], 'available') | |
| 149 | volume_2 = self.volumes_client.show_volume(volume_2['id'])['volume'] | |
| 150 | ||
| 151 | self.snapshots_client.delete_snapshot(snapshot_1['id']) | |
| 152 | self.snapshots_client.wait_for_resource_deletion(snapshot_1['id']) | |
| 153 | ||
| 154 | self.volumes_client.delete_volume(volume_1['id']) | |
| 155 | self.volumes_client.wait_for_resource_deletion(volume_1['id']) | |
| 156 | ||
| 157 | self.volumes_client.delete_volume(volume_2['id']) | |
| 158 | self.volumes_client.wait_for_resource_deletion(volume_2['id']) | |