]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware: Take the volume size from the user input
authorVincent Hou <sbhou@cn.ibm.com>
Wed, 4 Dec 2013 08:51:20 +0000 (03:51 -0500)
committerVincent Hou <sbhou@cn.ibm.com>
Tue, 11 Mar 2014 01:50:24 +0000 (21:50 -0400)
When we create a volume from an image or a snapshot, we need to
take the size of the volume from the user input and validate whether
the size is appropriate instead of taking the image size as the
volume size directly.

Change-Id: If09933d8ffa989c4dacc0860c19ea332bc21092a
Closes-Bug: #1237557

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

index 677e3fcba5043ef5fe96870214de7bd76c70e966..7031a2bbf986e6ed365223ed0b926e4b4ea1a65c 100644 (file)
@@ -140,6 +140,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
     TASK_POLL_INTERVAL = 5.0
     IMG_TX_TIMEOUT = 10
     MAX_OBJECTS = 100
+    VMDK_DRIVER = vmdk.VMwareEsxVmdkDriver
 
     def setUp(self):
         super(VMwareEsxVmdkDriverTestCase, self).setUp()
@@ -682,34 +683,6 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         driver._verify_volume_creation.assert_called_once_with(volume)
         mock_vops.get_backing.assert_called_once_with('src_snapshot_name')
 
