]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware: Implement vmdk extend_volume
authorVincent Hou <sbhou@cn.ibm.com>
Fri, 7 Mar 2014 16:55:34 +0000 (11:55 -0500)
committerVincent Hou <sbhou@cn.ibm.com>
Mon, 17 Mar 2014 05:34:46 +0000 (01:34 -0400)
Add the API implementation of extend_volume for the VMware
vmdk driver.

Change-Id: Idf09c9e9cf015c78c1c5e91c05b897e3e9b7c006
Closes-Bug: #1232172

cinder/tests/test_vmware_vmdk.py
cinder/volume/drivers/vmware/vmdk.py

index 7031a2bbf986e6ed365223ed0b926e4b4ea1a65c..0864a66dc9285b3b422c967a811d58021a3edeb1 100644 (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)
index dbd31f4972ae877117d14b8bf150d8d3f12577d0..5f85615273370345f86fda4671a587be1794a3ab 100644 (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."""