From 015555acb75ee4d9298915951d2bfaf0d19d2b02 Mon Sep 17 00:00:00 2001 From: Vincent Hou Date: Wed, 4 Dec 2013 03:51:20 -0500 Subject: [PATCH] VMware: Take the volume size from the user input 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 | 660 +++++++++++++++------- cinder/tests/test_vmware_volumeops.py | 22 + cinder/volume/drivers/vmware/vmdk.py | 116 +++- cinder/volume/drivers/vmware/volumeops.py | 30 + 4 files changed, 589 insertions(+), 239 deletions(-) diff --git a/cinder/tests/test_vmware_vmdk.py b/cinder/tests/test_vmware_vmdk.py index 677e3fcba50..7031a2bbf98 100644 --- a/cinder/tests/test_vmware_vmdk.py +++ b/cinder/tests/test_vmware_vmdk.py @@ -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() + fake_context, fake_volume, + image_service, fake_image_id) - def test_copy_image_to_volume_vmdk(self): + @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 + 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) - 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' + 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() + 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 - def test_copy_image_to_volume_stream_optimized(self): + # 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 + self._test_copy_image_to_volume_stream_optimized(volumeops, + session, + _select_ds_for_volume, + _extend_virtual_disk, + fetch_optimized_image) - image_id = 'image-id' + 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()) + 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']) - m.ReplayAll() - self._driver._clone_backing(volume, mox.IgnoreArg(), mox.IgnoreArg(), - volumeops.LINKED_CLONE_TYPE) - m.UnsetStubs() - m.VerifyAll() + # 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) - def test_clone_backing_full(self): + @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) + 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']) - m.ReplayAll() - self._driver._clone_backing(volume, backing, mox.IgnoreArg(), - volumeops.FULL_CLONE_TYPE) - m.UnsetStubs() - m.VerifyAll() + # 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) diff --git a/cinder/tests/test_vmware_volumeops.py b/cinder/tests/test_vmware_volumeops.py index 1c1b7dc2e25..ab71dcb63ea 100644 --- a/cinder/tests/test_vmware_volumeops.py +++ b/cinder/tests/test_vmware_volumeops.py @@ -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) diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index 256dcf441da..0321bda27e7 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -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. diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py index f582d8e8275..8ba7b01f153 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -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.