-    def test_clone_backing_by_copying(self):
-        """Test _clone_backing_by_copying."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        volume = FakeObject()
-        src_vmdk_path = "[datastore] src_vm/src_vm.vmdk"
-        new_vmdk_path = "[datastore] dest_vm/dest_vm.vmdk"
-        backing = FakeMor('VirtualMachine', 'my_back')
-        m.StubOutWithMock(self._driver, '_create_backing_in_inventory')
-        mux = self._driver._create_backing_in_inventory(volume)
-        mux.AndReturn(backing)
-        m.StubOutWithMock(self._volumeops, 'get_vmdk_path')
-        self._volumeops.get_vmdk_path(backing).AndReturn(new_vmdk_path)
-        m.StubOutWithMock(self._volumeops, 'get_dc')
-        datacenter = FakeMor('Datacenter', 'my_dc')
-        self._volumeops.get_dc(backing).AndReturn(datacenter)
-        m.StubOutWithMock(self._volumeops, 'delete_vmdk_file')
-        self._volumeops.delete_vmdk_file(new_vmdk_path, datacenter)
-        m.StubOutWithMock(self._volumeops, 'copy_vmdk_file')
-        self._volumeops.copy_vmdk_file(datacenter, src_vmdk_path,
-                                       new_vmdk_path)
-
-        m.ReplayAll()
-        self._driver._clone_backing_by_copying(volume, src_vmdk_path)
-        m.UnsetStubs()
-        m.VerifyAll()
-
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareEsxVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
     def test_create_cloned_volume_with_backing(self, mock_vops):
@@ -717,13 +690,14 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         mock_vops = mock_vops.return_value
         driver = self._driver
         volume = mock.sentinel.volume
-        src_vref = {'name': 'src_snapshot_name'}
+        fake_size = 1
+        src_vref = {'name': 'src_snapshot_name', 'size': fake_size}
         backing = mock.sentinel.backing
         driver._verify_volume_creation = mock.MagicMock()
         mock_vops.get_backing.return_value = backing
-        src_vmdk_path = "[datastore] src_vm/src_vm.vmdk"
-        mock_vops.get_vmdk_path.return_value = src_vmdk_path
-        driver._clone_backing_by_copying = mock.MagicMock()
+        src_vmdk = "[datastore] src_vm/src_vm.vmdk"
+        mock_vops.get_vmdk_path.return_value = src_vmdk
+        driver._create_backing_by_copying = mock.MagicMock()
 
         # invoke the create_volume_from_snapshot api
         driver.create_cloned_volume(volume, src_vref)
@@ -732,8 +706,57 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         driver._verify_volume_creation.assert_called_once_with(volume)
         mock_vops.get_backing.assert_called_once_with('src_snapshot_name')
         mock_vops.get_vmdk_path.assert_called_once_with(backing)
-        driver._clone_backing_by_copying.assert_called_once_with(volume,
-                                                                 src_vmdk_path)
+        driver._create_backing_by_copying.assert_called_once_with(volume,
+                                                                  src_vmdk,
+                                                                  fake_size)
+
+    @mock.patch.object(VMDK_DRIVER, '_extend_volumeops_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_create_backing_by_copying(self, volumeops, create_backing,
+                                       _extend_virtual_disk):
+        self._test_create_backing_by_copying(volumeops, create_backing,
+                                             _extend_virtual_disk)
+
+    def _test_create_backing_by_copying(self, volumeops, create_backing,
+                                        _extend_virtual_disk):
+        """Test _create_backing_by_copying."""
+        fake_volume = {'size': 2, 'name': 'fake_volume-0000000000001'}
+        fake_size = 1
+        fake_src_vmdk_path = "[datastore] src_vm/src_vm.vmdk"
+        fake_backing = mock.sentinel.backing
+        fake_vmdk_path = mock.sentinel.path
+        #"[datastore] dest_vm/dest_vm.vmdk"
+        fake_dc = mock.sentinel.datacenter
+
+        create_backing.return_value = fake_backing
+        volumeops.get_vmdk_path.return_value = fake_vmdk_path
+        volumeops.get_dc.return_value = fake_dc
+
+        # Test with fake_volume['size'] greater than fake_size
+        self._driver._create_backing_by_copying(fake_volume,
+                                                fake_src_vmdk_path,
+                                                fake_size)
+        create_backing.assert_called_once_with(fake_volume)
+        volumeops.get_vmdk_path.assert_called_once_with(fake_backing)
+        volumeops.get_dc.assert_called_once_with(fake_backing)
+        volumeops.delete_vmdk_file.assert_called_once_with(fake_vmdk_path,
+                                                           fake_dc)
+        volumeops.copy_vmdk_file.assert_called_once_with(fake_dc,
+                                                         fake_src_vmdk_path,
+                                                         fake_vmdk_path)
+        _extend_virtual_disk.assert_called_once_with(fake_volume['size'],
+                                                     fake_vmdk_path,
+                                                     fake_dc)
+
+        # Reset all the mocks and test with fake_volume['size']
+        # not greater than fake_size
+        _extend_virtual_disk.reset_mock()
+        fake_size = 2
+        self._driver._create_backing_by_copying(fake_volume,
+                                                fake_src_vmdk_path,
+                                                fake_size)
+        self.assertFalse(_extend_virtual_disk.called)
 
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareEsxVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
@@ -782,15 +805,17 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         mock_vops = mock_vops.return_value
         driver = self._driver
         volume = {'volume_type_id': None, 'name': 'mock_vol'}
-        snapshot = {'volume_name': 'mock_vol', 'name': 'mock_snap'}
+        snapshot = {'volume_name': 'mock_vol', 'name': 'mock_snap',
+                    'volume_size': 1}
+        fake_size = snapshot['volume_size']
         backing = mock.sentinel.backing
         snap_moref = mock.sentinel.snap_moref
         driver._verify_volume_creation = mock.MagicMock()
         mock_vops.get_backing.return_value = backing
         mock_vops.get_snapshot.return_value = snap_moref
-        src_vmdk_path = "[datastore] src_vm/src_vm-001.vmdk"
-        mock_vops.get_vmdk_path.return_value = src_vmdk_path
-        driver._clone_backing_by_copying = mock.MagicMock()
+        src_vmdk = "[datastore] src_vm/src_vm-001.vmdk"
+        mock_vops.get_vmdk_path.return_value = src_vmdk
+        driver._create_backing_by_copying = mock.MagicMock()
 
         # invoke the create_volume_from_snapshot api
         driver.create_volume_from_snapshot(volume, snapshot)
@@ -801,161 +826,228 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         mock_vops.get_snapshot.assert_called_once_with(backing,
                                                        'mock_snap')
         mock_vops.get_vmdk_path.assert_called_once_with(snap_moref)
-        driver._clone_backing_by_copying.assert_called_once_with(volume,
-                                                                 src_vmdk_path)
+        driver._create_backing_by_copying.assert_called_once_with(volume,
+                                                                  src_vmdk,
+                                                                  fake_size)
 
     def test_copy_image_to_volume_non_vmdk(self):
         """Test copy_image_to_volume for a non-vmdk disk format."""
-        m = self.mox
-        image_id = 'image-123456789'
-        image_meta = FakeObject()
-        image_meta['disk_format'] = 'novmdk'
-        image_service = m.CreateMock(glance.GlanceImageService)
-        image_service.show(mox.IgnoreArg(), image_id).AndReturn(image_meta)
-
-        m.ReplayAll()
+        fake_context = mock.sentinel.context
+        fake_image_id = 'image-123456789'
+        fake_image_meta = {'disk_format': 'novmdk'}
+        image_service = mock.Mock()
+        image_service.show.return_value = fake_image_meta
+        fake_volume = {'name': 'fake_name', 'size': 1}
         self.assertRaises(exception.ImageUnacceptable,
                           self._driver.copy_image_to_volume,
-                          mox.IgnoreArg(), mox.IgnoreArg(),
-                          image_service, image_id)
-        m.UnsetStubs()
-        m.VerifyAll()
-
-    def test_copy_image_to_volume_vmdk(self):
+                          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.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."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'session')
-        self._driver.session = self._session
-        m.StubOutWithMock(api.VMwareAPISession, 'vim')
-        self._session.vim = self._vim
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-
-        image_id = 'image-id'
-        image_meta = FakeObject()
-        image_meta['disk_format'] = 'vmdk'
-        image_meta['size'] = 1 * units.MiB
-        image_meta['properties'] = {'vmware_disktype': 'preallocated'}
-        image_service = m.CreateMock(glance.GlanceImageService)
-        image_service.show(mox.IgnoreArg(), image_id).AndReturn(image_meta)
-        volume = FakeObject()
-        vol_name = 'volume name'
-        volume['name'] = vol_name
-        backing = FakeMor('VirtualMachine', 'my_vm')
-        m.StubOutWithMock(self._driver, '_create_backing_in_inventory')
-        self._driver._create_backing_in_inventory(volume).AndReturn(backing)
-        datastore_name = 'datastore1'
+        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)
+
+    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.GiB,
+                           'properties': {'vmware_disktype': 'preallocated'}}
+        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'
-        m.StubOutWithMock(self._driver, '_get_ds_name_flat_vmdk_path')
-        moxed = self._driver._get_ds_name_flat_vmdk_path(mox.IgnoreArg(),
-                                                         vol_name)
-        moxed.AndReturn((datastore_name, flat_vmdk_path))
-        host = FakeMor('Host', 'my_host')
-        m.StubOutWithMock(self._volumeops, 'get_host')
-        self._volumeops.get_host(backing).AndReturn(host)
-        datacenter = FakeMor('Datacenter', 'my_datacenter')
-        m.StubOutWithMock(self._volumeops, 'get_dc')
-        self._volumeops.get_dc(host).AndReturn(datacenter)
-        datacenter_name = 'my-datacenter'
-        m.StubOutWithMock(self._volumeops, 'get_entity_name')
-        self._volumeops.get_entity_name(datacenter).AndReturn(datacenter_name)
-        flat_path = '[%s] %s' % (datastore_name, flat_vmdk_path)
-        m.StubOutWithMock(self._volumeops, 'delete_file')
-        self._volumeops.delete_file(flat_path, datacenter)
-        client = FakeObject()
-        client.options = FakeObject()
-        client.options.transport = FakeObject()
-        cookies = FakeObject()
-        client.options.transport.cookiejar = cookies
-        m.StubOutWithMock(self._vim.__class__, 'client')
-        self._vim.client = client
-        m.StubOutWithMock(vmware_images, 'fetch_flat_image')
+        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
-        vmware_images.fetch_flat_image(mox.IgnoreArg(), timeout, image_service,
-                                       image_id, image_size=image_meta['size'],
-                                       host=self.IP,
-                                       data_center_name=datacenter_name,
-                                       datastore_name=datastore_name,
-                                       cookies=cookies,
-                                       file_path=flat_vmdk_path)
 
-        m.ReplayAll()
-        self._driver.copy_image_to_volume(mox.IgnoreArg(), volume,
-                                          image_service, image_id)
-        m.UnsetStubs()
-        m.VerifyAll()
-
-    def test_copy_image_to_volume_stream_optimized(self):
+        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
+
+        # 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'])
+
+        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)
+
+        # 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)
+
+        # If fetch_flat_image raises an Exception, delete_backing
+        # will be called.
+        fetch_flat_image.side_effect = exception.CinderException
+        self.assertRaises(exception.CinderException,
+                          self._driver.copy_image_to_volume,
+                          fake_context, fake_volume,
+                          image_service, fake_image_id)
+        volume_ops.delete_backing.assert_called_with(fake_backing)
+
+    @mock.patch.object(vmware_images, 'fetch_stream_optimized_image')
+    @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
+    @mock.patch.object(VMDK_DRIVER, 'session')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_copy_image_to_volume_stream_optimized(self, volumeops,
+                                                   session,
+                                                   _select_ds_for_volume,
+                                                   _extend_virtual_disk,
+                                                   fetch_optimized_image):
         """Test copy_image_to_volume.
 
         Test with an acceptable vmdk disk format and streamOptimized disk type.
         """
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'session')
-        self._driver.session = self._session
-        m.StubOutWithMock(api.VMwareAPISession, 'vim')
-        self._session.vim = self._vim
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-
-        image_id = 'image-id'
+        self._test_copy_image_to_volume_stream_optimized(volumeops,
+                                                         session,
+                                                         _select_ds_for_volume,
+                                                         _extend_virtual_disk,
+                                                         fetch_optimized_image)
+
+    def _test_copy_image_to_volume_stream_optimized(self, volumeops,
+                                                    session,
+                                                    _select_ds_for_volume,
+                                                    _extend_virtual_disk,
+                                                    fetch_optimized_image):
+        fake_context = mock.Mock()
+        fake_backing = mock.sentinel.backing
+        fake_image_id = 'image-id'
         size = 5 * units.GiB
-        size_kb = float(size) / units.KiB
         size_gb = float(size) / units.GiB
-        # image_service.show call
-        image_meta = FakeObject()
-        image_meta['disk_format'] = 'vmdk'
-        image_meta['size'] = size
-        image_meta['properties'] = {'vmware_disktype': 'streamOptimized'}
-        image_service = m.CreateMock(glance.GlanceImageService)
-        image_service.show(mox.IgnoreArg(), image_id).AndReturn(image_meta)
-        # _select_ds_for_volume call
-        (host, rp, folder, summary) = (FakeObject(), FakeObject(),
-                                       FakeObject(), FakeObject())
-        summary.name = "datastore-1"
-        vol_name = 'volume name'
-        volume = FakeObject()
-        volume['name'] = vol_name
-        volume['size'] = size_gb
-        volume['volume_type_id'] = None  # _get_disk_type will return 'thin'
-        disk_type = 'thin'
-        m.StubOutWithMock(self._driver, '_select_ds_for_volume')
-        self._driver._select_ds_for_volume(volume).AndReturn((host, rp,
-                                                              folder,
-                                                              summary))
-
-        # _get_create_spec call
-        m.StubOutWithMock(self._volumeops, '_get_create_spec')
-        self._volumeops._get_create_spec(vol_name, 0, disk_type,
-                                         summary.name)
-
-        # vim.client.factory.create call
-        class FakeFactory(object):
-            def create(self, name):
-                return mox.MockAnything()
-
-        client = FakeObject()
-        client.factory = FakeFactory()
-        m.StubOutWithMock(self._vim.__class__, 'client')
-        self._vim.client = client
-        # fetch_stream_optimized_image call
+        fake_volume_size = 1 + size_gb
+        fake_image_meta = {'disk_format': 'vmdk', 'size': size,
+                           'properties': {'vmware_disktype':
+                                          'streamOptimized'}}
+        image_service = mock.Mock(glance.GlanceImageService)
+        fake_host = mock.sentinel.host
+        fake_rp = mock.sentinel.rp
+        fake_folder = mock.sentinel.folder
+        fake_summary = mock.sentinel.summary
+        fake_summary.name = "datastore-1"
+        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,
+                       'volume_type_id': None}
+        cf = session.vim.client.factory
+        vm_import_spec = cf.create('ns0:VirtualMachineImportSpec')
+        vm_import_spec.configSpec = fake_vm_create_spec
         timeout = self._config.vmware_image_transfer_timeout_secs
-        m.StubOutWithMock(vmware_images, 'fetch_stream_optimized_image')
-        vmware_images.fetch_stream_optimized_image(mox.IgnoreArg(), timeout,
-                                                   image_service, image_id,
-                                                   session=self._session,
-                                                   host=self.IP,
-                                                   resource_pool=rp,
-                                                   vm_folder=folder,
-                                                   vm_create_spec=
-                                                   mox.IgnoreArg(),
-                                                   image_size=size)
 
-        m.ReplayAll()
-        self._driver.copy_image_to_volume(mox.IgnoreArg(), volume,
-                                          image_service, image_id)
-        m.UnsetStubs()
-        m.VerifyAll()
+        image_service.show.return_value = fake_image_meta
+        volumeops._get_create_spec.return_value = fake_vm_create_spec
+        volumeops.get_backing.return_value = fake_backing
+
+        # 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._driver.copy_image_to_volume,
+                          fake_context, fake_volume,
+                          image_service, fake_image_id)
+        self.assertFalse(volumeops._get_create_spec.called)
+
+        # If the volume size is greater then than the image size,
+        # _extend_vmdk_virtual_disk will be called.
+        _select_ds_for_volume.side_effect = None
+        _select_ds_for_volume.return_value = (fake_host, fake_rp,
+                                              fake_folder, fake_summary)
+        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)
+        _select_ds_for_volume.assert_called_with(fake_volume)
+        volumeops._get_create_spec.assert_called_with(fake_volume['name'],
+                                                      0,
+                                                      fake_disk_type,
+                                                      fake_summary.name)
+        self.assertTrue(fetch_optimized_image.called)
+        fetch_optimized_image.assert_called_with(fake_context, timeout,
+                                                 image_service,
+                                                 fake_image_id,
+                                                 session=session,
+                                                 host=self.IP,
+                                                 resource_pool=fake_rp,
+                                                 vm_folder=fake_folder,
+                                                 vm_create_spec=
+                                                 vm_import_spec,
+                                                 image_size=size)
+        _extend_virtual_disk.assert_called_with(fake_volume['name'],
+                                                fake_volume_size)
+        self.assertFalse(volumeops.get_backing.called)
+        self.assertFalse(volumeops.delete_backing.called)
+
+        # If the volume size is not greater then than the image size,
+        # _extend_vmdk_virtual_disk will not be called.
+        fake_volume_size = size_gb
+        fake_volume['size'] = fake_volume_size
+        _extend_virtual_disk.reset_mock()
+        self._driver.copy_image_to_volume(fake_context, fake_volume,
+                                          image_service, fake_image_id)
+        self.assertFalse(_extend_virtual_disk.called)
+        self.assertFalse(volumeops.get_backing.called)
+        self.assertFalse(volumeops.delete_backing.called)
+
+        # If fetch_stream_optimized_image raises an exception,
+        # get_backing and delete_backing will be called.
+        fetch_optimized_image.side_effect = exception.CinderException
+        self.assertRaises(exception.CinderException,
+                          self._driver.copy_image_to_volume,
+                          fake_context, fake_volume,
+                          image_service, fake_image_id)
+        volumeops.get_backing.assert_called_with(fake_volume['name'])
+        volumeops.delete_backing.assert_called_with(fake_backing)
 
     def test_copy_volume_to_image_non_vmdk(self):
         """Test copy_volume_to_image for a non-vmdk disk format."""
@@ -1075,9 +1167,51 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
         m.UnsetStubs()
         m.VerifyAll()
 
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_extend_vmdk_virtual_disk(self, volume_ops):
+        """Test vmdk._extend_vmdk_virtual_disk."""
+        self._test_extend_vmdk_virtual_disk(volume_ops)
+
+    def _test_extend_vmdk_virtual_disk(self, volume_ops):
+        fake_backing = mock.sentinel.backing
+        fake_vmdk_path = "[datastore] dest_vm/dest_vm.vmdk"
+        fake_dc = mock.sentinel.datacenter
+        fake_name = 'fake_name'
+        fake_size = 7
+
+        # If the backing is None, get_vmdk_path and get_dc
+        # will not be called
+        volume_ops.get_backing.return_value = None
+        volume_ops.get_vmdk_path.return_value = fake_vmdk_path
+        volume_ops.get_dc.return_value = fake_dc
+        self._driver._extend_vmdk_virtual_disk(fake_name, fake_size)
+        volume_ops.get_backing.assert_called_once_with(fake_name)
+        self.assertFalse(volume_ops.get_vmdk_path.called)
+        self.assertFalse(volume_ops.get_dc.called)
+        self.assertFalse(volume_ops.extend_virtual_disk.called)
+
+        # Reset the mock and set the backing with a fake,
+        # all the mocks should be called.
+        volume_ops.get_backing.reset_mock()
+        volume_ops.get_backing.return_value = fake_backing
+        self._driver._extend_vmdk_virtual_disk(fake_name, fake_size)
+        volume_ops.get_vmdk_path.assert_called_once_with(fake_backing)
+        volume_ops.get_dc.assert_called_once_with(fake_backing)
+        volume_ops.extend_virtual_disk.assert_called_once_with(fake_size,
+                                                               fake_vmdk_path,
+                                                               fake_dc)
+
+        # Test the exceptional case for extend_virtual_disk
+        volume_ops.extend_virtual_disk.side_effect = error_util.VimException(
+            'VimException raised.')
+        self.assertRaises(error_util.VimException,
+                          self._driver._extend_vmdk_virtual_disk,
+                          fake_name, fake_size)
+
 
 class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
     """Test class for VMwareVcVmdkDriver."""
+    VMDK_DRIVER = vmdk.VMwareVcVmdkDriver
 
     DEFAULT_PROFILE = 'fakeProfile'
     DEFAULT_VC_VERSION = '5.5'
@@ -1162,6 +1296,14 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         self.assertFalse(self._driver._storage_policy_enabled)
         self.assertFalse(vol_ops.retrieve_profile_id.called)
 
+    @mock.patch.object(VMDK_DRIVER, '_extend_volumeops_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, '_create_backing_in_inventory')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_create_backing_by_copying(self, volumeops, create_backing,
+                                       extend_virtual_disk):
+        self._test_create_backing_by_copying(volumeops, create_backing,
+                                             extend_virtual_disk)
+
     def test_init_conn_with_instance_and_backing(self):
         """Test initialize_connection with instance and backing."""
         m = self.mox
@@ -1256,50 +1398,85 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         m.UnsetStubs()
         m.VerifyAll()
 
-    def test_clone_backing_linked(self):
+    @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_clone_backing_linked(self, volume_ops, _extend_vmdk_virtual_disk):
         """Test _clone_backing with clone type - linked."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        m.StubOutWithMock(self._volumeops, 'clone_backing')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        self._volumeops.clone_backing(volume['name'], mox.IgnoreArg(),
