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:
parent
d1b95a6e6f
commit
5eddb2e224
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
|
Loading…
Reference in New Issue