]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware: Skip vSAN for preallocated image download
authorVipin Balachandran <vbala@vmware.com>
Wed, 7 Jan 2015 06:09:43 +0000 (11:39 +0530)
committerVipin Balachandran <vbala@vmware.com>
Thu, 2 Apr 2015 17:36:53 +0000 (10:36 -0700)
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
cinder/tests/test_vmware_volumeops.py
cinder/volume/drivers/vmware/datastore.py
cinder/volume/drivers/vmware/vmdk.py
cinder/volume/drivers/vmware/volumeops.py

index e32719c1fcd7eddccd82d54ad0c745b5fbe650f7..a985d838344146ed63b79e9f2701e290b454ebcc 100644 (file)
@@ -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.'
index 237e52e2124fba7df172e0875a0dacdf345eff7a..50a1f0cbd9a711104241c3a9b0797c5fb0ff70df 100644 (file)
@@ -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)
 
index 0f3a914fc47c5323290aa06e9efab120874403cc..2e4e9b0ca3dc08925792a869f372ad43ea715cbb 100644 (file)
@@ -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."""
index 84cab530860c93c70c95f17ed6bda33e0f5850da..6a48f119451b41578e84975b33d651a07acda938 100644 (file)
@@ -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."""
index 44d6e7685782deb1fac723cf47df70b0f95d74a6..e401f7126273a299e99c3a0e5c8e4b1364080f00 100644 (file)
@@ -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."),