-                                      mox.IgnoreArg(),
-                                      volumeops.LINKED_CLONE_TYPE,
-                                      mox.IgnoreArg())
-
-        m.ReplayAll()
-        self._driver._clone_backing(volume, mox.IgnoreArg(), mox.IgnoreArg(),
-                                    volumeops.LINKED_CLONE_TYPE)
-        m.UnsetStubs()
-        m.VerifyAll()
-
-    def test_clone_backing_full(self):
+        fake_size = 3
+        fake_volume = {'volume_type_id': None, 'name': 'fake_name',
+                       'size': fake_size}
+        fake_snapshot = {'volume_name': 'volume_name',
+                         'name': 'snapshot_name',
+                         'volume_size': 2}
+        fake_type = volumeops.LINKED_CLONE_TYPE
+        fake_backing = mock.sentinel.backing
+        self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
+                                    volumeops.LINKED_CLONE_TYPE,
+                                    fake_snapshot['volume_size'])
+        volume_ops.clone_backing.assert_called_with(fake_volume['name'],
+                                                    fake_backing,
+                                                    fake_snapshot,
+                                                    fake_type,
+                                                    None)
+        # If the volume size is greater than the original snapshot size,
+        # _extend_vmdk_virtual_disk will be called.
+        _extend_vmdk_virtual_disk.assert_called_with(fake_volume['name'],
+                                                     fake_volume['size'])
+
+        # If the volume size is not greater than the original snapshot size,
+        # _extend_vmdk_virtual_disk will not be called.
+        fake_size = 2
+        fake_volume['size'] = fake_size
+        _extend_vmdk_virtual_disk.reset_mock()
+        self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
+                                    volumeops.LINKED_CLONE_TYPE,
+                                    fake_snapshot['volume_size'])
+        self.assertFalse(_extend_vmdk_virtual_disk.called)
+
+    @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_clone_backing_full(self, volume_ops, _select_ds_for_volume,
+                                _extend_vmdk_virtual_disk):
         """Test _clone_backing with clone type - full."""
