From 753d6a8574fcc2c0680c9f75c31e7bc9035e1534 Mon Sep 17 00:00:00 2001 From: Vipin Balachandran Date: Wed, 7 Jan 2015 11:39:43 +0530 Subject: [PATCH] VMware: Skip vSAN for preallocated image download Copying preallocated image to volume involves creating a virtual disk using the image. The virtual disk has a descriptor and an extent. The preallocated image is used as the flat extent to create the virtual disk. To create virtual disk from preallocated image, we first create a temporary virtual disk (with a descriptor and flat extent), then replace its flat extent with the downloaded image. Since vSAN datastores do not support flat extent, the above operation fails if such a datastore is selected for virtual disk creation. This patch fixes the problem by not selecting vSAN datastores for temporary virtual disk creation. Change-Id: Ib4c0fcf5c379b137e70378f137d996bf510f05b3 Closes-Bug: #1301943 --- cinder/tests/test_vmware_vmdk.py | 179 +++++++++++++++++++--- cinder/tests/test_vmware_volumeops.py | 94 +++++++++++- cinder/volume/drivers/vmware/datastore.py | 6 + cinder/volume/drivers/vmware/vmdk.py | 102 +++++++++--- cinder/volume/drivers/vmware/volumeops.py | 43 +++++- 5 files changed, 366 insertions(+), 58 deletions(-) diff --git a/cinder/tests/test_vmware_vmdk.py b/cinder/tests/test_vmware_vmdk.py index e32719c1f..a985d8383 100644 --- a/cinder/tests/test_vmware_vmdk.py +++ b/cinder/tests/test_vmware_vmdk.py @@ -898,53 +898,158 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase): vops.delete_backing.assert_called_once_with(backing) self.assertFalse(extend_disk.called) + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') @mock.patch( 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') @mock.patch.object(VMDK_DRIVER, '_copy_image') @mock.patch.object(VMDK_DRIVER, 'volumeops') def test_create_virtual_disk_from_preallocated_image( - self, vops, copy_image, flat_extent_path): + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): self._test_create_virtual_disk_from_preallocated_image( - vops, copy_image, flat_extent_path) + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) def _test_create_virtual_disk_from_preallocated_image( - self, vops, copy_image, flat_extent_path): + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): context = mock.Mock() image_service = mock.Mock() image_id = mock.Mock() image_size_in_bytes = 2 * units.Gi - dc_ref = mock.Mock() - ds_name = "nfs" - folder_path = "A/B/" - disk_name = "disk-1" + dest_dc_ref = mock.sentinel.dest_dc_ref + dest_ds_name = "nfs" + dest_folder_path = "A/B/" + dest_disk_name = "disk-1" adapter_type = "ide" - src_path = mock.Mock() - flat_extent_path.return_value = src_path + dc_ref = mock.sentinel.dc_ref + ds_name = "local-0" + folder_path = "cinder_temp" + get_temp_image_folder.return_value = (dc_ref, ds_name, folder_path) + + path = mock.Mock() + dest_path = mock.Mock() + flat_extent_path.side_effect = [path, dest_path] ret = self._driver._create_virtual_disk_from_preallocated_image( - context, image_service, image_id, image_size_in_bytes, dc_ref, - ds_name, folder_path, disk_name, adapter_type) + context, image_service, image_id, image_size_in_bytes, dest_dc_ref, + dest_ds_name, dest_folder_path, dest_disk_name, adapter_type) create_descriptor = vops.create_flat_extent_virtual_disk_descriptor create_descriptor.assert_called_once_with( - dc_ref, src_path, image_size_in_bytes / units.Ki, adapter_type, + dc_ref, path, image_size_in_bytes / units.Ki, adapter_type, vmdk.EAGER_ZEROED_THICK_VMDK_TYPE) copy_image.assert_called_once_with( context, dc_ref, image_service, image_id, image_size_in_bytes, - ds_name, src_path.get_flat_extent_file_path()) - self.assertEqual(src_path, ret) + ds_name, path.get_flat_extent_file_path()) + copy_temp_virtual_disk.assert_called_once_with(dc_ref, path, + dest_dc_ref, dest_path) + self.assertEqual(dest_path, ret) + + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') + @mock.patch( + 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') + @mock.patch.object(VMDK_DRIVER, '_copy_image') + @mock.patch.object(VMDK_DRIVER, 'volumeops') + def test_create_virtual_disk_from_preallocated_image_with_no_disk_copy( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + self._test_create_virtual_disk_from_preallocated_image_with_no_copy( + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) + + def _test_create_virtual_disk_from_preallocated_image_with_no_copy( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + context = mock.Mock() + image_service = mock.Mock() + image_id = mock.Mock() + image_size_in_bytes = 2 * units.Gi + dest_dc_ref = mock.Mock(value=mock.sentinel.dest_dc_ref) + dest_ds_name = "nfs" + dest_folder_path = "A/B/" + dest_disk_name = "disk-1" + adapter_type = "ide" + + dc_ref = mock.Mock(value=mock.sentinel.dest_dc_ref) + ds_name = dest_ds_name + folder_path = "cinder_temp" + get_temp_image_folder.return_value = (dc_ref, ds_name, folder_path) + + path = mock.Mock() + flat_extent_path.return_value = path + + ret = self._driver._create_virtual_disk_from_preallocated_image( + context, image_service, image_id, image_size_in_bytes, dest_dc_ref, + dest_ds_name, dest_folder_path, dest_disk_name, adapter_type) + + create_descriptor = vops.create_flat_extent_virtual_disk_descriptor + create_descriptor.assert_called_once_with( + dc_ref, path, image_size_in_bytes / units.Ki, adapter_type, + vmdk.EAGER_ZEROED_THICK_VMDK_TYPE) + copy_image.assert_called_once_with( + context, dc_ref, image_service, image_id, image_size_in_bytes, + ds_name, path.get_flat_extent_file_path()) + self.assertFalse(copy_temp_virtual_disk.called) + self.assertEqual(path, ret) + + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') + @mock.patch( + 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') + @mock.patch.object(VMDK_DRIVER, '_copy_image') + @mock.patch.object(VMDK_DRIVER, 'volumeops') + def test_create_virtual_disk_from_preallocated_image_with_copy_error( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + self._test_create_virtual_disk_from_preallocated_image_with_copy_error( + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) + + def _test_create_virtual_disk_from_preallocated_image_with_copy_error( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + context = mock.Mock() + image_service = mock.Mock() + image_id = mock.Mock() + image_size_in_bytes = 2 * units.Gi + dest_dc_ref = mock.sentinel.dest_dc_ref + dest_ds_name = "nfs" + dest_folder_path = "A/B/" + dest_disk_name = "disk-1" + adapter_type = "ide" + + dc_ref = mock.sentinel.dc_ref + ds_name = "local-0" + folder_path = "cinder_temp" + get_temp_image_folder.return_value = (dc_ref, ds_name, folder_path) + + path = mock.Mock() + dest_path = mock.Mock() + flat_extent_path.side_effect = [path, dest_path] - create_descriptor.reset_mock() - copy_image.reset_mock() copy_image.side_effect = exceptions.VimException("error") + self.assertRaises( exceptions.VimException, self._driver._create_virtual_disk_from_preallocated_image, - context, image_service, image_id, image_size_in_bytes, dc_ref, - ds_name, folder_path, disk_name, adapter_type) + context, image_service, image_id, image_size_in_bytes, dest_dc_ref, + dest_ds_name, dest_folder_path, dest_disk_name, adapter_type) + + create_descriptor = vops.create_flat_extent_virtual_disk_descriptor + create_descriptor.assert_called_once_with( + dc_ref, path, image_size_in_bytes / units.Ki, adapter_type, + vmdk.EAGER_ZEROED_THICK_VMDK_TYPE) + + copy_image.assert_called_once_with( + context, dc_ref, image_service, image_id, image_size_in_bytes, + ds_name, path.get_flat_extent_file_path()) vops.delete_file.assert_called_once_with( - src_path.get_descriptor_ds_file_path(), dc_ref) + path.get_descriptor_ds_file_path(), dc_ref) + self.assertFalse(copy_temp_virtual_disk.called) @mock.patch( 'cinder.volume.drivers.vmware.volumeops.' @@ -984,7 +1089,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase): context, dc_ref, image_service, image_id, image_size_in_bytes, ds_name, src_path.get_descriptor_file_path()) copy_temp_virtual_disk.assert_called_once_with( - dc_ref, src_path, dest_path) + dc_ref, src_path, dc_ref, dest_path) self.assertEqual(dest_path, ret) @mock.patch.object(image_transfer, 'download_stream_optimized_image') @@ -2384,14 +2489,44 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): generate_uuid, extend_disk) + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') @mock.patch( 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') @mock.patch.object(VMDK_DRIVER, '_copy_image') @mock.patch.object(VMDK_DRIVER, 'volumeops') def test_create_virtual_disk_from_preallocated_image( - self, vops, copy_image, flat_extent_path): + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): self._test_create_virtual_disk_from_preallocated_image( - vops, copy_image, flat_extent_path) + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) + + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') + @mock.patch( + 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') + @mock.patch.object(VMDK_DRIVER, '_copy_image') + @mock.patch.object(VMDK_DRIVER, 'volumeops') + def test_create_virtual_disk_from_preallocated_image_with_no_disk_copy( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + self._test_create_virtual_disk_from_preallocated_image_with_no_copy( + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) + + @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') + @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') + @mock.patch( + 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath') + @mock.patch.object(VMDK_DRIVER, '_copy_image') + @mock.patch.object(VMDK_DRIVER, 'volumeops') + def test_create_virtual_disk_from_preallocated_image_with_copy_error( + self, vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk): + self._test_create_virtual_disk_from_preallocated_image_with_copy_error( + vops, copy_image, flat_extent_path, get_temp_image_folder, + copy_temp_virtual_disk) @mock.patch( 'cinder.volume.drivers.vmware.volumeops.' diff --git a/cinder/tests/test_vmware_volumeops.py b/cinder/tests/test_vmware_volumeops.py index 237e52e21..50a1f0cbd 100644 --- a/cinder/tests/test_vmware_volumeops.py +++ b/cinder/tests/test_vmware_volumeops.py @@ -1174,6 +1174,63 @@ class VolumeOpsTestCase(test.TestCase): datacenter=datacenter) self.session.wait_for_task.assert_called_once_with(task) + def test_create_datastore_folder(self): + file_manager = mock.sentinel.file_manager + self.session.vim.service_content.fileManager = file_manager + invoke_api = self.session.invoke_api + + ds_name = "nfs" + folder_path = "test/" + datacenter = mock.sentinel.datacenter + + self.vops.create_datastore_folder(ds_name, folder_path, datacenter) + invoke_api.assert_called_once_with(self.session.vim, + 'MakeDirectory', + file_manager, + name="[nfs] test/", + datacenter=datacenter) + + def test_create_datastore_folder_with_existing_folder(self): + file_manager = mock.sentinel.file_manager + self.session.vim.service_content.fileManager = file_manager + invoke_api = self.session.invoke_api + invoke_api.side_effect = exceptions.FileAlreadyExistsException + + ds_name = "nfs" + folder_path = "test/" + datacenter = mock.sentinel.datacenter + + self.vops.create_datastore_folder(ds_name, folder_path, datacenter) + invoke_api.assert_called_once_with(self.session.vim, + 'MakeDirectory', + file_manager, + name="[nfs] test/", + datacenter=datacenter) + invoke_api.side_effect = None + + def test_create_datastore_folder_with_invoke_api_error(self): + file_manager = mock.sentinel.file_manager + self.session.vim.service_content.fileManager = file_manager + invoke_api = self.session.invoke_api + invoke_api.side_effect = exceptions.VimFaultException( + ["FileFault"], "error") + + ds_name = "nfs" + folder_path = "test/" + datacenter = mock.sentinel.datacenter + + self.assertRaises(exceptions.VimFaultException, + self.vops.create_datastore_folder, + ds_name, + folder_path, + datacenter) + invoke_api.assert_called_once_with(self.session.vim, + 'MakeDirectory', + file_manager, + name="[nfs] test/", + datacenter=datacenter) + invoke_api.side_effect = None + def test_get_path_name(self): path = mock.Mock(spec=object) path_name = mock.sentinel.vm_path_name @@ -1298,19 +1355,44 @@ class VolumeOpsTestCase(test.TestCase): task = mock.sentinel.task invoke_api = self.session.invoke_api invoke_api.return_value = task + disk_mgr = self.session.vim.service_content.virtualDiskManager - dc_ref = self.session.dc_ref - src_vmdk_file_path = self.session.src - dest_vmdk_file_path = self.session.dest - self.vops.copy_vmdk_file(dc_ref, src_vmdk_file_path, + src_dc_ref = mock.sentinel.src_dc_ref + src_vmdk_file_path = mock.sentinel.src_vmdk_file_path + dest_dc_ref = mock.sentinel.dest_dc_ref + dest_vmdk_file_path = mock.sentinel.dest_vmdk_file_path + self.vops.copy_vmdk_file(src_dc_ref, src_vmdk_file_path, + dest_vmdk_file_path, dest_dc_ref) + + invoke_api.assert_called_once_with(self.session.vim, + 'CopyVirtualDisk_Task', + disk_mgr, + sourceName=src_vmdk_file_path, + sourceDatacenter=src_dc_ref, + destName=dest_vmdk_file_path, + destDatacenter=dest_dc_ref, + force=True) + self.session.wait_for_task.assert_called_once_with(task) + + def test_copy_vmdk_file_with_default_dest_datacenter(self): + task = mock.sentinel.task + invoke_api = self.session.invoke_api + invoke_api.return_value = task + + disk_mgr = self.session.vim.service_content.virtualDiskManager + src_dc_ref = mock.sentinel.src_dc_ref + src_vmdk_file_path = mock.sentinel.src_vmdk_file_path + dest_vmdk_file_path = mock.sentinel.dest_vmdk_file_path + self.vops.copy_vmdk_file(src_dc_ref, src_vmdk_file_path, dest_vmdk_file_path) + invoke_api.assert_called_once_with(self.session.vim, 'CopyVirtualDisk_Task', disk_mgr, sourceName=src_vmdk_file_path, - sourceDatacenter=dc_ref, + sourceDatacenter=src_dc_ref, destName=dest_vmdk_file_path, - destDatacenter=dc_ref, + destDatacenter=src_dc_ref, force=True) self.session.wait_for_task.assert_called_once_with(task) diff --git a/cinder/volume/drivers/vmware/datastore.py b/cinder/volume/drivers/vmware/datastore.py index 0f3a914fc..2e4e9b0ca 100644 --- a/cinder/volume/drivers/vmware/datastore.py +++ b/cinder/volume/drivers/vmware/datastore.py @@ -36,6 +36,12 @@ class DatastoreType(object): VMFS = "vmfs" VSAN = "vsan" + _ALL_TYPES = {NFS, VMFS, VSAN} + + @staticmethod + def get_all_types(): + return DatastoreType._ALL_TYPES + class DatastoreSelector(object): """Class for selecting datastores which satisfy input requirements.""" diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index 84cab5308..6a48f1194 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -58,6 +58,8 @@ CREATE_PARAM_ADAPTER_TYPE = 'adapter_type' CREATE_PARAM_DISK_LESS = 'disk_less' CREATE_PARAM_BACKING_NAME = 'name' +TMP_IMAGES_DATASTORE_FOLDER_PATH = "cinder_temp/" + vmdk_opts = [ cfg.StrOpt('vmware_host_ip', default=None, @@ -564,7 +566,22 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): def _relocate_backing(self, volume, backing, host): pass - def _select_ds_for_volume(self, volume, host=None): + def _select_datastore(self, req, host=None): + """Selects datastore satisfying the given requirements. + + :return: (host, resource_pool, summary) + """ + + hosts = [host] if host else None + best_candidate = self.ds_sel.select_datastore(req, hosts=hosts) + if not best_candidate: + LOG.error(_LE("There is no valid datastore satisfying " + "requirements: %s."), req) + raise vmdk_exceptions.NoValidDatastoreException() + + return best_candidate + + def _select_ds_for_volume(self, volume, host=None, create_params=None): """Select datastore that can accommodate the given volume's backing. Returns the selected datastore summary along with a compute host and @@ -577,16 +594,7 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): req[hub.DatastoreSelector.PROFILE_NAME] = self._get_storage_profile( volume) - # Select datastore satisfying the requirements. - hosts = [host] if host else None - best_candidate = self.ds_sel.select_datastore(req, hosts=hosts) - if not best_candidate: - LOG.error(_LE("There is no valid datastore to create backing for " - "volume: %s."), - volume['name']) - raise vmdk_exceptions.NoValidDatastoreException() - - (host_ref, resource_pool, summary) = best_candidate + (host_ref, resource_pool, summary) = self._select_datastore(req, host) dc = self.volumeops.get_dc(resource_pool) folder = self._get_volume_group_folder(dc) @@ -906,13 +914,14 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): descriptor_ds_file_path, exc_info=True) - def _copy_temp_virtual_disk(self, dc_ref, src_path, dest_path): + def _copy_temp_virtual_disk(self, src_dc_ref, src_path, dest_dc_ref, + dest_path): """Clones a temporary virtual disk and deletes it finally.""" try: self.volumeops.copy_vmdk_file( - dc_ref, src_path.get_descriptor_ds_file_path(), - dest_path.get_descriptor_ds_file_path()) + src_dc_ref, src_path.get_descriptor_ds_file_path(), + dest_path.get_descriptor_ds_file_path(), dest_dc_ref) except exceptions.VimException: with excutils.save_and_reraise_exception(): LOG.exception(_LE("Error occurred while copying %(src)s to " @@ -922,7 +931,29 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): finally: # Delete temporary disk. self._delete_temp_disk(src_path.get_descriptor_ds_file_path(), - dc_ref) + src_dc_ref) + + def _get_temp_image_folder(self, image_size_in_bytes): + """Get datastore folder for downloading temporary images.""" + # Form requirements for datastore selection. + req = {} + req[hub.DatastoreSelector.SIZE_BYTES] = image_size_in_bytes + # vSAN datastores don't support virtual disk with + # flat extent; skip such datastores. + req[hub.DatastoreSelector.HARD_AFFINITY_DS_TYPE] = ( + hub.DatastoreType.get_all_types() - {hub.DatastoreType.VSAN}) + + # Select datastore satisfying the requirements. + (host_ref, _resource_pool, summary) = self._select_datastore(req) + + ds_name = summary.name + dc_ref = self.volumeops.get_dc(host_ref) + + # Create temporary datastore folder. + folder_path = TMP_IMAGES_DATASTORE_FOLDER_PATH + self.volumeops.create_datastore_folder(ds_name, folder_path, dc_ref) + + return (dc_ref, ds_name, folder_path) def _create_virtual_disk_from_sparse_image( self, context, image_service, image_id, image_size_in_bytes, @@ -947,19 +978,42 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): dest_path = volumeops.FlatExtentVirtualDiskPath(ds_name, folder_path, disk_name) - self._copy_temp_virtual_disk(dc_ref, src_path, dest_path) + self._copy_temp_virtual_disk(dc_ref, src_path, dc_ref, dest_path) LOG.debug("Created virtual disk: %s from sparse vmdk image.", dest_path.get_descriptor_ds_file_path()) return dest_path def _create_virtual_disk_from_preallocated_image( self, context, image_service, image_id, image_size_in_bytes, - dc_ref, ds_name, folder_path, disk_name, adapter_type): + dest_dc_ref, dest_ds_name, dest_folder_path, dest_disk_name, + adapter_type): """Creates virtual disk from an image which is a flat extent.""" - path = volumeops.FlatExtentVirtualDiskPath(ds_name, - folder_path, - disk_name) + # Upload the image and use it as a flat extent to create a virtual + # disk. First, find the datastore folder to download the image. + (dc_ref, ds_name, + folder_path) = self._get_temp_image_folder(image_size_in_bytes) + + # pylint: disable=E1101 + if ds_name == dest_ds_name and dc_ref.value == dest_dc_ref.value: + # Temporary image folder and destination path are on the same + # datastore. We can directly download the image to the destination + # folder to save one virtual disk copy. + path = volumeops.FlatExtentVirtualDiskPath(dest_ds_name, + dest_folder_path, + dest_disk_name) + dest_path = path + else: + # Use the image to create a temporary virtual disk which is then + # copied to the destination folder. + disk_name = uuidutils.generate_uuid() + path = volumeops.FlatExtentVirtualDiskPath(ds_name, + folder_path, + disk_name) + dest_path = volumeops.FlatExtentVirtualDiskPath(dest_ds_name, + dest_folder_path, + dest_disk_name) + LOG.debug("Creating virtual disk: %(path)s from (flat extent) image: " "%(image_id)s.", {'path': path.get_descriptor_ds_file_path(), @@ -992,9 +1046,13 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): path.get_descriptor_ds_file_path(), exc_info=True) + if dest_path != path: + # Copy temporary disk to given destination. + self._copy_temp_virtual_disk(dc_ref, path, dest_dc_ref, dest_path) + LOG.debug("Created virtual disk: %s from flat extent image.", - path.get_descriptor_ds_file_path()) - return path + dest_path.get_descriptor_ds_file_path()) + return dest_path def _check_disk_conversion(self, image_disk_type, extra_spec_disk_type): """Check if disk type conversion is needed.""" diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py index 44d6e7685..e401f7126 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -1183,6 +1183,28 @@ class VMwareVolumeOps(object): self._session.wait_for_task(task) LOG.info(_LI("Successfully deleted file: %s."), file_path) + def create_datastore_folder(self, ds_name, folder_path, datacenter): + """Creates a datastore folder. + + This method returns silently if the folder already exists. + + :param ds_name: datastore name + :param folder_path: path of folder to create + :param datacenter: datacenter of target datastore + """ + fileManager = self._session.vim.service_content.fileManager + ds_folder_path = "[%s] %s" % (ds_name, folder_path) + LOG.debug("Creating datastore folder: %s.", ds_folder_path) + try: + self._session.invoke_api(self._session.vim, + 'MakeDirectory', + fileManager, + name=ds_folder_path, + datacenter=datacenter) + LOG.info(_LI("Created datastore folder: %s."), folder_path) + except exceptions.FileAlreadyExistsException: + LOG.debug("Datastore folder: %s already exists.", folder_path) + def get_path_name(self, backing): """Get path name of the backing. @@ -1308,26 +1330,31 @@ class VMwareVolumeOps(object): LOG.debug("Created descriptor: %s.", path.get_descriptor_ds_file_path()) - def copy_vmdk_file(self, dc_ref, src_vmdk_file_path, dest_vmdk_file_path): + def copy_vmdk_file(self, src_dc_ref, src_vmdk_file_path, + dest_vmdk_file_path, dest_dc_ref=None): """Copy contents of the src vmdk file to dest vmdk file. - During the copy also coalesce snapshots of src if present. - dest_vmdk_file_path will be created if not already present. - - :param dc_ref: Reference to datacenter containing src and dest + :param src_dc_ref: Reference to datacenter containing src datastore :param src_vmdk_file_path: Source vmdk file path :param dest_vmdk_file_path: Destination vmdk file path + :param dest_dc_ref: Reference to datacenter of dest datastore. + If unspecified, source datacenter is used. """ - LOG.debug('Copying disk data before snapshot of the VM') + LOG.debug('Copying disk: %(src)s to %(dest)s.', + {'src': src_vmdk_file_path, + 'dest': dest_vmdk_file_path}) + + dest_dc_ref = dest_dc_ref or src_dc_ref diskMgr = self._session.vim.service_content.virtualDiskManager task = self._session.invoke_api(self._session.vim, 'CopyVirtualDisk_Task', diskMgr, sourceName=src_vmdk_file_path, - sourceDatacenter=dc_ref, + sourceDatacenter=src_dc_ref, destName=dest_vmdk_file_path, - destDatacenter=dc_ref, + destDatacenter=dest_dc_ref, force=True) + LOG.debug("Initiated copying disk data via task: %s.", task) self._session.wait_for_task(task) LOG.info(_LI("Successfully copied disk at: %(src)s to: %(dest)s."), -- 2.45.2