Merge "VMware: Relocate volume only during no disk space"

This commit is contained in:
Jenkins 2015-11-09 20:51:25 +00:00 committed by Gerrit Code Review
commit 1cc3a08815
3 changed files with 186 additions and 225 deletions
cinder
tests/unit
volume/drivers/vmware

@ -294,90 +294,6 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self.assertRaises(cinder_exceptions.InvalidVolume, self.assertRaises(cinder_exceptions.InvalidVolume,
self._driver.delete_snapshot, snapshot) self._driver.delete_snapshot, snapshot)
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_volume(self, volume_ops, _extend_virtual_disk,
_select_ds_for_volume):
"""Test extend_volume."""
self._test_extend_volume(volume_ops, _extend_virtual_disk,
_select_ds_for_volume)
def _test_extend_volume(self, volume_ops, _extend_virtual_disk,
_select_ds_for_volume):
fake_name = u'volume-00000001'
new_size = '21'
fake_size = '20'
fake_vol = {'project_id': 'testprjid', 'name': fake_name,
'size': fake_size,
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
fake_host = mock.sentinel.host
fake_rp = mock.sentinel.rp
fake_folder = mock.sentinel.folder
fake_summary = mock.Mock(spec=object)
fake_summary.datastore = mock.sentinel.datastore
fake_summary.name = 'fake_name'
fake_backing = mock.sentinel.backing
volume_ops.get_backing.return_value = fake_backing
# If there is enough space in the datastore, where the volume is
# located, then the rest of this method will not be called.
self._driver.extend_volume(fake_vol, new_size)
_extend_virtual_disk.assert_called_with(fake_name, new_size)
self.assertFalse(_select_ds_for_volume.called)
self.assertFalse(volume_ops.get_backing.called)
self.assertFalse(volume_ops.relocate_backing.called)
self.assertFalse(volume_ops.move_backing_to_folder.called)
# If there is not enough space in the datastore, where the volume is
# located, then the rest of this method will be called. The first time
# _extend_virtual_disk is called, VimFaultException is raised. The
# second time it is called, there is no exception.
_extend_virtual_disk.reset_mock()
_extend_virtual_disk.side_effect = [exceptions.
VimFaultException([],
'Error'), None]
# When _select_ds_for_volume raises no exception.
_select_ds_for_volume.return_value = (fake_host, fake_rp,
fake_folder, fake_summary)
self._driver.extend_volume(fake_vol, new_size)
_select_ds_for_volume.assert_called_with(new_size)
volume_ops.get_backing.assert_called_with(fake_name)
volume_ops.relocate_backing.assert_called_with(fake_backing,
fake_summary.datastore,
fake_rp,
fake_host)
_extend_virtual_disk.assert_called_with(fake_name, new_size)
volume_ops.move_backing_to_folder.assert_called_with(fake_backing,
fake_folder)
# If get_backing raises error_util.VimException,
# this exception will be caught for volume extend.
_extend_virtual_disk.reset_mock()
_extend_virtual_disk.side_effect = [exceptions.
VimFaultException([],
'Error'), None]
volume_ops.get_backing.side_effect = exceptions.VimException('Error')
self.assertRaises(exceptions.VimException, self._driver.extend_volume,
fake_vol, new_size)
# If _select_ds_for_volume raised an exception, the rest code will
# not be called.
_extend_virtual_disk.reset_mock()
volume_ops.get_backing.reset_mock()
volume_ops.relocate_backing.reset_mock()
volume_ops.move_backing_to_folder.reset_mock()
_extend_virtual_disk.side_effect = [exceptions.
VimFaultException([],
'Error'), None]
_select_ds_for_volume.side_effect = exceptions.VimException('Error')
self.assertRaises(exceptions.VimException, self._driver.extend_volume,
fake_vol, new_size)
_extend_virtual_disk.assert_called_once_with(fake_name, new_size)
self.assertFalse(volume_ops.get_backing.called)
self.assertFalse(volume_ops.relocate_backing.called)
self.assertFalse(volume_ops.move_backing_to_folder.called)
def test_copy_image_to_volume_non_vmdk(self): def test_copy_image_to_volume_non_vmdk(self):
"""Test copy_image_to_volume for a non-vmdk disk format.""" """Test copy_image_to_volume for a non-vmdk disk format."""
fake_context = mock.sentinel.context fake_context = mock.sentinel.context
@ -391,7 +307,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
fake_context, fake_volume, fake_context, fake_volume,
image_service, fake_image_id) image_service, fake_image_id)
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk') @mock.patch.object(VMDK_DRIVER, '_extend_backing')
@mock.patch('oslo_utils.uuidutils.generate_uuid') @mock.patch('oslo_utils.uuidutils.generate_uuid')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume') @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, 'volumeops') @mock.patch.object(VMDK_DRIVER, 'volumeops')
@ -405,7 +321,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
def test_copy_image_to_volume_non_stream_optimized( def test_copy_image_to_volume_non_stream_optimized(
self, create_backing, get_ds_name_folder_path, get_disk_type, self, create_backing, get_ds_name_folder_path, get_disk_type,
create_disk_from_sparse_image, create_disk_from_preallocated_image, create_disk_from_sparse_image, create_disk_from_preallocated_image,
vops, select_ds_for_volume, generate_uuid, extend_disk): vops, select_ds_for_volume, generate_uuid, extend_backing):
self._test_copy_image_to_volume_non_stream_optimized( self._test_copy_image_to_volume_non_stream_optimized(
create_backing, create_backing,
get_ds_name_folder_path, get_ds_name_folder_path,
@ -415,12 +331,12 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
vops, vops,
select_ds_for_volume, select_ds_for_volume,
generate_uuid, generate_uuid,
extend_disk) extend_backing)
def _test_copy_image_to_volume_non_stream_optimized( def _test_copy_image_to_volume_non_stream_optimized(
self, create_backing, get_ds_name_folder_path, get_disk_type, self, create_backing, get_ds_name_folder_path, get_disk_type,
create_disk_from_sparse_image, create_disk_from_preallocated_image, create_disk_from_sparse_image, create_disk_from_preallocated_image,
vops, select_ds_for_volume, generate_uuid, extend_disk): vops, select_ds_for_volume, generate_uuid, extend_backing):
image_size_in_bytes = 2 * units.Gi image_size_in_bytes = 2 * units.Gi
adapter_type = 'lsiLogic' adapter_type = 'lsiLogic'
image_meta = {'disk_format': 'vmdk', image_meta = {'disk_format': 'vmdk',
@ -494,8 +410,9 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
vops.delete_backing.assert_called_once_with(backing) vops.delete_backing.assert_called_once_with(backing)
vops.update_backing_disk_uuid.assert_called_once_with(clone, vops.update_backing_disk_uuid.assert_called_once_with(clone,
volume['id']) volume['id'])
self.assertFalse(extend_disk.called) self.assertFalse(extend_backing.called)
vops.get_backing.return_value = backing
vops.get_disk_size.return_value = 1 * units.Gi vops.get_disk_size.return_value = 1 * units.Gi
create_backing.reset_mock() create_backing.reset_mock()
vops.attach_disk_to_backing.reset_mock() vops.attach_disk_to_backing.reset_mock()
@ -517,9 +434,9 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
adapter_type, path.get_descriptor_ds_file_path()) adapter_type, path.get_descriptor_ds_file_path())
vops.update_backing_disk_uuid.assert_called_once_with(backing, vops.update_backing_disk_uuid.assert_called_once_with(backing,
volume['id']) volume['id'])
extend_disk.assert_called_once_with(volume['name'], volume['size']) extend_backing.assert_called_once_with(backing, volume['size'])
extend_disk.reset_mock() extend_backing.reset_mock()
create_disk_from_preallocated_image.side_effect = ( create_disk_from_preallocated_image.side_effect = (
exceptions.VimException("Error")) exceptions.VimException("Error"))
@ -527,7 +444,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self._driver.copy_image_to_volume, self._driver.copy_image_to_volume,
context, volume, image_service, image_id) context, volume, image_service, image_id)
vops.delete_backing.assert_called_once_with(backing) vops.delete_backing.assert_called_once_with(backing)
self.assertFalse(extend_disk.called) self.assertFalse(extend_backing.called)
@mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk') @mock.patch.object(VMDK_DRIVER, '_copy_temp_virtual_disk')
@mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder') @mock.patch.object(VMDK_DRIVER, '_get_temp_image_folder')
@ -724,7 +641,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self.assertEqual(dest_path, ret) self.assertEqual(dest_path, ret)
@mock.patch.object(image_transfer, 'download_stream_optimized_image') @mock.patch.object(image_transfer, 'download_stream_optimized_image')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk') @mock.patch.object(VMDK_DRIVER, '_extend_backing')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume') @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_get_storage_profile_id') @mock.patch.object(VMDK_DRIVER, '_get_storage_profile_id')
@mock.patch.object(VMDK_DRIVER, 'session') @mock.patch.object(VMDK_DRIVER, 'session')
@ -734,7 +651,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
session, session,
get_profile_id, get_profile_id,
_select_ds_for_volume, _select_ds_for_volume,
_extend_virtual_disk, extend_backing,
download_image): download_image):
"""Test copy_image_to_volume. """Test copy_image_to_volume.
@ -744,14 +661,14 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
session, session,
get_profile_id, get_profile_id,
_select_ds_for_volume, _select_ds_for_volume,
_extend_virtual_disk, extend_backing,
download_image) download_image)
def _test_copy_image_to_volume_stream_optimized(self, volumeops, def _test_copy_image_to_volume_stream_optimized(self, volumeops,
session, session,
get_profile_id, get_profile_id,
_select_ds_for_volume, _select_ds_for_volume,
_extend_virtual_disk, extend_backing,
download_image): download_image):
fake_context = mock.Mock() fake_context = mock.Mock()
fake_backing = mock.sentinel.backing fake_backing = mock.sentinel.backing
@ -797,7 +714,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self.assertFalse(volumeops.get_create_spec.called) self.assertFalse(volumeops.get_create_spec.called)
# If the volume size is greater then than the backing's disk size, # If the volume size is greater then than the backing's disk size,
# _extend_vmdk_virtual_disk will be called. # _extend_backing will be called.
_select_ds_for_volume.side_effect = None _select_ds_for_volume.side_effect = None
_select_ds_for_volume.return_value = (fake_host, fake_rp, _select_ds_for_volume.return_value = (fake_host, fake_rp,
fake_folder, fake_summary) fake_folder, fake_summary)
@ -836,18 +753,17 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
image_size=size) image_size=size)
volumeops.update_backing_disk_uuid.assert_called_once_with( volumeops.update_backing_disk_uuid.assert_called_once_with(
backing, fake_volume['id']) backing, fake_volume['id'])
_extend_virtual_disk.assert_called_once_with(fake_volume['name'], extend_backing.assert_called_once_with(backing, fake_volume_size)
fake_volume_size)
# If the volume size is not greater then than backing's disk size, # If the volume size is not greater then than backing's disk size,
# _extend_vmdk_virtual_disk will not be called. # _extend_backing will not be called.
volumeops.get_disk_size.return_value = fake_volume_size * units.Gi volumeops.get_disk_size.return_value = fake_volume_size * units.Gi
_extend_virtual_disk.reset_mock() extend_backing.reset_mock()
self._driver.copy_image_to_volume(fake_context, fake_volume, self._driver.copy_image_to_volume(fake_context, fake_volume,
image_service, fake_image_id) image_service, fake_image_id)
self.assertFalse(_extend_virtual_disk.called) self.assertFalse(extend_backing.called)
# If fetch_stream_optimized_image raises an exception, # If fetch_stream_optimized_image raises an exception,
# get_backing and delete_backing will be called. # get_backing and delete_backing will be called.
@ -859,7 +775,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
image_service, fake_image_id) image_service, fake_image_id)
volumeops.get_backing.assert_called_with(fake_volume['name']) volumeops.get_backing.assert_called_with(fake_volume['name'])
volumeops.delete_backing.assert_called_with(fake_backing) volumeops.delete_backing.assert_called_with(fake_backing)
self.assertFalse(_extend_virtual_disk.called) self.assertFalse(extend_backing.called)
def test_copy_volume_to_image_non_vmdk(self): def test_copy_volume_to_image_non_vmdk(self):
"""Test copy_volume_to_image for a non-vmdk disk format.""" """Test copy_volume_to_image for a non-vmdk disk format."""
@ -1152,45 +1068,21 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self.assertFalse(vops.change_backing_profile.called) self.assertFalse(vops.change_backing_profile.called)
@mock.patch.object(VMDK_DRIVER, 'volumeops') @mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_vmdk_virtual_disk(self, volume_ops): def test_extend_backing(self, vops):
"""Test vmdk._extend_vmdk_virtual_disk.""" vmdk_path = mock.sentinel.vmdk_path
self._test_extend_vmdk_virtual_disk(volume_ops) vops.get_vmdk_path.return_value = vmdk_path
dc = mock.sentinel.datacenter
vops.get_dc.return_value = dc
def _test_extend_vmdk_virtual_disk(self, volume_ops): backing = mock.sentinel.backing
fake_backing = mock.sentinel.backing new_size = 1
fake_vmdk_path = "[datastore] dest_vm/dest_vm.vmdk" self._driver._extend_backing(backing, new_size)
fake_dc = mock.sentinel.datacenter
fake_name = 'fake_name'
fake_size = 7
# If the backing is None, get_vmdk_path and get_dc vops.get_vmdk_path.assert_called_once_with(backing)
# will not be called vops.get_dc.assert_called_once_with(backing)
volume_ops.get_backing.return_value = None vops.extend_virtual_disk.assert_called_once_with(new_size,
volume_ops.get_vmdk_path.return_value = fake_vmdk_path vmdk_path,
volume_ops.get_dc.return_value = fake_dc 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 = exceptions.VimException(
'VimException raised.')
self.assertRaises(exceptions.VimException,
self._driver._extend_vmdk_virtual_disk,
fake_name, fake_size)
@mock.patch.object(image_transfer, 'copy_stream_optimized_disk') @mock.patch.object(image_transfer, 'copy_stream_optimized_disk')
@mock.patch('cinder.volume.drivers.vmware.vmdk.open', create=True) @mock.patch('cinder.volume.drivers.vmware.vmdk.open', create=True)
@ -1790,9 +1682,9 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
vops.create_vm_inventory_folder.assert_called_once_with( vops.create_vm_inventory_folder.assert_called_once_with(
datacenter, ['OpenStack', project_folder_name, self.VOLUME_FOLDER]) datacenter, ['OpenStack', project_folder_name, self.VOLUME_FOLDER])
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk') @mock.patch.object(VMDK_DRIVER, '_extend_backing')
@mock.patch.object(VMDK_DRIVER, 'volumeops') @mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_clone_backing_linked(self, volume_ops, _extend_vmdk_virtual_disk): def test_clone_backing_linked(self, volume_ops, extend_backing):
"""Test _clone_backing with clone type - linked.""" """Test _clone_backing with clone type - linked."""
clone = mock.sentinel.clone clone = mock.sentinel.clone
volume_ops.clone_backing.return_value = clone volume_ops.clone_backing.return_value = clone
@ -1823,25 +1715,24 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
clone, fake_volume['id']) clone, fake_volume['id'])
# If the volume size is greater than the original snapshot size, # If the volume size is greater than the original snapshot size,
# _extend_vmdk_virtual_disk will be called. # _extend_backing will be called.
_extend_vmdk_virtual_disk.assert_called_with(fake_volume['name'], extend_backing.assert_called_with(clone, fake_volume['size'])
fake_volume['size'])
# If the volume size is not greater than the original snapshot size, # If the volume size is not greater than the original snapshot size,
# _extend_vmdk_virtual_disk will not be called. # _extend_backing will not be called.
fake_size = 2 fake_size = 2
fake_volume['size'] = fake_size fake_volume['size'] = fake_size
_extend_vmdk_virtual_disk.reset_mock() extend_backing.reset_mock()
self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot, self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
volumeops.LINKED_CLONE_TYPE, volumeops.LINKED_CLONE_TYPE,
fake_snapshot['volume_size']) fake_snapshot['volume_size'])
self.assertFalse(_extend_vmdk_virtual_disk.called) self.assertFalse(extend_backing.called)
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk') @mock.patch.object(VMDK_DRIVER, '_extend_backing')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume') @mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, 'volumeops') @mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_clone_backing_full(self, volume_ops, _select_ds_for_volume, def test_clone_backing_full(self, volume_ops, _select_ds_for_volume,
_extend_vmdk_virtual_disk): extend_backing):
"""Test _clone_backing with clone type - full.""" """Test _clone_backing with clone type - full."""
fake_host = mock.sentinel.host fake_host = mock.sentinel.host
fake_folder = mock.sentinel.folder fake_folder = mock.sentinel.folder
@ -1882,19 +1773,18 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
clone, fake_volume['id']) clone, fake_volume['id'])
# If the volume size is greater than the original snapshot size, # If the volume size is greater than the original snapshot size,
# _extend_vmdk_virtual_disk will be called. # _extend_backing will be called.
_extend_vmdk_virtual_disk.assert_called_with(fake_volume['name'], extend_backing.assert_called_with(clone, fake_volume['size'])
fake_volume['size'])
# If the volume size is not greater than the original snapshot size, # If the volume size is not greater than the original snapshot size,
# _extend_vmdk_virtual_disk will not be called. # _extend_backing will not be called.
fake_size = 2 fake_size = 2
fake_volume['size'] = fake_size fake_volume['size'] = fake_size
_extend_vmdk_virtual_disk.reset_mock() extend_backing.reset_mock()
self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot, self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot,
volumeops.FULL_CLONE_TYPE, volumeops.FULL_CLONE_TYPE,
fake_snapshot['volume_size']) fake_snapshot['volume_size'])
self.assertFalse(_extend_vmdk_virtual_disk.called) self.assertFalse(extend_backing.called)
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.' @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
'volumeops', new_callable=mock.PropertyMock) 'volumeops', new_callable=mock.PropertyMock)
@ -2492,6 +2382,88 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
cacert=self._config.vmware_ca_file, cacert=self._config.vmware_ca_file,
insecure=self._config.vmware_insecure) insecure=self._config.vmware_insecure)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_extend_backing')
def test_extend_volume_with_no_backing(self, extend_backing, vops):
vops.get_backing.return_value = None
volume = {'name': 'volume-51e47214-8e3c-475d-b44b-aea6cd3eef53',
'volume_type_id': None, 'size': 1,
'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53',
'display_name': 'foo'}
self._driver.extend_volume(volume, 2)
self.assertFalse(extend_backing.called)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_extend_backing')
def test_extend_volume(self, extend_backing, vops):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
volume = {'name': 'volume-51e47214-8e3c-475d-b44b-aea6cd3eef53',
'volume_type_id': None, 'size': 1,
'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53',
'display_name': 'foo'}
new_size = 2
self._driver.extend_volume(volume, new_size)
extend_backing.assert_called_once_with(backing, new_size)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_extend_backing')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
def test_extend_volume_with_no_disk_space(self, select_ds_for_volume,
extend_backing, vops):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
extend_backing.side_effect = [exceptions.NoDiskSpaceException, None]
host = mock.sentinel.host
rp = mock.sentinel.rp
folder = mock.sentinel.folder
datastore = mock.sentinel.datastore
summary = mock.Mock(datastore=datastore)
select_ds_for_volume.return_value = (host, rp, folder, summary)
volume = {'name': 'volume-51e47214-8e3c-475d-b44b-aea6cd3eef53',
'volume_type_id': None, 'size': 1,
'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53',
'display_name': 'foo'}
new_size = 2
self._driver.extend_volume(volume, new_size)
create_params = {vmdk.CREATE_PARAM_DISK_SIZE: new_size}
select_ds_for_volume.assert_called_once_with(
volume, create_params=create_params)
vops.relocate_backing.assert_called_once_with(backing, datastore, rp,
host)
vops.move_backing_to_folder(backing, folder)
extend_backing_calls = [mock.call(backing, new_size),
mock.call(backing, new_size)]
self.assertEqual(extend_backing_calls, extend_backing.call_args_list)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_extend_backing')
def test_extend_volume_with_extend_backing_error(
self, extend_backing, vops):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
extend_backing.side_effect = exceptions.VimException("Error")
volume = {'name': 'volume-51e47214-8e3c-475d-b44b-aea6cd3eef53',
'volume_type_id': None, 'size': 1,
'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53',
'display_name': 'foo'}
new_size = 2
self.assertRaises(exceptions.VimException, self._driver.extend_volume,
volume, new_size)
extend_backing.assert_called_once_with(backing, new_size)
class ImageDiskTypeTest(test.TestCase): class ImageDiskTypeTest(test.TestCase):
"""Unit tests for ImageDiskType.""" """Unit tests for ImageDiskType."""