-        m = self.mox
-        m.StubOutWithMock(self._driver.__class__, 'volumeops')
-        self._driver.volumeops = self._volumeops
-        backing = FakeMor('VirtualMachine', 'my_vm')
-        datastore = FakeMor('Datastore', 'my_ds')
-        m.StubOutWithMock(self._driver, '_select_ds_for_volume')
-        volume = FakeObject()
-        volume['name'] = 'volume_name'
-        volume['size'] = 1
-        summary = FakeDatastoreSummary(1, 1, datastore=datastore)
-        self._driver._select_ds_for_volume(volume).AndReturn((_, _, _,
-                                                              summary))
-        m.StubOutWithMock(self._volumeops, 'clone_backing')
-        self._volumeops.clone_backing(volume['name'], backing,
-                                      mox.IgnoreArg(),
-                                      volumeops.FULL_CLONE_TYPE,
-                                      datastore)
-
-        m.ReplayAll()
-        self._driver._clone_backing(volume, backing, mox.IgnoreArg(),
-                                    volumeops.FULL_CLONE_TYPE)
-        m.UnsetStubs()
-        m.VerifyAll()
+        fake_host = mock.sentinel.host
+        fake_backing = mock.sentinel.backing
+        fake_folder = mock.sentinel.folder
+        fake_datastore = mock.sentinel.datastore
+        fake_resource_pool = mock.sentinel.resourcePool
+        fake_summary = mock.Mock(spec=object)
+        fake_summary.datastore = fake_datastore
+        fake_size = 3
+        fake_volume = {'volume_type_id': None, 'name': 'fake_name',
+                       'size': fake_size}
+        fake_snapshot = {'volume_name': 'volume_name', 'name': 'snapshot_name',
+                         'volume_size': 2}
+        _select_ds_for_volume.return_value = (fake_host,
+                                              fake_resource_pool,
+                                              fake_folder, fake_summary)
+        self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
+                                    volumeops.FULL_CLONE_TYPE,
+                                    fake_snapshot['volume_size'])
+        _select_ds_for_volume.assert_called_with(fake_volume)
+        volume_ops.clone_backing.assert_called_with(fake_volume['name'],
+                                                    fake_backing,
+                                                    fake_snapshot,
+                                                    volumeops.FULL_CLONE_TYPE,
+                                                    fake_datastore)
+        # If the volume size is greater than the original snapshot size,
+        # _extend_vmdk_virtual_disk will be called.
+        _extend_vmdk_virtual_disk.assert_called_with(fake_volume['name'],
+                                                     fake_volume['size'])
+
+        # If the volume size is not greater than the original snapshot size,
+        # _extend_vmdk_virtual_disk will not be called.
+        fake_size = 2
+        fake_volume['size'] = fake_size
+        _extend_vmdk_virtual_disk.reset_mock()
+        self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
+                                    volumeops.FULL_CLONE_TYPE,
+                                    fake_snapshot['volume_size'])
+        self.assertFalse(_extend_vmdk_virtual_disk.called)
 
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
@@ -1348,7 +1525,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         mock_vops = mock_vops.return_value
         driver = self._driver
         volume = {'volume_type_id': None, 'name': 'mock_vol'}
