Merge "VMware: Volume from non-streamOptimized image"

This commit is contained in:
Jenkins 2014-08-09 02:30:37 +00:00 committed by Gerrit Code Review
commit 057d9feeb8
2 changed files with 593 additions and 174 deletions

View File

@ -931,98 +931,209 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
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)
@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_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'}}
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
backing = mock.Mock()
# 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'])
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
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)
ds_name = mock.Mock()
folder_path = mock.Mock()
get_ds_name_folder_path.return_value = (ds_name, folder_path)
# 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)
summary = mock.Mock()
select_ds_for_volume.return_value = (mock.sentinel.host,
mock.sentinel.rp,
mock.sentinel.folder,
summary)
# If fetch_flat_image raises an Exception, delete_backing
# will be called.
fetch_flat_image.side_effect = exception.CinderException
self.assertRaises(exception.CinderException,
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')
@ -1073,7 +1184,10 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
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')
@ -1872,23 +1986,51 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
"""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.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)
@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)
@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)
@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')
@ -1955,3 +2097,38 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
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")

View File

@ -32,6 +32,7 @@ from cinder.openstack.common import excutils
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
@ -49,6 +50,7 @@ EAGER_ZEROED_THICK_VMDK_TYPE = 'eagerZeroedThick'
CREATE_PARAM_ADAPTER_TYPE = 'adapter_type'
CREATE_PARAM_DISK_LESS = 'disk_less'
CREATE_PARAM_BACKING_NAME = 'name'
vmdk_opts = [
cfg.StrOpt('vmware_host_ip',
@ -138,6 +140,41 @@ def _get_volume_type_extra_spec(type_id, spec_key, possible_values=None,
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."""
@ -459,12 +496,16 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
# 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,
@ -476,7 +517,7 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
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,
@ -813,18 +854,16 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
"""
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):
@ -838,54 +877,249 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
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."""
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"
"""
# 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)
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."""
try:
(datastore_name,
flat_vmdk_path) = self._get_ds_name_flat_vmdk_path(backing,
volume['name'])
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)
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)
# 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
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)
raise exception.VolumeBackendAPIException(data=err_msg)
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.
"""
# 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:
# 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)
dc_ref = self.volumeops.get_dc(host)
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):
@ -1003,48 +1237,56 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
: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.