fake_context, fake_volume,
image_service, fake_image_id)
- @mock.patch.object(vmware_images, 'fetch_flat_image')
- @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
- @mock.patch.object(VMDK_DRIVER, '_get_ds_name_flat_vmdk_path')
- @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
- @mock.patch.object(VMDK_DRIVER, 'session')
+ @mock.patch('cinder.openstack.common.uuidutils.generate_uuid')
+ @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
- def test_copy_image_to_volume_vmdk(self, volume_ops, session,
- _create_backing_in_inventory,
- _get_ds_name_flat_vmdk_path,
- _extend_vmdk_virtual_disk,
- fetch_flat_image):
- """Test copy_image_to_volume with an acceptable vmdk disk format."""
- self._test_copy_image_to_volume_vmdk(volume_ops, session,
- _create_backing_in_inventory,
- _get_ds_name_flat_vmdk_path,
- _extend_vmdk_virtual_disk,
- fetch_flat_image)
-
- def _test_copy_image_to_volume_vmdk(self, volume_ops, session,
- _create_backing_in_inventory,
- _get_ds_name_flat_vmdk_path,
- _extend_vmdk_virtual_disk,
- fetch_flat_image):
- cookies = session.vim.client.options.transport.cookiejar
- fake_context = mock.sentinel.context
- fake_image_id = 'image-id'
- fake_image_meta = {'disk_format': 'vmdk',
- 'size': 2 * units.Gi,
- 'properties': {'vmware_disktype': 'preallocated'}}
+ @mock.patch.object(VMDK_DRIVER,
+ '_create_virtual_disk_from_preallocated_image')
+ @mock.patch.object(VMDK_DRIVER, '_create_virtual_disk_from_sparse_image')
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.vmdk.VMwareEsxVmdkDriver._get_disk_type')
+ @mock.patch.object(VMDK_DRIVER, '_get_ds_name_folder_path')
+ @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
+ def test_copy_image_to_volume_non_stream_optimized(
+ self, create_backing, get_ds_name_folder_path, get_disk_type,
+ create_disk_from_sparse_image, create_disk_from_preallocated_image,
+ vops, select_ds_for_volume, generate_uuid):
+ self._test_copy_image_to_volume_non_stream_optimized(
+ create_backing,
+ get_ds_name_folder_path,
+ get_disk_type,
+ create_disk_from_sparse_image,
+ create_disk_from_preallocated_image,
+ vops,
+ select_ds_for_volume,
+ generate_uuid)
+
+ def _test_copy_image_to_volume_non_stream_optimized(
+ self, create_backing, get_ds_name_folder_path, get_disk_type,
+ create_disk_from_sparse_image, create_disk_from_preallocated_image,
+ vops, select_ds_for_volume, generate_uuid):
+ image_size_in_bytes = 2 * units.Gi
+ adapter_type = 'lsiLogic'
+ image_meta = {'disk_format': 'vmdk',
+ 'size': image_size_in_bytes,
+ 'properties': {'vmware_disktype': 'sparse',
+ 'vmwware_adaptertype': adapter_type}}
image_service = mock.Mock(glance.GlanceImageService)
- fake_size = 3
- fake_volume = {'name': 'volume_name', 'size': fake_size}
- fake_backing = mock.sentinel.backing
- fake_datastore_name = 'datastore1'
- flat_vmdk_path = 'myvolumes/myvm-flat.vmdk'
- fake_host = mock.sentinel.host
- fake_datacenter = mock.sentinel.datacenter
- fake_datacenter_name = mock.sentinel.datacenter_name
- timeout = self._config.vmware_image_transfer_timeout_secs
+ image_service.show.return_value = image_meta
- image_service.show.return_value = fake_image_meta
- _create_backing_in_inventory.return_value = fake_backing
- _get_ds_name_flat_vmdk_path.return_value = (fake_datastore_name,
- flat_vmdk_path)
- volume_ops.get_host.return_value = fake_host
- volume_ops.get_dc.return_value = fake_datacenter
- volume_ops.get_entity_name.return_value = fake_datacenter_name
-
- # If the volume size is greater than the image size,
- # _extend_vmdk_virtual_disk will be called.
- self._driver.copy_image_to_volume(fake_context, fake_volume,
- image_service, fake_image_id)
- image_service.show.assert_called_with(fake_context, fake_image_id)
- _create_backing_in_inventory.assert_called_with(fake_volume)
- _get_ds_name_flat_vmdk_path.assert_called_with(fake_backing,
- fake_volume['name'])
-
- volume_ops.get_host.assert_called_with(fake_backing)
- volume_ops.get_dc.assert_called_with(fake_host)
- volume_ops.get_entity_name.assert_called_with(fake_datacenter)
- fetch_flat_image.assert_called_with(fake_context, timeout,
- image_service,
- fake_image_id,
- image_size=fake_image_meta['size'],
- host=self.IP,
- data_center_name=
- fake_datacenter_name,
- datastore_name=fake_datastore_name,
- cookies=cookies,
- file_path=flat_vmdk_path)
- _extend_vmdk_virtual_disk.assert_called_with(fake_volume['name'],
- fake_size)
- self.assertFalse(volume_ops.delete_backing.called)
+ backing = mock.Mock()
- # If the volume size is not greater then than the image size,
- # _extend_vmdk_virtual_disk will not be called.
- _extend_vmdk_virtual_disk.reset_mock()
- fake_size = 2
- fake_volume['size'] = fake_size
- self._driver.copy_image_to_volume(fake_context, fake_volume,
- image_service, fake_image_id)
- self.assertFalse(_extend_vmdk_virtual_disk.called)
- self.assertFalse(volume_ops.delete_backing.called)
+ def create_backing_mock(volume, create_params):
+ self.assertTrue(create_params[vmdk.CREATE_PARAM_DISK_LESS])
+ return backing
+ create_backing.side_effect = create_backing_mock
- # If fetch_flat_image raises an Exception, delete_backing
- # will be called.
- fetch_flat_image.side_effect = exception.CinderException
- self.assertRaises(exception.CinderException,
+ ds_name = mock.Mock()
+ folder_path = mock.Mock()
+ get_ds_name_folder_path.return_value = (ds_name, folder_path)
+
+ summary = mock.Mock()
+ select_ds_for_volume.return_value = (mock.sentinel.host,
+ mock.sentinel.rp,
+ mock.sentinel.folder,
+ summary)
+
+ uuid = "6b77b25a-9136-470e-899e-3c930e570d8e"
+ generate_uuid.return_value = uuid
+
+ host = mock.Mock()
+ dc_ref = mock.Mock()
+ vops.get_host.return_value = host
+ vops.get_dc.return_value = dc_ref
+
+ disk_type = vmdk.EAGER_ZEROED_THICK_VMDK_TYPE
+ get_disk_type.return_value = disk_type
+
+ path = mock.Mock()
+ create_disk_from_sparse_image.return_value = path
+ create_disk_from_preallocated_image.return_value = path
+
+ context = mock.Mock()
+ volume = {'name': 'volume_name',
+ 'id': 'volume_id',
+ 'size': image_size_in_bytes}
+ image_id = mock.Mock()
+ self._driver.copy_image_to_volume(
+ context, volume, image_service, image_id)
+
+ create_params = {vmdk.CREATE_PARAM_DISK_LESS: True,
+ vmdk.CREATE_PARAM_BACKING_NAME: uuid}
+ create_backing.assert_called_once_with(volume, create_params)
+ create_disk_from_sparse_image.assert_called_once_with(
+ context, image_service, image_id, image_size_in_bytes,
+ dc_ref, ds_name, folder_path, uuid)
+ vops.attach_disk_to_backing.assert_called_once_with(
+ backing, image_size_in_bytes / units.Ki, disk_type,
+ adapter_type, path.get_descriptor_ds_file_path())
+ select_ds_for_volume.assert_called_once_with(volume)
+ vops.clone_backing.assert_called_once_with(
+ volume['name'], backing, None, volumeops.FULL_CLONE_TYPE,
+ summary.datastore, disk_type)
+ vops.delete_backing.assert_called_once_with(backing)
+
+ create_backing.reset_mock()
+ vops.attach_disk_to_backing.reset_mock()
+ vops.delete_backing.reset_mock()
+ image_meta['properties']['vmware_disktype'] = 'preallocated'
+ self._driver.copy_image_to_volume(
+ context, volume, image_service, image_id)
+
+ del create_params[vmdk.CREATE_PARAM_BACKING_NAME]
+ create_backing.assert_called_once_with(volume, create_params)
+ create_disk_from_preallocated_image.assert_called_once_with(
+ context, image_service, image_id, image_size_in_bytes,
+ dc_ref, ds_name, folder_path, volume['name'], adapter_type)
+ vops.attach_disk_to_backing.assert_called_once_with(
+ backing, image_size_in_bytes / units.Ki, disk_type,
+ adapter_type, path.get_descriptor_ds_file_path())
+
+ create_disk_from_preallocated_image.side_effect = (
+ error_util.VimException("Error"))
+ self.assertRaises(error_util.VimException,
self._driver.copy_image_to_volume,
- fake_context, fake_volume,
- image_service, fake_image_id)
- volume_ops.delete_backing.assert_called_with(fake_backing)
+ context, volume, image_service, image_id)
+ vops.delete_backing.assert_called_once_with(backing)
+
+ @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._test_create_virtual_disk_from_preallocated_image(
+ vops, copy_image, flat_extent_path)
+
+ def _test_create_virtual_disk_from_preallocated_image(
+ self, vops, copy_image, flat_extent_path):
+ 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"
+ adapter_type = "ide"
+
+ src_path = mock.Mock()
+ flat_extent_path.return_value = src_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)
+
+ 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,
+ 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)
+
+ create_descriptor.reset_mock()
+ copy_image.reset_mock()
+ copy_image.side_effect = error_util.VimException("error")
+ self.assertRaises(
+ error_util.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)
+ vops.delete_file.assert_called_once_with(
+ src_path.get_descriptor_ds_file_path(), dc_ref)
+
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.volumeops.'
+ 'MonolithicSparseVirtualDiskPath')
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath')
+ @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk')
+ @mock.patch.object(VMDK_DRIVER, '_copy_image')
+ def test_create_virtual_disk_from_sparse_image(
+ self, copy_image, copy_temp_virtual_disk, flat_extent_path,
+ sparse_path):
+ self._test_create_virtual_disk_from_sparse_image(
+ copy_image, copy_temp_virtual_disk, flat_extent_path, sparse_path)
+
+ def _test_create_virtual_disk_from_sparse_image(
+ self, copy_image, copy_temp_virtual_disk, flat_extent_path,
+ sparse_path):
+ 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"
+
+ src_path = mock.Mock()
+ sparse_path.return_value = src_path
+ dest_path = mock.Mock()
+ flat_extent_path.return_value = dest_path
+
+ ret = self._driver._create_virtual_disk_from_sparse_image(
+ context, image_service, image_id, image_size_in_bytes, dc_ref,
+ ds_name, folder_path, disk_name)
+
+ copy_image.assert_called_once_with(
+ 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)
+ self.assertEqual(dest_path, ret)
@mock.patch.object(vmware_images, 'fetch_stream_optimized_image')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
fake_vm_create_spec = mock.sentinel.spec
fake_disk_type = 'thin'
vol_name = 'fake_volume name'
- fake_volume = {'name': vol_name, 'size': fake_volume_size,
+ vol_id = '12345'
+ fake_volume = {'name': vol_name,
+ 'id': vol_id,
+ 'size': fake_volume_size,
'volume_type_id': None}
cf = session.vim.client.factory
vm_import_spec = cf.create('ns0:VirtualMachineImportSpec')
"""Test vmdk._extend_vmdk_virtual_disk."""
self._test_extend_vmdk_virtual_disk(volume_ops)
- @mock.patch.object(vmware_images, 'fetch_flat_image')
- @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
- @mock.patch.object(VMDK_DRIVER, '_get_ds_name_flat_vmdk_path')
+ @mock.patch('cinder.openstack.common.uuidutils.generate_uuid')
+ @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
+ @mock.patch.object(VMDK_DRIVER, 'volumeops')
+ @mock.patch.object(VMDK_DRIVER,
+ '_create_virtual_disk_from_preallocated_image')
+ @mock.patch.object(VMDK_DRIVER, '_create_virtual_disk_from_sparse_image')
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.vmdk.VMwareEsxVmdkDriver._get_disk_type')
+ @mock.patch.object(VMDK_DRIVER, '_get_ds_name_folder_path')
@mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
- @mock.patch.object(VMDK_DRIVER, 'session')
+ def test_copy_image_to_volume_non_stream_optimized(
+ self, create_backing, get_ds_name_folder_path, get_disk_type,
+ create_disk_from_sparse_image, create_disk_from_preallocated_image,
+ vops, select_ds_for_volume, generate_uuid):
+ self._test_copy_image_to_volume_non_stream_optimized(
+ create_backing,
+ get_ds_name_folder_path,
+ get_disk_type,
+ create_disk_from_sparse_image,
+ create_disk_from_preallocated_image,
+ vops,
+ select_ds_for_volume,
+ generate_uuid)
+
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath')
+ @mock.patch.object(VMDK_DRIVER, '_copy_image')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
- def test_copy_image_to_volume_vmdk(self, volume_ops, session,
- _create_backing_in_inventory,
- _get_ds_name_flat_vmdk_path,
- _extend_vmdk_virtual_disk,
- fetch_flat_image):
- """Test copy_image_to_volume with an acceptable vmdk disk format."""
- self._test_copy_image_to_volume_vmdk(volume_ops, session,
- _create_backing_in_inventory,
- _get_ds_name_flat_vmdk_path,
- _extend_vmdk_virtual_disk,
- fetch_flat_image)
+ def test_create_virtual_disk_from_preallocated_image(
+ self, vops, copy_image, flat_extent_path):
+ self._test_create_virtual_disk_from_preallocated_image(
+ vops, copy_image, flat_extent_path)
+
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.volumeops.'
+ 'MonolithicSparseVirtualDiskPath')
+ @mock.patch(
+ 'cinder.volume.drivers.vmware.volumeops.FlatExtentVirtualDiskPath')
+ @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk')
+ @mock.patch.object(VMDK_DRIVER, '_copy_image')
+ def test_create_virtual_disk_from_sparse_image(
+ self, copy_image, copy_temp_virtual_disk, flat_extent_path,
+ sparse_path):
+ self._test_create_virtual_disk_from_sparse_image(
+ copy_image, copy_temp_virtual_disk, flat_extent_path, sparse_path)
@mock.patch.object(vmware_images, 'fetch_stream_optimized_image')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
summary.name,
None,
'ide')
+
+ vops.create_backing.reset_mock()
+ backing_name = "temp-vol"
+ create_params = {vmdk.CREATE_PARAM_BACKING_NAME: backing_name}
+ self._driver._create_backing(volume, host, create_params)
+
+ vops.create_backing.assert_called_once_with(backing_name,
+ units.Mi,
+ vmdk.THIN_VMDK_TYPE,
+ folder,
+ resource_pool,
+ host,
+ summary.name,
+ None,
+ 'lsiLogic')
+
+
+class ImageDiskTypeTest(test.TestCase):
+ """Unit tests for ImageDiskType."""
+
+ def test_is_valid(self):
+ self.assertTrue(vmdk.ImageDiskType.is_valid("thin"))
+ self.assertTrue(vmdk.ImageDiskType.is_valid("preallocated"))
+ self.assertTrue(vmdk.ImageDiskType.is_valid("streamOptimized"))
+ self.assertTrue(vmdk.ImageDiskType.is_valid("sparse"))
+ self.assertFalse(vmdk.ImageDiskType.is_valid("thick"))
+
+ def test_validate(self):
+ vmdk.ImageDiskType.validate("thin")
+ vmdk.ImageDiskType.validate("preallocated")
+ vmdk.ImageDiskType.validate("streamOptimized")
+ vmdk.ImageDiskType.validate("sparse")
+ self.assertRaises(exception.ImageUnacceptable,
+ vmdk.ImageDiskType.validate,
+ "thick")
from cinder.openstack.common.gettextutils import _
from cinder.openstack.common import log as logging
from cinder.openstack.common import units
+from cinder.openstack.common import uuidutils
from cinder.volume import driver
from cinder.volume.drivers.vmware import api
from cinder.volume.drivers.vmware import error_util
CREATE_PARAM_ADAPTER_TYPE = 'adapter_type'
CREATE_PARAM_DISK_LESS = 'disk_less'
+CREATE_PARAM_BACKING_NAME = 'name'
vmdk_opts = [
cfg.StrOpt('vmware_host_ip',
LOG.debug("Invalid spec value: %s specified." % spec_value)
+class ImageDiskType:
+ """Supported disk types in images."""
+
+ PREALLOCATED = "preallocated"
+ SPARSE = "sparse"
+ STREAM_OPTIMIZED = "streamOptimized"
+ THIN = "thin"
+
+ @staticmethod
+ def is_valid(extra_spec_disk_type):
+ """Check if the given disk type in extra_spec is valid.
+
+ :param extra_spec_disk_type: disk type to check
+ :return: True if valid
+ """
+ return extra_spec_disk_type in [ImageDiskType.PREALLOCATED,
+ ImageDiskType.SPARSE,
+ ImageDiskType.STREAM_OPTIMIZED,
+ ImageDiskType.THIN]
+
+ @staticmethod
+ def validate(extra_spec_disk_type):
+ """Validate the given disk type in extra_spec.
+
+ This method throws ImageUnacceptable if the disk type is not a
+ supported one.
+
+ :param extra_spec_disk_type: disk type
+ :raises: ImageUnacceptable
+ """
+ if not ImageDiskType.is_valid(extra_spec_disk_type):
+ raise exception.ImageUnacceptable(_("Invalid disk type: %s.") %
+ extra_spec_disk_type)
+
+
class VMwareEsxVmdkDriver(driver.VolumeDriver):
"""Manage volumes on VMware ESX server."""
# check if a storage profile needs to be associated with the backing VM
profile_id = self._get_storage_profile_id(volume)
+ # Use volume name as the default backing name.
+ backing_name = create_params.get(CREATE_PARAM_BACKING_NAME,
+ volume['name'])
+
# default is a backing with single disk
disk_less = create_params.get(CREATE_PARAM_DISK_LESS, False)
if disk_less:
# create a disk-less backing-- disk can be added later; for e.g.,
# by copying an image
- return self.volumeops.create_backing_disk_less(volume['name'],
+ return self.volumeops.create_backing_disk_less(backing_name,
folder,
resource_pool,
host,
size_kb = volume['size'] * units.Mi
adapter_type = create_params.get(CREATE_PARAM_ADAPTER_TYPE,
'lsiLogic')
- return self.volumeops.create_backing(volume['name'],
+ return self.volumeops.create_backing(backing_name,
size_kb,
disk_type,
folder,
"""
self._create_volume_from_snapshot(volume, snapshot)
- def _get_ds_name_flat_vmdk_path(self, backing, vol_name):
- """Get datastore name and folder path of the flat VMDK of the backing.
+ def _get_ds_name_folder_path(self, backing):
+ """Get datastore name and folder path of the given backing.
:param backing: Reference to the backing entity
- :param vol_name: Name of the volume
- :return: datastore name and folder path of the VMDK of the backing
+ :return: datastore name and folder path of the backing
"""
- file_path_name = self.volumeops.get_path_name(backing)
+ vmdk_ds_file_path = self.volumeops.get_path_name(backing)
(datastore_name,
- folder_path, _) = volumeops.split_datastore_path(file_path_name)
- flat_vmdk_path = '%s%s-flat.vmdk' % (folder_path, vol_name)
- return (datastore_name, flat_vmdk_path)
+ folder_path, _) = volumeops.split_datastore_path(vmdk_ds_file_path)
+ return (datastore_name, folder_path)
@staticmethod
def _validate_disk_format(disk_format):
LOG.error(msg)
raise exception.ImageUnacceptable(msg)
- def _fetch_flat_image(self, context, volume, image_service, image_id,
- image_size):
- """Creates a volume from flat glance image.
+ def _copy_image(self, context, dc_ref, image_service, image_id,
+ image_size_in_bytes, ds_name, upload_file_path):
+ """Copy image (flat extent or sparse vmdk) to datastore."""
+
+ timeout = self.configuration.vmware_image_transfer_timeout_secs
+ host_ip = self.configuration.vmware_host_ip
+ cookies = self.session.vim.client.options.transport.cookiejar
+ dc_name = self.volumeops.get_entity_name(dc_ref)
+
+ LOG.debug("Copying image: %(image_id)s to %(path)s.",
+ {'image_id': image_id,
+ 'path': upload_file_path})
+ vmware_images.fetch_flat_image(context,
+ timeout,
+ image_service,
+ image_id,
+ image_size=image_size_in_bytes,
+ host=host_ip,
+ data_center_name=dc_name,
+ datastore_name=ds_name,
+ cookies=cookies,
+ file_path=upload_file_path)
+ LOG.debug("Image: %(image_id)s copied to %(path)s.",
+ {'image_id': image_id,
+ 'path': upload_file_path})
+
+ def _delete_temp_disk(self, descriptor_ds_file_path, dc_ref):
+ """Deletes a temporary virtual disk."""
+
+ LOG.debug("Deleting temporary disk: %s.", descriptor_ds_file_path)
+ try:
+ self.volumeops.delete_vmdk_file(
+ descriptor_ds_file_path, dc_ref)
+ except error_util.VimException:
+ LOG.warn(_("Error occurred while deleting temporary "
+ "disk: %s."),
+ descriptor_ds_file_path,
+ exc_info=True)
+
+ def _copy_temp_virtual_disk(self, dc_ref, src_path, dest_path):
+ """Clones a temporary virtual disk and deletes it finally."""
- Creates a backing for the volume under the ESX/VC server and
- copies the VMDK flat file from the glance image content.
- The method assumes glance image is VMDK disk format and its
- vmware_disktype is "sparse" or "preallocated", but not
- "streamOptimized"
+ try:
+ self.volumeops.copy_vmdk_file(
+ dc_ref, src_path.get_descriptor_ds_file_path(),
+ dest_path.get_descriptor_ds_file_path())
+ except error_util.VimException:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Error occurred while copying %(src)s to "
+ "%(dst)s."),
+ {'src': src_path.get_descriptor_ds_file_path(),
+ 'dst': dest_path.get_descriptor_ds_file_path()})
+ finally:
+ # Delete temporary disk.
+ self._delete_temp_disk(src_path.get_descriptor_ds_file_path(),
+ dc_ref)
+
+ def _create_virtual_disk_from_sparse_image(
+ self, context, image_service, image_id, image_size_in_bytes,
+ dc_ref, ds_name, folder_path, disk_name):
+ """Creates a flat extent virtual disk from sparse vmdk image."""
+
+ # Upload the image to a temporary virtual disk.
+ src_disk_name = uuidutils.generate_uuid()
+ src_path = volumeops.MonolithicSparseVirtualDiskPath(ds_name,
+ folder_path,
+ src_disk_name)
+
+ LOG.debug("Creating temporary virtual disk: %(path)s from sparse vmdk "
+ "image: %(image_id)s.",
+ {'path': src_path.get_descriptor_ds_file_path(),
+ 'image_id': image_id})
+ self._copy_image(context, dc_ref, image_service, image_id,
+ image_size_in_bytes, ds_name,
+ src_path.get_descriptor_file_path())
+
+ # Copy sparse disk to create a flat extent virtual disk.
+ dest_path = volumeops.FlatExtentVirtualDiskPath(ds_name,
+ folder_path,
+ disk_name)
+ self._copy_temp_virtual_disk(dc_ref, src_path, 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):
+ """Creates virtual disk from an image which is a flat extent."""
+
+ path = volumeops.FlatExtentVirtualDiskPath(ds_name,
+ folder_path,
+ disk_name)
+ LOG.debug("Creating virtual disk: %(path)s from (flat extent) image: "
+ "%(image_id)s.",
+ {'path': path.get_descriptor_ds_file_path(),
+ 'image_id': image_id})
+
+ # We first create a descriptor with desired settings.
+ self.volumeops.create_flat_extent_virtual_disk_descriptor(
+ dc_ref, path, image_size_in_bytes / units.Ki, adapter_type,
+ EAGER_ZEROED_THICK_VMDK_TYPE)
+ # Upload the image and use it as the flat extent.
+ try:
+ self._copy_image(context, dc_ref, image_service, image_id,
+ image_size_in_bytes, ds_name,
+ path.get_flat_extent_file_path())
+ except Exception:
+ # Delete the descriptor.
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Error occurred while copying image: "
+ "%(image_id)s to %(path)s."),
+ {'path': path.get_descriptor_ds_file_path(),
+ 'image_id': image_id})
+ LOG.debug("Deleting descriptor: %s.",
+ path.get_descriptor_ds_file_path())
+ try:
+ self.volumeops.delete_file(
+ path.get_descriptor_ds_file_path(), dc_ref)
+ except error_util.VimException:
+ LOG.warn(_("Error occurred while deleting "
+ "descriptor: %s."),
+ path.get_descriptor_ds_file_path(),
+ exc_info=True)
+
+ LOG.debug("Created virtual disk: %s from flat extent image.",
+ path.get_descriptor_ds_file_path())
+ return path
+
+ def _check_disk_conversion(self, image_disk_type, extra_spec_disk_type):
+ """Check if disk type conversion is needed."""
+
+ if image_disk_type == ImageDiskType.SPARSE:
+ # We cannot reliably determine the destination disk type of a
+ # virtual disk copied from a sparse image.
+ return True
+ # Virtual disk created from flat extent is always of type
+ # eagerZeroedThick.
+ return not (volumeops.VirtualDiskType.get_virtual_disk_type(
+ extra_spec_disk_type) ==
+ volumeops.VirtualDiskType.EAGER_ZEROED_THICK)
+
+ def _delete_temp_backing(self, backing):
+ """Deletes temporary backing."""
+
+ LOG.debug("Deleting backing: %s.", backing)
+ try:
+ self.volumeops.delete_backing(backing)
+ except error_util.VimException:
+ LOG.warn(_("Error occurred while deleting backing: %s."),
+ backing,
+ exc_info=True)
+
+ def _create_volume_from_non_stream_optimized_image(
+ self, context, volume, image_service, image_id,
+ image_size_in_bytes, adapter_type, image_disk_type):
+ """Creates backing VM from non-streamOptimized image.
+
+ First, we create a disk-less backing. Then we create a virtual disk
+ using the image which is then attached to the backing VM. Finally, the
+ backing VM is cloned if disk type conversion is required.
"""
- # Set volume size in GB from image metadata
- volume['size'] = float(image_size) / units.Gi
- # First create empty backing in the inventory
- backing = self._create_backing_in_inventory(volume)
+ # We should use the disk type in volume type for backing's virtual
+ # disk.
+ disk_type = VMwareEsxVmdkDriver._get_disk_type(volume)
+
+ # First, create a disk-less backing.
+ create_params = {CREATE_PARAM_DISK_LESS: True}
+
+ disk_conversion = self._check_disk_conversion(image_disk_type,
+ disk_type)
+ if disk_conversion:
+ # The initial backing is a temporary one and used as the source
+ # for clone operation.
+ disk_name = uuidutils.generate_uuid()
+ create_params[CREATE_PARAM_BACKING_NAME] = disk_name
+ else:
+ disk_name = volume['name']
+
+ LOG.debug("Creating disk-less backing for volume: %(id)s with params: "
+ "%(param)s.",
+ {'id': volume['id'],
+ 'param': create_params})
+ backing = self._create_backing_in_inventory(volume, create_params)
try:
- (datastore_name,
- flat_vmdk_path) = self._get_ds_name_flat_vmdk_path(backing,
- volume['name'])
+ # Find the backing's datacenter, host, datastore and folder.
+ (ds_name, folder_path) = self._get_ds_name_folder_path(backing)
host = self.volumeops.get_host(backing)
- datacenter = self.volumeops.get_dc(host)
- datacenter_name = self.volumeops.get_entity_name(datacenter)
- flat_vmdk_ds_path = '[%s] %s' % (datastore_name, flat_vmdk_path)
- # Delete the *-flat.vmdk file within the backing
- self.volumeops.delete_file(flat_vmdk_ds_path, datacenter)
+ dc_ref = self.volumeops.get_dc(host)
- # copy over image from glance into *-flat.vmdk
- timeout = self.configuration.vmware_image_transfer_timeout_secs
- host_ip = self.configuration.vmware_host_ip
- cookies = self.session.vim.client.options.transport.cookiejar
- LOG.debug("Fetching glance image: %(id)s to server: %(host)s." %
- {'id': image_id, 'host': host_ip})
- vmware_images.fetch_flat_image(context, timeout, image_service,
- image_id, image_size=image_size,
- host=host_ip,
- data_center_name=datacenter_name,
- datastore_name=datastore_name,
- cookies=cookies,
- file_path=flat_vmdk_path)
- LOG.info(_("Done copying image: %(id)s to volume: %(vol)s.") %
- {'id': image_id, 'vol': volume['name']})
- except Exception as excep:
- err_msg = (_("Exception in copy_image_to_volume: "
- "%(excep)s. Deleting the backing: "
- "%(back)s.") % {'excep': excep, 'back': backing})
- # delete the backing
- self.volumeops.delete_backing(backing)
- raise exception.VolumeBackendAPIException(data=err_msg)
+ vmdk_path = None
+ attached = False
+
+ # Create flat extent virtual disk from the image.
+ if image_disk_type == ImageDiskType.SPARSE:
+ # Monolithic sparse image has embedded descriptor.
+ vmdk_path = self._create_virtual_disk_from_sparse_image(
+ context, image_service, image_id, image_size_in_bytes,
+ dc_ref, ds_name, folder_path, disk_name)
+ else:
+ # The image is just a flat extent.
+ vmdk_path = self._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)
+
+ # Attach the virtual disk to the backing.
+ LOG.debug("Attaching virtual disk: %(path)s to backing: "
+ "%(backing)s.",
+ {'path': vmdk_path.get_descriptor_ds_file_path(),
+ 'backing': backing})
+
+ self.volumeops.attach_disk_to_backing(
+ backing, image_size_in_bytes / units.Ki, disk_type,
+ adapter_type, vmdk_path.get_descriptor_ds_file_path())
+ attached = True
+
+ if disk_conversion:
+ # Clone the temporary backing for disk type conversion.
+ (host, rp, folder, summary) = self._select_ds_for_volume(
+ volume)
+ datastore = summary.datastore
+ LOG.debug("Cloning temporary backing: %s for disk type "
+ "conversion.", backing)
+ self.volumeops.clone_backing(volume['name'],
+ backing,
+ None,
+ volumeops.FULL_CLONE_TYPE,
+ datastore,
+ disk_type)
+ self._delete_temp_backing(backing)
+ except Exception:
+ # Delete backing and virtual disk created from image.
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Error occured while creating volume: %(id)s"
+ " from image: %(image_id)s."),
+ {'id': volume['id'],
+ 'image_id': image_id})
+ self._delete_temp_backing(backing)
+ # Delete virtual disk if exists and unattached.
+ if vmdk_path is not None and not attached:
+ self._delete_temp_disk(
+ vmdk_path.get_descriptor_ds_file_path(), dc_ref)
def _fetch_stream_optimized_image(self, context, volume, image_service,
image_id, image_size, adapter_type):
:param image_id: Glance image id
"""
LOG.debug("Copy glance image: %s to create new volume." % image_id)
- # Record the volume size specified by the user, if the size is input
- # from the API.
- volume_size_in_gb = volume['size']
+
# Verify glance image is vmdk disk format
metadata = image_service.show(context, image_id)
VMwareEsxVmdkDriver._validate_disk_format(metadata['disk_format'])
# Get the disk type, adapter type and size of vmdk image
- disk_type = 'preallocated'
- adapter_type = 'lsiLogic'
+ image_disk_type = ImageDiskType.PREALLOCATED
+ image_adapter_type = volumeops.VirtualDiskAdapterType.LSI_LOGIC
image_size_in_bytes = metadata['size']
properties = metadata['properties']
if properties:
if 'vmware_disktype' in properties:
- disk_type = properties['vmware_disktype']
+ image_disk_type = properties['vmware_disktype']
if 'vmware_adaptertype' in properties:
- adapter_type = properties['vmware_adaptertype']
+ image_adapter_type = properties['vmware_adaptertype']
try:
- if disk_type == 'streamOptimized':
+ # validate disk and adapter types in image meta-data
+ volumeops.VirtualDiskAdapterType.validate(image_adapter_type)
+ ImageDiskType.validate(image_disk_type)
+
+ if image_disk_type == ImageDiskType.STREAM_OPTIMIZED:
self._fetch_stream_optimized_image(context, volume,
image_service, image_id,
image_size_in_bytes,
- adapter_type)
+ image_adapter_type)
else:
- self._fetch_flat_image(context, volume, image_service,
- image_id, image_size_in_bytes)
+ self._create_volume_from_non_stream_optimized_image(
+ context, volume, image_service, image_id,
+ image_size_in_bytes, image_adapter_type, image_disk_type)
except exception.CinderException as excep:
with excutils.save_and_reraise_exception():
LOG.exception(_("Exception in copying the image to the "
"volume: %s."), excep)
- # image_size_in_bytes is the capacity of the image in Bytes and
- # volume_size_in_gb is the size specified by the user, if the
- # size is input from the API.
- #
- # Convert the volume_size_in_gb into bytes and compare with the
- # image size. If the volume_size_in_gb is greater, meaning the
- # user specifies a larger volume, we need to extend/resize the vmdk
- # virtual disk to the capacity specified by the user.
- if volume_size_in_gb * units.Gi > image_size_in_bytes:
- self._extend_vmdk_virtual_disk(volume['name'], volume_size_in_gb)
+ LOG.debug("Volume: %(id)s created from image: %(image_id)s.",
+ {'id': volume['id'],
+ 'image_id': image_id})
+
+ # If the user-specified volume size is greater than image size, we need
+ # to extend the virtual disk to the capacity specified by the user.
+ volume_size_in_bytes = volume['size'] * units.Gi
+ if volume_size_in_bytes > image_size_in_bytes:
+ LOG.debug("Extending volume: %(id)s since the user specified "
+ "volume size (bytes): %(size)s is greater than image"
+ " size (bytes): %(image_size)s.",
+ {'id': volume['id'],
+ 'size': volume_size_in_bytes,
+ 'image_size': image_size_in_bytes})
+ self._extend_vmdk_virtual_disk(volume['name'], volume['size'])
def copy_volume_to_image(self, context, volume, image_service, image_meta):
"""Creates glance image from volume.