-        snapshot = {'volume_name': 'mock_vol', 'name': 'mock_snap'}
+        snapshot = {'volume_name': 'mock_vol', 'name': 'mock_snap',
+                    'volume_size': 2}
         backing = mock.sentinel.backing
         snap_moref = mock.sentinel.snap_moref
         driver._verify_volume_creation = mock.MagicMock()
@@ -1368,7 +1546,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         driver._clone_backing.assert_called_once_with(volume,
                                                       backing,
                                                       snap_moref,
-                                                      default_clone_type)
+                                                      default_clone_type,
+                                                      snapshot['volume_size'])
 
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
@@ -1391,7 +1570,7 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         mock_vops = mock_vops.return_value
         driver = self._driver
         volume = {'volume_type_id': None, 'name': 'mock_vol'}
-        src_vref = {'name': 'src_snapshot_name'}
+        src_vref = {'name': 'src_snapshot_name', 'size': 1}
         backing = mock.sentinel.backing
         driver._verify_volume_creation = mock.MagicMock()
         mock_vops.get_backing.return_value = backing
@@ -1407,7 +1586,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         driver._clone_backing.assert_called_once_with(volume,
                                                       backing,
                                                       None,
-                                                      default_clone_type)
+                                                      default_clone_type,
+                                                      src_vref['size'])
 
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
@@ -1419,7 +1599,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         mock_vops = mock_vops.return_value
         driver = self._driver
         volume = {'volume_type_id': None, 'name': 'mock_vol', 'id': 'mock_id'}
