diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py index 9ea8dab189..e224dba27f 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py @@ -26,6 +26,7 @@ from cinder import context from cinder import exception +from cinder.image import image_utils from cinder.objects import fields from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_volume @@ -550,11 +551,125 @@ def test__update_volume_stats(self): self.assertRaises(NotImplementedError, self.driver._update_volume_stats) + @mock.patch.object(image_utils, 'resize_image') + @mock.patch.object(image_utils, 'qemu_img_info') + @mock.patch.object(image_utils, 'fetch_to_raw') + def test_copy_image_to_volume_cache_uses_image_size( + self, + mock_fetch_to_raw, + mock_qemu_img_info, + mock_resize_image): + + volume = fake_volume.fake_volume_obj( + self.ctxt, + id=fake.VOLUME_ID, + size=10, + host='fake-host') + + image_id = 'image-1' + image_size = 10 + + mock_qemu_img_info.return_value.virtual_size = image_size * units.Gi + + self.mock_object( + self.driver, + '_is_flexgroup', + return_value=False) + + mock_register = self.mock_object( + self.driver, + '_register_image_in_cache') + + self.mock_object( + self.driver, + 'local_path', + return_value=f'/tmp/{volume.id}') + + self.driver.copy_image_to_volume( + mock.sentinel.context, + volume, + mock.sentinel.image_service, + image_id) + + mock_fetch_to_raw.assert_called_once_with( + mock.sentinel.context, + mock.sentinel.image_service, + image_id, + f'/tmp/{volume.id}', + self.driver.configuration.volume_dd_blocksize, + size=volume.size, + run_as_root=self.driver._execute_as_root, + disable_sparse=False) + + mock_qemu_img_info.assert_called_once_with( + f'/tmp/{volume.id}', + run_as_root=self.driver._execute_as_root) + + mock_resize_image.assert_called_once_with( + f'/tmp/{volume.id}', + image_size, + run_as_root=self.driver._execute_as_root) + + mock_register.assert_called_once_with( + volume, image_id) + + @mock.patch.object(image_utils, 'resize_image') + @mock.patch.object(image_utils, 'qemu_img_info') + @mock.patch.object(image_utils, 'fetch_to_raw') + def test_copy_image_to_volume_image_size_mismatch( + self, + mock_fetch_to_raw, + mock_qemu_img_info, + mock_resize_image): + + volume = fake_volume.fake_volume_obj( + self.ctxt, + id=fake.VOLUME_ID, + size=30, + host='fake-host') + + image_id = 'image-1' + + self.mock_object( + self.driver, + '_is_flexgroup', + return_value=False) + + mock_register = self.mock_object( + self.driver, + '_register_image_in_cache') + + self.mock_object( + self.driver, + 'local_path', + return_value=f'/tmp/{volume.id}') + + mock_qemu_img_info.return_value.virtual_size = 10 * units.Gi + + self.assertRaises( + exception.ImageUnacceptable, + self.driver.copy_image_to_volume, + mock.sentinel.context, + volume, + mock.sentinel.image_service, + image_id) + + mock_fetch_to_raw.assert_called_once() + mock_qemu_img_info.assert_called_once_with( + f'/tmp/{volume.id}', + run_as_root=self.driver._execute_as_root) + mock_resize_image.assert_called_once_with( + f'/tmp/{volume.id}', + 30, + run_as_root=self.driver._execute_as_root) + + mock_register.assert_called_once() + def test_copy_image_to_volume_base_exception(self): mock_info_log = self.mock_object(nfs_base.LOG, 'info') self.mock_object(self.driver, '_ensure_flexgroup_not_in_cg') - self.mock_object(remotefs.RemoteFSDriver, 'copy_image_to_volume', - side_effect=exception.NfsException) + self.mock_object(self.driver, 'local_path', + mock.Mock(side_effect=exception.NfsException)) self.assertRaises(exception.NfsException, self.driver.copy_image_to_volume, @@ -562,29 +677,43 @@ def test_copy_image_to_volume_base_exception(self): 'fake_img_service', fake.IMAGE_FILE_ID) mock_info_log.assert_not_called() - def test_copy_image_to_volume(self): + @mock.patch.object(image_utils, 'resize_image') + @mock.patch.object(image_utils, 'qemu_img_info') + @mock.patch.object(image_utils, 'fetch_to_raw') + def test_copy_image_to_volume(self, + mock_fetch_to_raw, + mock_qemu_img_info, + mock_resize_image): + volume = fake_volume.fake_volume_obj(self.ctxt, **fake.NFS_VOLUME) + mock_qemu_img_info.return_value.virtual_size = ( + volume.size * units.Gi) + mock_log = self.mock_object(nfs_base, 'LOG') self.mock_object(self.driver, '_is_flexgroup', return_value=False) self.mock_object(self.driver, '_is_flexgroup_clone_file_supported', return_value=True) self.mock_object(self.driver, '_ensure_flexgroup_not_in_cg') - mock_copy_image = self.mock_object( - remotefs.RemoteFSDriver, 'copy_image_to_volume') + self.mock_object(self.driver, 'local_path') mock_register_image = self.mock_object( self.driver, '_register_image_in_cache') + image_service = mock.Mock() + image_service.show.return_value = { + 'disk_format': 'raw', + 'container_format': 'bare', + } self.driver.copy_image_to_volume('fake_context', - fake.NFS_VOLUME, - 'fake_img_service', + volume, + image_service, fake.IMAGE_FILE_ID) - mock_copy_image.assert_called_once_with( - 'fake_context', fake.NFS_VOLUME, 'fake_img_service', - fake.IMAGE_FILE_ID, disable_sparse=False) + self.assertEqual(1, mock_fetch_to_raw.call_count) + self.assertEqual(1, mock_qemu_img_info.call_count) + self.assertEqual(1, mock_resize_image.call_count) self.assertEqual(1, mock_log.info.call_count) mock_register_image.assert_called_once_with( - fake.NFS_VOLUME, fake.IMAGE_FILE_ID) + volume, fake.IMAGE_FILE_ID) @ddt.data(None, Exception) def test__register_image_in_cache(self, exc): diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py index e25e42e631..063fcf20ba 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py @@ -511,12 +511,17 @@ def copy_image_to_volume(self, context, volume, image_service, image_id, disable_sparse=False): """Fetch the image from image_service and write it to the volume.""" self._ensure_flexgroup_not_in_cg(volume) - super(NetAppNfsDriver, self).copy_image_to_volume( - context, volume, image_service, image_id, - disable_sparse=disable_sparse) + # modified behaviour for cinder/volume/drivers/remotefs.py +530 + image_utils.fetch_to_raw(context, + image_service, + image_id, + self.local_path(volume), + self.configuration.volume_dd_blocksize, + size=volume.size, + run_as_root=self._execute_as_root, + disable_sparse=disable_sparse) LOG.info('Copied image to volume %s using regular download.', volume['id']) - if (not self._is_flexgroup(host=volume['host']) or self._is_flexgroup_clone_file_supported()): # NOTE(felipe_rodrigues): NetApp image cache relies on the @@ -524,6 +529,17 @@ def copy_image_to_volume(self, context, volume, image_service, image_id, # versions of FlexGroup. self._register_image_in_cache(volume, image_id) + image_utils.resize_image(self.local_path(volume), volume.size, + run_as_root=self._execute_as_root) + data = image_utils.qemu_img_info(self.local_path(volume), + run_as_root=self._execute_as_root) + virt_size = int(data.virtual_size // units.Gi) + if virt_size != volume.size: + raise exception.ImageUnacceptable( + image_id=image_id, + reason=(_("Expected volume size was %d") % volume.size) + + (_(" but size is now %d") % virt_size)) + def _register_image_in_cache(self, volume, image_id): """Stores image in the cache.""" file_name = 'img-cache-%s' % image_id