From 5eddb2e2245e9c42f458de201ade166b727ee8f4 Mon Sep 17 00:00:00 2001 From: Vincent Hou Date: Fri, 7 Mar 2014 11:55:34 -0500 Subject: [PATCH] VMware: Implement vmdk extend_volume Add the API implementation of extend_volume for the VMware vmdk driver. Change-Id: Idf09c9e9cf015c78c1c5e91c05b897e3e9b7c006 Closes-Bug: #1232172 --- cinder/tests/test_vmware_vmdk.py | 95 +++++++++++++++++++++++++++- cinder/volume/drivers/vmware/vmdk.py | 93 +++++++++++++++++++++------ 2 files changed, 168 insertions(+), 20 deletions(-) diff --git a/cinder/tests/test_vmware_vmdk.py b/cinder/tests/test_vmware_vmdk.py index 7031a2bbf..0864a66dc 100644 --- a/cinder/tests/test_vmware_vmdk.py +++ b/cinder/tests/test_vmware_vmdk.py @@ -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) diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index dbd31f497..5f8561527 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -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.""" -- 2.45.2