-        src_vref = {'name': 'src_snapshot_name', 'status': 'available'}
+        src_vref = {'name': 'src_snapshot_name', 'status': 'available',
+                    'size': 1}
         backing = mock.sentinel.backing
         driver._verify_volume_creation = mock.MagicMock()
         mock_vops.get_backing.return_value = backing
@@ -1441,7 +1622,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         driver._clone_backing.assert_called_once_with(volume,
                                                       backing,
                                                       mock.sentinel.snapshot,
-                                                      linked_clone)
+                                                      linked_clone,
+                                                      src_vref['size'])
 
     @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
                 'volumeops', new_callable=mock.PropertyMock)
@@ -1474,7 +1656,6 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
     @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs')
     def test_get_storage_profile(self, get_volume_type_extra_specs):
         """Test vmdk _get_storage_profile."""
-
         # volume with no type id returns None
         volume = FakeObject()
         volume['volume_type_id'] = None
@@ -1586,3 +1767,46 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
         size = volume['size'] * units.GiB
         driver._select_datastore_summary.assert_called_once_with(size,
                                                                  filtered_dss)
+
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_extend_vmdk_virtual_disk(self, volume_ops):
+        """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.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(vmware_images, 'fetch_stream_optimized_image')
+    @mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
+    @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
+    @mock.patch.object(VMDK_DRIVER, 'session')
+    @mock.patch.object(VMDK_DRIVER, 'volumeops')
+    def test_copy_image_to_volume_stream_optimized(self, volumeops,
+                                                   session,
+                                                   _select_ds_for_volume,
+                                                   _extend_virtual_disk,
+                                                   fetch_optimized_image):
+        """Test copy_image_to_volume.
+
+        Test with an acceptable vmdk disk format and streamOptimized disk type.
+        """
+        self._test_copy_image_to_volume_stream_optimized(volumeops,
+                                                         session,
+                                                         _select_ds_for_volume,
+                                                         _extend_virtual_disk,
+                                                         fetch_optimized_image)
index 1c1b7dc2e2526c2400d1b37fc61ca4c8365b6611..ab71dcb63eaa66e4fcb0514ae7e1e8e82c2accb8 100644 (file)
@@ -20,6 +20,7 @@ Test suite for VMware VMDK driver volumeops module.
 import mock
 
 from cinder import test
+from cinder import units
 from cinder.volume.drivers.vmware import error_util
 from cinder.volume.drivers.vmware import vim_util
 from cinder.volume.drivers.vmware import volumeops
@@ -791,3 +792,24 @@ class VolumeOpsTestCase(test.TestCase):
                                            name=vmdk_file_path,
                                            datacenter=dc_ref)
         self.session.wait_for_task.assert_called_once_with(task)
+
+    def test_extend_virtual_disk(self):
+        """Test volumeops.extend_virtual_disk."""
+        task = mock.sentinel.task
+        invoke_api = self.session.invoke_api
+        invoke_api.return_value = task
+        disk_mgr = self.session.vim.service_content.virtualDiskManager
+        fake_size = 5
+        fake_size_in_kb = fake_size * units.MiB
+        fake_name = 'fake_volume_0000000001'
+        fake_dc = mock.sentinel.datacenter
+        self.vops.extend_virtual_disk(fake_size,
+                                      fake_name, fake_dc)
+        invoke_api.assert_called_once_with(self.session.vim,
+                                           "ExtendVirtualDisk_Task",
+                                           disk_mgr,
+                                           name=fake_name,
+                                           datacenter=fake_dc,
+                                           newCapacityKb=fake_size_in_kb,
+                                           eagerZero=False)
+        self.session.wait_for_task.assert_called_once_with(task)
index 256dcf441da34aeeb68f72bfc3ce3a3704b04352..0321bda27e787b1b58e42229ebb375029a2b46af 100644 (file)
@@ -676,24 +676,37 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
         """
         self._delete_snapshot(snapshot)
 
