VMware: Implement vmdk extend_volume

Add the API implementation of extend_volume for the VMware
vmdk driver.

Change-Id: Idf09c9e9cf015c78c1c5e91c05b897e3e9b7c006
Closes-Bug: #1232172
This commit is contained in:
Vincent Hou 2014-03-07 11:55:34 -05:00
parent d1b95a6e6f
commit 5eddb2e224
2 changed files with 168 additions and 20 deletions

View File

@ -830,6 +830,90 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
src_vmdk,
fake_size)
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_volume(self, volume_ops, _extend_virtual_disk,
_select_ds_for_volume):
"""Test extend_volume."""
self._test_extend_volume(volume_ops, _extend_virtual_disk,
_select_ds_for_volume)
def _test_extend_volume(self, volume_ops, _extend_virtual_disk,
_select_ds_for_volume):
fake_name = u'volume-00000001'
new_size = '21'
fake_size = '20'
fake_vol = {'project_id': 'testprjid', 'name': fake_name,
'size': fake_size,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
fake_host = mock.sentinel.host
fake_rp = mock.sentinel.rp
fake_folder = mock.sentinel.folder
fake_summary = mock.Mock(spec=object)
fake_summary.datastore = mock.sentinel.datastore
fake_summary.name = 'fake_name'
fake_backing = mock.sentinel.backing
volume_ops.get_backing.return_value = fake_backing
# If there is enough space in the datastore, where the volume is
# located, then the rest of this method will not be called.
self._driver.extend_volume(fake_vol, new_size)
_extend_virtual_disk.assert_called_with(fake_name, new_size)
self.assertFalse(_select_ds_for_volume.called)
self.assertFalse(volume_ops.get_backing.called)
self.assertFalse(volume_ops.relocate_backing.called)
self.assertFalse(volume_ops.move_backing_to_folder.called)
# If there is not enough space in the datastore, where the volume is
# located, then the rest of this method will be called. The first time
# _extend_virtual_disk is called, VimFaultException is raised. The
# second time it is called, there is no exception.
_extend_virtual_disk.reset_mock()
_extend_virtual_disk.side_effect = [error_util.
VimFaultException(mock.Mock(),
'Error'), None]
# When _select_ds_for_volume raises no exception.
_select_ds_for_volume.return_value = (fake_host, fake_rp,
fake_folder, fake_summary)
self._driver.extend_volume(fake_vol, new_size)
_select_ds_for_volume.assert_called_with(new_size)
volume_ops.get_backing.assert_called_with(fake_name)
volume_ops.relocate_backing.assert_called_with(fake_backing,
fake_summary.datastore,
fake_rp,
fake_host)
_extend_virtual_disk.assert_called_with(fake_name, new_size)
volume_ops.move_backing_to_folder.assert_called_with(fake_backing,
fake_folder)
# If get_backing raises error_util.VimException,
# this exception will be caught for volume extend.
_extend_virtual_disk.reset_mock()
_extend_virtual_disk.side_effect = [error_util.
VimFaultException(mock.Mock(),
'Error'), None]
volume_ops.get_backing.side_effect = error_util.VimException('Error')
self.assertRaises(error_util.VimException, self._driver.extend_volume,
fake_vol, new_size)
# If _select_ds_for_volume raised an exception, the rest code will
# not be called.
_extend_virtual_disk.reset_mock()
volume_ops.get_backing.reset_mock()
volume_ops.relocate_backing.reset_mock()
volume_ops.move_backing_to_folder.reset_mock()
_extend_virtual_disk.side_effect = [error_util.
VimFaultException(mock.Mock(),
'Error'), None]
_select_ds_for_volume.side_effect = error_util.VimException('Error')
self.assertRaises(error_util.VimException, self._driver.extend_volume,
fake_vol, new_size)
_extend_virtual_disk.assert_called_once_with(fake_name, new_size)
self.assertFalse(volume_ops.get_backing.called)
self.assertFalse(volume_ops.relocate_backing.called)
self.assertFalse(volume_ops.move_backing_to_folder.called)
def test_copy_image_to_volume_non_vmdk(self):
"""Test copy_image_to_volume for a non-vmdk disk format."""
fake_context = mock.sentinel.context
@ -993,7 +1077,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
# If _select_ds_for_volume raises an exception, _get_create_spec
# will not be called.
_select_ds_for_volume.side_effect = error_util.VimException('Error')
self.assertRaises(error_util.VimException,
self.assertRaises(exception.VolumeBackendAPIException,
self._driver.copy_image_to_volume,
fake_context, fake_volume,
image_service, fake_image_id)
@ -1810,3 +1894,12 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
_select_ds_for_volume,
_extend_virtual_disk,
fetch_optimized_image)
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_volume(self, volume_ops, _extend_virtual_disk,
_select_ds_for_volume):
"""Test extend_volume."""
self._test_extend_volume(volume_ops, _extend_virtual_disk,
_select_ds_for_volume)

View File

@ -856,12 +856,12 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
LOG.info(_("Done copying image: %(id)s to volume: %(vol)s.") %
{'id': image_id, 'vol': volume['name']})
except Exception as excep:
LOG.exception(_("Exception in copy_image_to_volume: %(excep)s. "
"Deleting the backing: %(back)s.") %
{'excep': excep, 'back': backing})
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 excep
raise exception.VolumeBackendAPIException(data=err_msg)
def _fetch_stream_optimized_image(self, context, volume, image_service,
image_id, image_size):
@ -876,8 +876,9 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
# find host in which to create the volume
(host, rp, folder, summary) = self._select_ds_for_volume(volume)
except error_util.VimException as excep:
LOG.exception(_("Exception in _select_ds_for_volume: %s.") % excep)
raise excep
err_msg = (_("Exception in _select_ds_for_volume: "
"%s."), excep)
raise exception.VolumeBackendAPIException(data=err_msg)
size_gb = volume['size']
LOG.debug(_("Selected datastore %(ds)s for new volume of size "
@ -915,13 +916,14 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
vm_import_spec,
image_size=image_size)
except exception.CinderException as excep:
LOG.exception(_("Exception in copy_image_to_volume: %s.") % excep)
backing = self.volumeops.get_backing(volume['name'])
if backing:
LOG.exception(_("Deleting the backing: %s") % backing)
# delete the backing
self.volumeops.delete_backing(backing)
raise excep
with excutils.save_and_reraise_exception():
LOG.exception(_("Exception in copy_image_to_volume: %s."),
excep)
backing = self.volumeops.get_backing(volume['name'])
if backing:
LOG.exception(_("Deleting the backing: %s") % backing)
# delete the backing
self.volumeops.delete_backing(backing)
LOG.info(_("Done copying image: %(id)s to volume: %(vol)s.") %
{'id': image_id, 'vol': volume['name']})
@ -988,12 +990,19 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
if properties and 'vmware_disktype' in properties:
disk_type = properties['vmware_disktype']
if disk_type == 'streamOptimized':
self._fetch_stream_optimized_image(context, volume, image_service,
image_id, image_size_in_bytes)
else:
self._fetch_flat_image(context, volume, image_service, image_id,
image_size_in_bytes)
try:
if disk_type == 'streamOptimized':
self._fetch_stream_optimized_image(context, volume,
image_service, image_id,
image_size_in_bytes)
else:
self._fetch_flat_image(context, volume, image_service,
image_id, image_size_in_bytes)
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.
@ -1053,6 +1062,52 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
LOG.info(_("Done copying volume %(vol)s to a new image %(img)s") %
{'vol': volume['name'], 'img': image_meta['name']})
def extend_volume(self, volume, new_size):
"""Extend vmdk to new_size.
Extends the vmdk backing to new volume size. First try to extend in
place on the same datastore. If that fails, try to relocate the volume
to a different datastore that can accommodate the new_size'd volume.
:param volume: dictionary describing the existing 'available' volume
:param new_size: new size in GB to extend this volume to
"""
vol_name = volume['name']
# try extending vmdk in place
try:
self._extend_vmdk_virtual_disk(vol_name, new_size)
LOG.info(_("Done extending volume %(vol)s to size %(size)s GB.") %
{'vol': vol_name, 'size': new_size})
return
except error_util.VimFaultException:
LOG.info(_("Relocating volume %s vmdk to a different "
"datastore since trying to extend vmdk file "
"in place failed."), vol_name)
# If in place extend fails, then try to relocate the volume
try:
(host, rp, folder, summary) = self._select_ds_for_volume(new_size)
except error_util.VimException:
with excutils.save_and_reraise_exception():
LOG.exception(_("Not able to find a different datastore to "
"place the extended volume %s."), vol_name)
LOG.info(_("Selected datastore %(ds)s to place extended volume of "
"size %(size)s GB.") % {'ds': summary.name,
'size': new_size})
try:
backing = self.volumeops.get_backing(vol_name)
self.volumeops.relocate_backing(backing, summary.datastore, rp,
host)
self._extend_vmdk_virtual_disk(vol_name, new_size)
self.volumeops.move_backing_to_folder(backing, folder)
except error_util.VimException:
with excutils.save_and_reraise_exception():
LOG.exception(_("Not able to relocate volume %s for "
"extending."), vol_name)
LOG.info(_("Done extending volume %(vol)s to size %(size)s GB.") %
{'vol': vol_name, 'size': new_size})
class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
"""Manage volumes on VMware VC server."""