@ -57,6 +57,7 @@ EAGER_ZEROED_THICK_VMDK_TYPE = 'eagerZeroedThick'
CREATE_PARAM_ADAPTER_TYPE = 'adapter_type' CREATE_PARAM_ADAPTER_TYPE = 'adapter_type'
CREATE_PARAM_DISK_LESS = 'disk_less' CREATE_PARAM_DISK_LESS = 'disk_less'
CREATE_PARAM_BACKING_NAME = 'name' CREATE_PARAM_BACKING_NAME = 'name'
CREATE_PARAM_DISK_SIZE = 'disk_size'
TMP_IMAGES_DATASTORE_FOLDER_PATH = "cinder_temp/" TMP_IMAGES_DATASTORE_FOLDER_PATH = "cinder_temp/"
@ -474,8 +475,11 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
:return: (host, resource_pool, folder, summary) :return: (host, resource_pool, folder, summary)
""" """
# Form requirements for datastore selection. # Form requirements for datastore selection.
create_params = create_params or {}
size = create_params.get(CREATE_PARAM_DISK_SIZE, volume['size'])
req = {} req = {}
req[hub.DatastoreSelector.SIZE_BYTES] = (volume['size'] * units.Gi) req[hub.DatastoreSelector.SIZE_BYTES] = size * units.Gi
req[hub.DatastoreSelector.PROFILE_NAME] = self._get_storage_profile( req[hub.DatastoreSelector.PROFILE_NAME] = self._get_storage_profile(
volume) volume)
@ -1037,39 +1041,16 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
LOG.info(_LI("Done copying image: %(id)s to volume: %(vol)s."), LOG.info(_LI("Done copying image: %(id)s to volume: %(vol)s."),
{'id': image_id, 'vol': volume['name']}) {'id': image_id, 'vol': volume['name']})
def _extend_vmdk_virtual_disk(self, name, new_size_in_gb): def _extend_backing(self, backing, new_size_in_gb):
"""Extend the size of the vmdk virtual disk to the new size. """Extend volume backing's virtual disk.
:param name: the name of the volume :param backing: volume backing
:param new_size_in_gb: the new size the vmdk virtual disk extends to :param new_size_in_gb: new size of virtual disk
""" """
backing = self.volumeops.get_backing(name) root_vmdk_path = self.volumeops.get_vmdk_path(backing)
if not backing: datacenter = self.volumeops.get_dc(backing)
LOG.info(_LI("The backing is not found, so there is no need " self.volumeops.extend_virtual_disk(new_size_in_gb, root_vmdk_path,
"to extend the vmdk virtual disk for the volume " datacenter)
"%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 exceptions.VimException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("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): def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Creates volume from image. """Creates volume from image.
@ -1146,7 +1127,7 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
{'name': volume['name'], {'name': volume['name'],
'vol_size': volume_size, 'vol_size': volume_size,
'disk_size': disk_size}) 'disk_size': disk_size})
self._extend_vmdk_virtual_disk(volume['name'], volume['size']) self._extend_backing(backing, volume['size'])
# TODO(vbala): handle volume_size < disk_size case. # TODO(vbala): handle volume_size < disk_size case.
def copy_volume_to_image(self, context, volume, image_service, image_meta): def copy_volume_to_image(self, context, volume, image_service, image_meta):
@ -1377,50 +1358,58 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
return True return True
def extend_volume(self, volume, new_size): def extend_volume(self, volume, new_size):
"""Extend vmdk to new_size. """Extend volume to new size.
Extends the vmdk backing to new volume size. First try to extend in Extends the volume backing's virtual disk to new size. First, try to
place on the same datastore. If that fails, try to relocate the volume extend in place on the same datastore. If that fails due to
to a different datastore that can accommodate the new_size'd volume. insufficient disk space, then try to relocate the volume to a different
datastore that can accommodate the backing with new size and retry
extend.
:param volume: dictionary describing the existing 'available' volume :param volume: dictionary describing the existing 'available' volume
:param new_size: new size in GB to extend this volume to :param new_size: new size in GB to extend this volume to
""" """
vol_name = volume['name'] vol_name = volume['name']
backing = self.volumeops.get_backing(vol_name)
if not backing:
LOG.info(_LI("There is no backing for volume: %s; no need to "
"extend the virtual disk."), vol_name)
return
# try extending vmdk in place # try extending vmdk in place
try: try:
self._extend_vmdk_virtual_disk(vol_name, new_size) self._extend_backing(backing, new_size)
LOG.info(_LI("Done extending volume %(vol)s " LOG.info(_LI("Successfully extended volume: %(vol)s to size: "
"to size %(size)s GB."), "%(size)s GB."),
{'vol': vol_name, 'size': new_size}) {'vol': vol_name, 'size': new_size})
return return
except exceptions.VimFaultException: except exceptions.NoDiskSpaceException:
LOG.info(_LI("Relocating volume %s vmdk to a different " LOG.warning(_LW("Unable to extend volume: %(vol)s to size: "
"datastore since trying to extend vmdk file " "%(size)s on current datastore due to insufficient"
"in place failed."), vol_name) " space."),
# If in place extend fails, then try to relocate the volume {'vol': vol_name, 'size': new_size})
try:
(host, rp, folder, summary) = self._select_ds_for_volume(new_size)
except exceptions.VimException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Not able to find a different datastore to "
"place the extended volume %s."), vol_name)
LOG.info(_LI("Selected datastore %(ds)s to place extended volume of "
"size %(size)s GB."), {'ds': summary.name,
'size': new_size})
# Insufficient disk space; relocate the volume to a different datastore
# and retry extend.
LOG.info(_LI("Relocating volume: %s to a different datastore due to "
"insufficient disk space on current datastore."),
vol_name)
try: try:
backing = self.volumeops.get_backing(vol_name) create_params = {CREATE_PARAM_DISK_SIZE: new_size}
(host, rp, folder, summary) = self._select_ds_for_volume(
volume, create_params=create_params)
self.volumeops.relocate_backing(backing, summary.datastore, rp, self.volumeops.relocate_backing(backing, summary.datastore, rp,
host) host)
self._extend_vmdk_virtual_disk(vol_name, new_size)
self.volumeops.move_backing_to_folder(backing, folder) self.volumeops.move_backing_to_folder(backing, folder)
except exceptions.VimException: self._extend_backing(backing, new_size)
except exceptions.VMwareDriverException:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.exception(_LE("Not able to relocate volume %s for " LOG.error(_LE("Failed to extend volume: %(vol)s to size: "
"extending."), vol_name) "%(size)s GB."),
LOG.info(_LI("Done extending volume %(vol)s to size %(size)s GB."), {'vol': vol_name, 'size': new_size})
LOG.info(_LI("Successfully extended volume: %(vol)s to size: "
"%(size)s GB."),
{'vol': vol_name, 'size': new_size}) {'vol': vol_name, 'size': new_size})
@contextlib.contextmanager @contextlib.contextmanager
@ -1860,7 +1849,7 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
# we need to extend/resize the capacity of the vmdk virtual disk from # we need to extend/resize the capacity of the vmdk virtual disk from
# the size of the source volume to the volume size. # the size of the source volume to the volume size.
if volume['size'] > src_vsize: if volume['size'] > src_vsize:
self._extend_vmdk_virtual_disk(volume['name'], volume['size']) self._extend_backing(clone, volume['size'])
LOG.info(_LI("Successfully created clone: %s."), clone) LOG.info(_LI("Successfully created clone: %s."), clone)
def _create_volume_from_snapshot(self, volume, snapshot): def _create_volume_from_snapshot(self, volume, snapshot):

@ -617,18 +617,18 @@ class VMwareVolumeOps(object):
'folder': folder}) 'folder': folder})
return folder return folder
def extend_virtual_disk(self, requested_size_in_gb, name, dc_ref, def extend_virtual_disk(self, requested_size_in_gb, path, dc_ref,
eager_zero=False): eager_zero=False):
"""Extend the virtual disk to the requested size. """Extend the virtual disk to the requested size.
:param requested_size_in_gb: Size of the volume in GB :param requested_size_in_gb: Size of the volume in GB
:param name: Name of the backing :param path: Datastore path of the virtual disk to extend
:param dc_ref: Reference to datacenter :param dc_ref: Reference to datacenter
:param eager_zero: Boolean determining if the free space :param eager_zero: Boolean determining if the free space
is zeroed out is zeroed out
""" """
LOG.debug("Extending the volume %(name)s to %(size)s GB.", LOG.debug("Extending virtual disk: %(path)s to %(size)s GB.",
{'name': name, 'size': requested_size_in_gb}) {'path': path, 'size': requested_size_in_gb})
diskMgr = self._session.vim.service_content.virtualDiskManager diskMgr = self._session.vim.service_content.virtualDiskManager
# VMWare API needs the capacity unit to be in KB, so convert the # VMWare API needs the capacity unit to be in KB, so convert the
@ -637,14 +637,14 @@ class VMwareVolumeOps(object):
task = self._session.invoke_api(self._session.vim, task = self._session.invoke_api(self._session.vim,
"ExtendVirtualDisk_Task", "ExtendVirtualDisk_Task",
diskMgr, diskMgr,
name=name, name=path,
datacenter=dc_ref, datacenter=dc_ref,
newCapacityKb=size_in_kb, newCapacityKb=size_in_kb,
eagerZero=eager_zero) eagerZero=eager_zero)
self._session.wait_for_task(task) self._session.wait_for_task(task)
LOG.info(_LI("Successfully extended the volume %(name)s to " LOG.info(_LI("Successfully extended virtual disk: %(path)s to "
"%(size)s GB."), "%(size)s GB."),
{'name': name, 'size': requested_size_in_gb}) {'path': path, 'size': requested_size_in_gb})
def _create_controller_config_spec(self, adapter_type): def _create_controller_config_spec(self, adapter_type):
"""Returns config spec for adding a disk controller.""" """Returns config spec for adding a disk controller."""