-    def _clone_backing_by_copying(self, volume, src_vmdk_path):
-        """Clones volume backing.
+    def _create_backing_by_copying(self, volume, src_vmdk_path,
+                                   src_size_in_gb):
+        """Create volume backing.
 
         Creates a backing for the input volume and replaces its VMDK file
         with the input VMDK file copy.
 
         :param volume: New Volume object
         :param src_vmdk_path: VMDK file path of the source volume backing
+        :param src_size_in_gb: The size of the original volume to be cloned
+        in GB. The size of the target volume is saved in volume['size'].
+        This parameter is used to check if the size specified by the user is
+        greater than the original size. If so, the target volume should extend
+        its size.
         """
 
         # Create a backing
         backing = self._create_backing_in_inventory(volume)
-        new_vmdk_path = self.volumeops.get_vmdk_path(backing)
+        dest_vmdk_path = self.volumeops.get_vmdk_path(backing)
         datacenter = self.volumeops.get_dc(backing)
         # Deleting the current VMDK file
-        self.volumeops.delete_vmdk_file(new_vmdk_path, datacenter)
+        self.volumeops.delete_vmdk_file(dest_vmdk_path, datacenter)
         # Copying the source VMDK file
-        self.volumeops.copy_vmdk_file(datacenter, src_vmdk_path, new_vmdk_path)
+        self.volumeops.copy_vmdk_file(datacenter, src_vmdk_path,
+                                      dest_vmdk_path)
+        # If the target volume has a larger size than the source
+        # volume/snapshot, we need to resize/extend the size of the
+        # vmdk virtual disk to the value specified by the user.
+        if volume['size'] > src_size_in_gb:
+            self._extend_volumeops_virtual_disk(volume['size'], dest_vmdk_path,
+                                                datacenter)
         LOG.info(_("Successfully cloned new backing: %(back)s from "
                    "source VMDK file: %(vmdk)s.") %
                  {'back': backing, 'vmdk': src_vmdk_path})
@@ -718,7 +731,8 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
                       'vol': volume['name']})
             return
         src_vmdk_path = self.volumeops.get_vmdk_path(backing)
-        self._clone_backing_by_copying(volume, src_vmdk_path)
+        self._create_backing_by_copying(volume, src_vmdk_path,
+                                        src_vref['size'])
 
     def create_cloned_volume(self, volume, src_vref):
         """Creates volume clone.
@@ -750,13 +764,14 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
         snapshot_moref = self.volumeops.get_snapshot(backing,
                                                      snapshot['name'])
         if not snapshot_moref:
-            LOG.info(_("There is no snapshot point for the snapshoted volume: "
-                       "%(snap)s. Not creating any backing for the "
-                       "volume: %(vol)s.") %
+            LOG.info(_("There is no snapshot point for the snapshotted "
+                       "volume: %(snap)s. Not creating any backing for "
+                       "the volume: %(vol)s.") %
                      {'snap': snapshot['name'], 'vol': volume['name']})
             return
         src_vmdk_path = self.volumeops.get_vmdk_path(snapshot_moref)
-        self._clone_backing_by_copying(volume, src_vmdk_path)
+        self._create_backing_by_copying(volume, src_vmdk_path,
+                                        snapshot['volume_size'])
 
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a volume from a snapshot.
@@ -903,6 +918,40 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
         LOG.info(_("Done copying image: %(id)s to volume: %(vol)s.") %
                  {'id': image_id, 'vol': volume['name']})
 
+    def _extend_vmdk_virtual_disk(self, name, new_size_in_gb):
+        """Extend the size of the vmdk virtual disk to the new size.
+
+        :param name: the name of the volume
+        :param new_size_in_gb: the new size the vmdk virtual disk extends to
+        """
+        backing = self.volumeops.get_backing(name)
+        if not backing:
+            LOG.info(_("The backing is not found, so there is no need "
+                       "to extend the vmdk virtual disk for the volume "
+                       "%s."), name)
+        else:
+            root_vmdk_path = self.volumeops.get_vmdk_path(backing)
+            datacenter = self.volumeops.get_dc(backing)
+            self._extend_volumeops_virtual_disk(new_size_in_gb, root_vmdk_path,
+                                                datacenter)
+
+    def _extend_volumeops_virtual_disk(self, new_size_in_gb, root_vmdk_path,
+                                       datacenter):
+        """Call the ExtendVirtualDisk_Task.
+
+        :param new_size_in_gb: the new size the vmdk virtual disk extends to
+        :param root_vmdk_path: the path for the vmdk file
+        :param datacenter: reference to the datacenter
+        """
+        try:
+            self.volumeops.extend_virtual_disk(new_size_in_gb,
+                                               root_vmdk_path, datacenter)
+        except error_util.VimException:
+            with excutils.save_and_reraise_exception():
+                LOG.exception(_("Unable to extend the size of the "
+                                "vmdk virtual disk at the path %s."),
+                              root_vmdk_path)
+
     def copy_image_to_volume(self, context, volume, image_service, image_id):
         """Creates volume from image.
 
@@ -917,23 +966,36 @@ 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 disk_type for vmdk disk
         disk_type = None
+        image_size_in_bytes = metadata['size']
         properties = metadata['properties']
         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, metadata['size'])
+                                               image_id, image_size_in_bytes)
         else:
             self._fetch_flat_image(context, volume, image_service, image_id,
-                                   metadata['size'])
+                                   image_size_in_bytes)
+        # 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.GiB > image_size_in_bytes:
+            self._extend_vmdk_virtual_disk(volume['name'], volume_size_in_gb)
 
     def copy_volume_to_image(self, context, volume, image_service, image_meta):
         """Creates glance image from volume.
@@ -1159,13 +1221,14 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
                                             volumeops.LINKED_CLONE_TYPE),
                                            volumeops.FULL_CLONE_TYPE)
 
