# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # 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. """Tests for volume and images.""" import datetime import os import tempfile from unittest import mock from oslo_utils import imageutils from oslo_utils import units from cinder import db from cinder import exception from cinder.message import message_field from cinder import objects from cinder.objects import fields from cinder import quota from cinder.tests import fake_driver from cinder.tests.unit.api.v2 import fakes as v2_fakes from cinder.tests.unit import fake_constants as fake from cinder.tests.unit.image import fake as fake_image from cinder.tests.unit import utils as tests_utils from cinder.tests.unit import volume as base import cinder.volume from cinder.volume import manager as vol_manager QUOTAS = quota.QUOTAS NON_EXISTENT_IMAGE_ID = '003f540f-ec6b-4293-a3f9-7c68646b0f5c' class FakeImageService(object): def __init__(self, db_driver=None, image_service=None): pass def show(self, context, image_id): return {'size': 2 * units.Gi, 'disk_format': 'raw', 'container_format': 'bare', 'status': 'active'} class CopyVolumeToImageTestCase(base.BaseVolumeTestCase): def fake_local_path(self, volume): return self.dst_path def setUp(self): super(CopyVolumeToImageTestCase, self).setUp() self.dst_fd, self.dst_path = tempfile.mkstemp() self.addCleanup(os.unlink, self.dst_path) os.close(self.dst_fd) self.mock_object(self.volume.driver, 'local_path', self.fake_local_path) self.mock_cache = mock.MagicMock() self.image_id = '70a599e0-31e7-49b7-b260-868f441e862b' self.image_meta = { 'id': self.image_id, 'container_format': 'bare', 'disk_format': 'raw' } self.volume_id = fake.VOLUME_ID self.addCleanup(db.volume_destroy, self.context, self.volume_id) self.volume_attrs = { 'id': self.volume_id, 'updated_at': datetime.datetime(1, 1, 1, 1, 1, 1), 'display_description': 'Test Desc', 'size': 20, 'status': 'uploading', 'host': 'dummy', 'volume_type_id': fake.VOLUME_TYPE_ID } self.mock_object(db.sqlalchemy.api, 'volume_type_get', v2_fakes.fake_volume_type_get) def test_copy_volume_to_image_status_available(self): # creating volume testdata self.volume_attrs['instance_uuid'] = None volume_type_id = db.volume_type_create( self.context, {'name': 'test', 'extra_specs': { 'image_service:store_id': 'fake_store' }}).get('id') self.volume_attrs['volume_type_id'] = volume_type_id db.volume_create(self.context, self.volume_attrs) # start test self.volume.copy_volume_to_image(self.context, self.volume_id, self.image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) def test_copy_volume_to_image_over_image_quota(self): # creating volume testdata self.volume_attrs['instance_uuid'] = None volume = db.volume_create(self.context, self.volume_attrs) with mock.patch.object(self.volume.driver, 'copy_volume_to_image') as driver_copy_mock: driver_copy_mock.side_effect = exception.ImageLimitExceeded # test with image not in queued state self.assertRaises(exception.ImageLimitExceeded, self.volume.copy_volume_to_image, self.context, self.volume_id, self.image_meta) # Assert a user message was created self.volume.message_api.create.assert_called_once_with( self.context, message_field.Action.COPY_VOLUME_TO_IMAGE, resource_uuid=volume['id'], exception=mock.ANY, detail=message_field.Detail.FAILED_TO_UPLOAD_VOLUME) def test_copy_volume_to_image_instance_deleted(self): # During uploading volume to image if instance is deleted, # volume should be in available status. self.image_meta['id'] = 'a440c04b-79fa-479c-bed1-0b816eaec379' # Creating volume testdata self.volume_attrs['instance_uuid'] = 'b21f957d-a72f-4b93-b5a5-' \ '45b1161abb02' volume_type_id = db.volume_type_create( self.context, {'name': 'test', 'extra_specs': { 'image_service:store_id': 'fake_store' }}).get('id') self.volume_attrs['volume_type_id'] = volume_type_id db.volume_create(self.context, self.volume_attrs) method = 'volume_update_status_based_on_attachment' with mock.patch.object(db, method, wraps=getattr(db, method)) as mock_update: # Start test self.volume.copy_volume_to_image(self.context, self.volume_id, self.image_meta) # Check 'volume_update_status_after_copy_volume_to_image' # is called 1 time self.assertEqual(1, mock_update.call_count) # Check volume status has changed to available because # instance is deleted volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) def test_copy_volume_to_image_status_use(self): self.image_meta['id'] = 'a440c04b-79fa-479c-bed1-0b816eaec379' # creating volume testdata volume_type_id = db.volume_type_create( self.context, {'name': 'test', 'extra_specs': { 'image_service:store_id': 'fake_store' }}).get('id') self.volume_attrs['volume_type_id'] = volume_type_id db.volume_create(self.context, self.volume_attrs) # start test self.volume.copy_volume_to_image(self.context, self.volume_id, self.image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) def test_copy_volume_to_image_exception(self): self.image_meta['id'] = NON_EXISTENT_IMAGE_ID # creating volume testdata volume_type_id = db.volume_type_create( self.context, {'name': 'test', 'extra_specs': { 'image_service:store_id': 'fake_store' }}).get('id') self.volume_attrs['volume_type_id'] = volume_type_id self.volume_attrs['status'] = 'in-use' db.volume_create(self.context, self.volume_attrs) # start test self.assertRaises(exception.ImageNotFound, self.volume.copy_volume_to_image, self.context, self.volume_id, self.image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) def test_copy_volume_to_image_driver_not_initialized(self): # creating volume testdata db.volume_create(self.context, self.volume_attrs) # set initialized to False self.volume.driver._initialized = False # start test self.assertRaises(exception.DriverNotInitialized, self.volume.copy_volume_to_image, self.context, self.volume_id, self.image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume.status) def test_copy_volume_to_image_driver_exception(self): self.image_meta['id'] = self.image_id image_service = fake_image.FakeImageService() # create new image in queued state queued_image_id = 'd5133f15-f753-41bd-920a-06b8c49275d9' queued_image_meta = image_service.show(self.context, self.image_id) queued_image_meta['id'] = queued_image_id queued_image_meta['status'] = 'queued' image_service.create(self.context, queued_image_meta) # create new image in saving state saving_image_id = '5c6eec33-bab4-4e7d-b2c9-88e2d0a5f6f2' saving_image_meta = image_service.show(self.context, self.image_id) saving_image_meta['id'] = saving_image_id saving_image_meta['status'] = 'saving' image_service.create(self.context, saving_image_meta) # create volume self.volume_attrs['status'] = 'available' self.volume_attrs['instance_uuid'] = None db.volume_create(self.context, self.volume_attrs) with mock.patch.object(self.volume.driver, 'copy_volume_to_image') as driver_copy_mock: driver_copy_mock.side_effect = exception.VolumeDriverException( "Error") # test with image not in queued state self.assertRaises(exception.VolumeDriverException, self.volume.copy_volume_to_image, self.context, self.volume_id, self.image_meta) # Make sure we are passing an OVO instance and not an ORM instance # to the driver self.assertIsInstance(driver_copy_mock.call_args[0][1], objects.Volume) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) # image shouldn't be deleted if it is not in queued state image_service.show(self.context, self.image_id) # test with image in queued state self.assertRaises(exception.VolumeDriverException, self.volume.copy_volume_to_image, self.context, self.volume_id, queued_image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) # queued image should be deleted self.assertRaises(exception.ImageNotFound, image_service.show, self.context, queued_image_id) # test with image in saving state self.assertRaises(exception.VolumeDriverException, self.volume.copy_volume_to_image, self.context, self.volume_id, saving_image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) # image in saving state should be deleted self.assertRaises(exception.ImageNotFound, image_service.show, self.context, saving_image_id) @mock.patch.object(QUOTAS, 'reserve') @mock.patch.object(QUOTAS, 'commit') @mock.patch.object(vol_manager.VolumeManager, 'create_volume') @mock.patch.object(fake_driver.FakeLoggingVolumeDriver, 'copy_volume_to_image') def _test_copy_volume_to_image_with_image_volume( self, mock_copy, mock_create, mock_quota_commit, mock_quota_reserve): self.volume.driver.configuration.image_upload_use_cinder_backend = True self.addCleanup(fake_image.FakeImageService_reset) image_service = fake_image.FakeImageService() def add_location_wrapper(ctx, id, uri, metadata): try: volume = db.volume_get(ctx, id) self.assertEqual(ctx.project_id, volume['metadata']['image_owner']) except exception.VolumeNotFound: pass return image_service.add_location_orig(ctx, id, uri, metadata) image_service.add_location_orig = image_service.add_location image_service.add_location = add_location_wrapper image_id = '5c6eec33-bab4-4e7d-b2c9-88e2d0a5f6f2' self.image_meta['id'] = image_id self.image_meta['status'] = 'queued' image_service.create(self.context, self.image_meta) # creating volume testdata self.volume_attrs['instance_uuid'] = None self.volume_attrs['snapshot_id'] = fake.SNAPSHOT_ID volume_type_id = db.volume_type_create( self.context, {'name': 'test', 'extra_specs': { 'image_service:store_id': 'fake_store' }}).get('id') self.volume_attrs['volume_type_id'] = volume_type_id db.volume_create(self.context, self.volume_attrs) def fake_create(context, volume, **kwargs): db.volume_update(context, volume.id, {'status': 'available'}) mock_create.side_effect = fake_create # start test self.volume.copy_volume_to_image(self.context, self.volume_id, self.image_meta) volume = db.volume_get(self.context, self.volume_id) self.assertEqual('available', volume['status']) # return create image image = image_service.show(self.context, image_id) image_service.delete(self.context, image_id) return image def test_copy_volume_to_image_with_image_volume(self): image = self._test_copy_volume_to_image_with_image_volume() self.assertTrue(image['locations'][0]['url'].startswith('cinder://')) image_volume_id = image['locations'][0]['url'][9:] # The image volume does NOT include the snapshot_id, and include the # source_volid which is the uploaded-volume id. vol_ref = db.volume_get(self.context, image_volume_id) self.assertIsNone(vol_ref['snapshot_id']) self.assertEqual(vol_ref['source_volid'], self.volume_id) def test_copy_volume_to_image_with_image_volume_qcow2(self): self.image_meta['disk_format'] = 'qcow2' image = self._test_copy_volume_to_image_with_image_volume() self.assertNotIn('locations', image) @mock.patch.object(vol_manager.VolumeManager, 'delete_volume') @mock.patch.object(fake_image._FakeImageService, 'add_location', side_effect=exception.Invalid) def test_copy_volume_to_image_with_image_volume_failure( self, mock_add_location, mock_delete): image = self._test_copy_volume_to_image_with_image_volume() self.assertNotIn('locations', image) self.assertTrue(mock_delete.called) @mock.patch('cinder.volume.manager.' 'VolumeManager._clone_image_volume') @mock.patch('cinder.volume.manager.' 'VolumeManager._create_image_cache_volume_entry') def test_create_image_cache_volume_entry(self, mock_cache_entry, mock_clone_image_volume): image_id = self.image_id image_meta = self.image_meta self.mock_cache.get_entry.return_value = mock_cache_entry if mock_cache_entry: # Entry is in cache, so basically don't do anything. # Make sure we didn't try and create a cache entry self.assertFalse(self.mock_cache.ensure_space.called) self.assertFalse(self.mock_cache.create_cache_entry.called) else: result = self.volume._create_image_cache_volume_entry( self.context, mock_clone_image_volume, image_id, image_meta) self.assertNotEqual(False, result) cache_entry = self.image_volume_cache.get_entry( self.context, mock_clone_image_volume, image_id, image_meta) self.assertIsNotNone(cache_entry) class ImageVolumeCacheTestCase(base.BaseVolumeTestCase): def setUp(self): super(ImageVolumeCacheTestCase, self).setUp() self.volume.driver.set_initialized() @mock.patch('oslo_utils.importutils.import_object') def test_cache_configs(self, mock_import_object): opts = { 'image_volume_cache_enabled': True, 'image_volume_cache_max_size_gb': 100, 'image_volume_cache_max_count': 20 } def conf_get(option): if option in opts: return opts[option] else: return None mock_driver = mock.Mock() mock_driver.configuration.safe_get.side_effect = conf_get mock_driver.configuration.extra_capabilities = 'null' def import_obj(*args, **kwargs): return mock_driver mock_import_object.side_effect = import_obj manager = vol_manager.VolumeManager(volume_driver=mock_driver) self.assertIsNotNone(manager) self.assertIsNotNone(manager.image_volume_cache) self.assertEqual(100, manager.image_volume_cache.max_cache_size_gb) self.assertEqual(20, manager.image_volume_cache.max_cache_size_count) def test_delete_image_volume(self): volume_params = { 'status': 'creating', 'host': 'some_host', 'cluster_name': 'some_cluster', 'size': 1 } volume_api = cinder.volume.api.API() volume = tests_utils.create_volume(self.context, **volume_params) volume.status = 'available' volume.save() image_id = '70a599e0-31e7-49b7-b260-868f441e862b' db.image_volume_cache_create(self.context, volume['host'], volume_params['cluster_name'], image_id, datetime.datetime.utcnow(), volume['id'], volume['size']) volume_api.delete(self.context, volume) entry = db.image_volume_cache_get_by_volume_id(self.context, volume['id']) self.assertIsNone(entry) def test_delete_volume_with_keymanager_exception(self): volume_params = { 'host': 'some_host', 'size': 1 } volume_api = cinder.volume.api.API() volume = tests_utils.create_volume(self.context, **volume_params) with mock.patch.object( volume_api.key_manager, 'delete') as key_del_mock: key_del_mock.side_effect = Exception("Key not found") volume_api.delete(self.context, volume) class ImageVolumeTestCases(base.BaseVolumeTestCase): def setUp(self): super(ImageVolumeTestCases, self).setUp() db.volume_type_create(self.context, v2_fakes.fake_default_type_get( fake.VOLUME_TYPE2_ID)) self.vol_type = db.volume_type_get_by_name(self.context, 'vol_type_name') @mock.patch('cinder.volume.drivers.lvm.LVMVolumeDriver.' 'create_cloned_volume') @mock.patch('cinder.quota.QUOTAS.rollback') @mock.patch('cinder.quota.QUOTAS.commit') @mock.patch('cinder.quota.QUOTAS.reserve', return_value=["RESERVATION"]) def test_clone_image_volume(self, mock_reserve, mock_commit, mock_rollback, mock_cloned_volume): vol = tests_utils.create_volume(self.context, **self.volume_params) # unnecessary attributes should be removed from image volume vol.consistencygroup = None result = self.volume._clone_image_volume(self.context, vol, {'id': fake.VOLUME_ID}) self.assertNotEqual(False, result) mock_reserve.assert_called_once_with(self.context, volumes=1, volumes_vol_type_name=1, gigabytes=vol.size, gigabytes_vol_type_name=vol.size) mock_commit.assert_called_once_with(self.context, ["RESERVATION"], project_id=vol.project_id) @mock.patch('cinder.quota.QUOTAS.rollback') @mock.patch('cinder.quota.QUOTAS.commit') @mock.patch('cinder.quota.QUOTAS.reserve', return_value=["RESERVATION"]) def test_clone_image_volume_creation_failure(self, mock_reserve, mock_commit, mock_rollback): vol = tests_utils.create_volume(self.context, **self.volume_params) with mock.patch.object(objects, 'Volume', side_effect=ValueError): self.assertIsNone(self.volume._clone_image_volume( self.context, vol, {'id': fake.VOLUME_ID})) mock_reserve.assert_called_once_with(self.context, volumes=1, volumes_vol_type_name=1, gigabytes=vol.size, gigabytes_vol_type_name=vol.size) mock_rollback.assert_called_once_with(self.context, ["RESERVATION"]) @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_cloned_status_available( self, mock_qemu_info): """Test create volume from image via cloning. Verify that after cloning image to volume, it is in available state and is bootable. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info volume = self._create_volume_from_image() self.assertEqual('available', volume['status']) self.assertTrue(volume['bootable']) self.volume.delete_volume(self.context, volume) @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_not_cloned_status_available( self, mock_qemu_info): """Test create volume from image via full copy. Verify that after copying image to volume, it is in available state and is bootable. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info volume = self._create_volume_from_image(fakeout_clone_image=True) self.assertEqual('available', volume['status']) self.assertTrue(volume['bootable']) self.volume.delete_volume(self.context, volume) def test_create_volume_from_image_exception(self): """Test create volume from a non-existing image. Verify that create volume from a non-existing image, the volume status is 'error' and is not bootable. """ dst_fd, dst_path = tempfile.mkstemp() os.close(dst_fd) self.mock_object(self.volume.driver, 'local_path', lambda x: dst_path) # creating volume testdata kwargs = {'display_description': 'Test Desc', 'size': 20, 'availability_zone': 'fake_availability_zone', 'status': 'creating', 'attach_status': fields.VolumeAttachStatus.DETACHED, 'host': 'dummy'} volume = objects.Volume(context=self.context, **kwargs) volume.create() self.assertRaises(exception.ImageNotFound, self.volume.create_volume, self.context, volume, {'image_id': NON_EXISTENT_IMAGE_ID}) volume = objects.Volume.get_by_id(self.context, volume.id) self.assertEqual("error", volume['status']) self.assertFalse(volume['bootable']) # cleanup volume.destroy() os.unlink(dst_path) @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_copy_exception_rescheduling( self, mock_qemu_info): """Test create volume with ImageCopyFailure This exception should not trigger rescheduling and allocated_capacity should be incremented so we're having assert for that here. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info def fake_copy_image_to_volume(context, volume, image_service, image_id): raise exception.ImageCopyFailure() self.mock_object(self.volume.driver, 'copy_image_to_volume', fake_copy_image_to_volume) mock_delete = self.mock_object(self.volume.driver, 'delete_volume') self.assertRaises(exception.ImageCopyFailure, self._create_volume_from_image) # NOTE(dulek): Rescheduling should not occur, so lets assert that # allocated_capacity is incremented. self.assertDictEqual(self.volume.stats['pools'], {'_pool0': {'allocated_capacity_gb': 1}}) # NOTE(dulek): As we haven't rescheduled, make sure no delete_volume # was called. self.assertFalse(mock_delete.called) @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_with_img_too_big( self, mock_qemu_info): """Test create volume with ImageCopyFailure This exception should not trigger rescheduling and allocated_capacity should be incremented so we're having assert for that here. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info def fake_copy_image_to_volume(context, volume, image_service, image_id): raise exception.ImageTooBig(image_id=image_id, reason='') self.mock_object(self.volume.driver, 'copy_image_to_volume', fake_copy_image_to_volume) self.assertRaises(exception.ImageTooBig, self._create_volume_from_image) @mock.patch('cinder.volume.volume_utils.brick_get_connector_properties') @mock.patch('cinder.volume.volume_utils.brick_get_connector') @mock.patch('cinder.volume.driver.BaseVD.secure_file_operations_enabled') @mock.patch('cinder.volume.driver.BaseVD._detach_volume') @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_unavailable( self, mock_qemu_info, mock_detach, mock_secure, *args): """Test create volume with ImageCopyFailure We'll raise an exception inside _connect_device after volume has already been attached to confirm that it detaches the volume. """ mock_secure.side_effect = NameError image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info unbound_copy_method = cinder.volume.driver.BaseVD.copy_image_to_volume bound_copy_method = unbound_copy_method.__get__(self.volume.driver) with mock.patch.object(self.volume.driver, 'copy_image_to_volume', side_effect=bound_copy_method): self.assertRaises(exception.ImageCopyFailure, self._create_volume_from_image, fakeout_copy_image_to_volume=False) # We must have called detach method. self.assertEqual(1, mock_detach.call_count) @mock.patch('cinder.volume.volume_utils.brick_get_connector_properties') @mock.patch('cinder.volume.volume_utils.brick_get_connector') @mock.patch('cinder.volume.driver.BaseVD._connect_device') @mock.patch('cinder.volume.driver.BaseVD._detach_volume') @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_unavailable_no_attach_info( self, mock_qemu_info, mock_detach, mock_connect, *args): """Test create volume with ImageCopyFailure We'll raise an exception on _connect_device call to confirm that it detaches the volume even if the exception doesn't have attach_info. """ mock_connect.side_effect = NameError image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info unbound_copy_method = cinder.volume.driver.BaseVD.copy_image_to_volume bound_copy_method = unbound_copy_method.__get__(self.volume.driver) with mock.patch.object(self.volume.driver, 'copy_image_to_volume', side_effect=bound_copy_method): self.assertRaises(exception.ImageCopyFailure, self._create_volume_from_image, fakeout_copy_image_to_volume=False) # We must have called detach method. self.assertEqual(1, mock_detach.call_count) @mock.patch('cinder.image.image_utils.qemu_img_info') def test_create_volume_from_image_clone_image_volume(self, mock_qemu_info): """Test create volume from image via image volume. Verify that after cloning image to volume, it is in available state and is bootable. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info volume = self._create_volume_from_image(clone_image_volume=True) self.assertEqual('available', volume['status']) self.assertTrue(volume['bootable']) self.volume.delete_volume(self.context, volume) def test_create_volume_from_exact_sized_image(self): """Test create volume from an image of the same size. Verify that an image which is exactly the same size as the volume, will work correctly. """ try: volume_id = None volume_api = cinder.volume.api.API( image_service=FakeImageService()) volume = volume_api.create(self.context, 2, 'name', 'description', image_id=self.FAKE_UUID, volume_type=self.vol_type) volume_id = volume['id'] self.assertEqual('creating', volume['status']) finally: # cleanup db.volume_destroy(self.context, volume_id) def test_create_volume_from_oversized_image(self): """Verify that an image which is too big will fail correctly.""" class _ModifiedFakeImageService(FakeImageService): def show(self, context, image_id): return {'size': 2 * units.Gi + 1, 'disk_format': 'raw', 'container_format': 'bare', 'status': 'active'} volume_api = cinder.volume.api.API( image_service=_ModifiedFakeImageService()) self.assertRaises(exception.InvalidInput, volume_api.create, self.context, 2, 'name', 'description', image_id=1) def test_create_volume_with_mindisk_error(self): """Verify volumes smaller than image minDisk will cause an error.""" class _ModifiedFakeImageService(FakeImageService): def show(self, context, image_id): return {'size': 2 * units.Gi, 'disk_format': 'raw', 'container_format': 'bare', 'min_disk': 5, 'status': 'active'} volume_api = cinder.volume.api.API( image_service=_ModifiedFakeImageService()) self.assertRaises(exception.InvalidInput, volume_api.create, self.context, 2, 'name', 'description', image_id=1) def test_create_volume_with_deleted_imaged(self): """Verify create volume from image will cause an error.""" class _ModifiedFakeImageService(FakeImageService): def show(self, context, image_id): return {'size': 2 * units.Gi, 'disk_format': 'raw', 'container_format': 'bare', 'min_disk': 5, 'status': 'deleted', 'id': image_id} volume_api = cinder.volume.api.API( image_service=_ModifiedFakeImageService()) self.assertRaises(exception.InvalidInput, volume_api.create, self.context, 2, 'name', 'description', image_id=1) def test_copy_volume_to_image_maintenance(self): """Test copy volume to image in maintenance.""" test_meta1 = {'fake_key1': 'fake_value1', 'fake_key2': 'fake_value2'} volume = tests_utils.create_volume(self.context, metadata=test_meta1, **self.volume_params) volume['status'] = 'maintenance' volume_api = cinder.volume.api.API() self.assertRaises(exception.InvalidVolume, volume_api.copy_volume_to_image, self.context, volume, test_meta1, force=True) class CopyVolumeToImagePrivateFunctionsTestCase( cinder.tests.unit.test.TestCase): @mock.patch('cinder.volume.api.API.get_volume_image_metadata', return_value={'some_key': 'some_value', 'cinder_encryption_key_id': 'stale_value'}) def test_merge_volume_image_meta(self, mock_get_img_meta): # this is what we're passing to copy_volume_to_image image_meta = { 'container_format': 'bare', 'disk_format': 'raw', 'cinder_encryption_key_id': 'correct_value' } self.assertNotIn('properties', image_meta) volume_api = cinder.volume.api.API() volume_api._merge_volume_image_meta(None, None, image_meta) # we've got 'properties' now self.assertIn('properties', image_meta) # verify the key_id is what we expect self.assertEqual(image_meta['cinder_encryption_key_id'], 'correct_value') translate = cinder.image.glance.GlanceImageService._translate_to_glance sent_to_glance = translate(image_meta) # this is correct, glance gets a "flat" dict of properties self.assertNotIn('properties', sent_to_glance) # make sure the image would be created in Glance with the # correct key_id self.assertEqual(image_meta['cinder_encryption_key_id'], sent_to_glance['cinder_encryption_key_id'])