-    def _clone_backing(self, volume, backing, snapshot, clone_type):
+    def _clone_backing(self, volume, backing, snapshot, clone_type, src_vsize):
         """Clone the backing.
 
         :param volume: New Volume object
         :param backing: Reference to the backing entity
-        :param snapshot: Reference to snapshot entity
+        :param snapshot: Reference to the snapshot entity
         :param clone_type: type of the clone
+        :param src_vsize: the size of the source volume
         """
         datastore = None
         if not clone_type == volumeops.LINKED_CLONE_TYPE:
@@ -1174,6 +1237,15 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
             datastore = summary.datastore
         clone = self.volumeops.clone_backing(volume['name'], backing,
                                              snapshot, clone_type, datastore)
+        # If the volume size specified by the user is greater than
+        # the size of the source volume, the newly created volume will
+        # allocate the capacity to the size of the source volume in the backend
+        # VMDK datastore, though the volume information indicates it has a
+        # capacity of the volume size. If the volume size is greater,
+        # we need to extend/resize the capacity of the vmdk virtual disk from
+        # the size of the source volume to the volume size.
+        if volume['size'] > src_vsize:
+            self._extend_vmdk_virtual_disk(volume['name'], volume['size'])
         LOG.info(_("Successfully created clone: %s.") % clone)
 
     def _create_volume_from_snapshot(self, volume, snapshot):
@@ -1188,7 +1260,7 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
         self._verify_volume_creation(volume)
         backing = self.volumeops.get_backing(snapshot['volume_name'])
         if not backing:
-            LOG.info(_("There is no backing for the snapshoted volume: "
+            LOG.info(_("There is no backing for the snapshotted volume: "
                        "%(snap)s. Not creating any backing for the "
                        "volume: %(vol)s.") %
                      {'snap': snapshot['name'], 'vol': volume['name']})
@@ -1196,13 +1268,14 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
         snapshot_moref = self.volumeops.get_snapshot(backing,
                                                      snapshot['name'])
         if not snapshot_moref:
-            LOG.info(_("There is no snapshot point for the snapshoted volume: "
-                       "%(snap)s. Not creating any backing for the "
-                       "volume: %(vol)s.") %
+            LOG.info(_("There is no snapshot point for the snapshotted "
+                       "volume: %(snap)s. Not creating any backing for "
+                       "the volume: %(vol)s.") %
                      {'snap': snapshot['name'], 'vol': volume['name']})
             return
         clone_type = VMwareVcVmdkDriver._get_clone_type(volume)
-        self._clone_backing(volume, backing, snapshot_moref, clone_type)
+        self._clone_backing(volume, backing, snapshot_moref, clone_type,
+                            snapshot['volume_size'])
 
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a volume from a snapshot.
@@ -1240,7 +1313,8 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
             # then create the linked clone out of this snapshot point.
             name = 'snapshot-%s' % volume['id']
             snapshot = self.volumeops.create_snapshot(backing, name, None)
-        self._clone_backing(volume, backing, snapshot, clone_type)
+        self._clone_backing(volume, backing, snapshot, clone_type,
+                            src_vref['size'])
 
     def create_cloned_volume(self, volume, src_vref):
         """Creates volume clone.
index f582d8e827584a3a5b37de7a7c437a49fc3eb023..8ba7b01f153ae5d2a8dced5c0b2eae953da33a99 100644 (file)
@@ -18,6 +18,7 @@ Implements operations on volumes residing on VMware datastores.
 """
 
 from cinder.openstack.common import log as logging
+from cinder import units
 from cinder.volume.drivers.vmware import error_util
 from cinder.volume.drivers.vmware import vim_util
 
@@ -318,6 +319,35 @@ class VMwareVolumeOps(object):
         LOG.debug(_("Created child folder: %s.") % child_folder)
         return child_folder
 
+    def extend_virtual_disk(self, requested_size_in_gb, name, dc_ref,
+                            eager_zero=False):
+        """Extend the virtual disk to the requested size.
+
+        :param requested_size_in_gb: Size of the volume in GB
+        :param name: Name of the backing
+        :param dc_ref: Reference datacenter
+        :param eager_zero: Boolean determining if the free space
+        is zeroed out
+        """
+        LOG.debug(_("Extending the volume %(name)s to %(size)s GB."),
+                  {'name': name, 'size': requested_size_in_gb})
+        diskMgr = self._session.vim.service_content.virtualDiskManager
+
+        # VMWare API needs the capacity unit to be in KB, so convert the
+        # capacity unit from GB to KB.
+        size_in_kb = requested_size_in_gb * units.MiB
+        task = self._session.invoke_api(self._session.vim,
+                                        "ExtendVirtualDisk_Task",
+                                        diskMgr,
+                                        name=name,
+                                        datacenter=dc_ref,
+                                        newCapacityKb=size_in_kb,
+                                        eagerZero=eager_zero)
+        self._session.wait_for_task(task)
+        LOG.info(_("Successfully extended the volume %(name)s to "
+                   "%(size)s GB."),
+                 {'name': name, 'size': requested_size_in_gb})
+
     def _get_create_spec(self, name, size_kb, disk_type, ds_name,
                          profileId=None):
         """Return spec for creating volume backing.