from cinder import test
from cinder.volume import configuration
from cinder.volume.drivers.vmware import api
+from cinder.volume.drivers.vmware import datastore as hub
from cinder.volume.drivers.vmware import error_util
from cinder.volume.drivers.vmware import vim
from cinder.volume.drivers.vmware import vim_util
self.assertRaises(error_util.VimFaultException, driver.create_volume,
volume)
+ # Clear side effects.
+ driver._select_ds_for_volume.side_effect = None
+
def test_success_wait_for_task(self):
"""Test successful wait_for_task."""
m = self.mox
size = volume['size'] * units.Gi
driver._select_datastore_summary.assert_called_once_with(size, dss)
- def test_get_disk_type(self):
+ @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs')
+ def test_get_disk_type(self, get_volume_type_extra_specs):
"""Test _get_disk_type."""
- volume = FakeObject()
- volume['volume_type_id'] = None
- self.assertEqual(vmdk.VMwareEsxVmdkDriver._get_disk_type(volume),
- 'thin')
+ # Test with no volume type.
+ volume = {'volume_type_id': None}
+ self.assertEqual(vmdk.THIN_VMDK_TYPE,
+ vmdk.VMwareEsxVmdkDriver._get_disk_type(volume))
+
+ # Test with valid vmdk_type.
+ volume_type_id = mock.sentinel.volume_type_id
+ volume = {'volume_type_id': volume_type_id}
+ get_volume_type_extra_specs.return_value = vmdk.THICK_VMDK_TYPE
+
+ self.assertEqual(vmdk.THICK_VMDK_TYPE,
+ vmdk.VMwareEsxVmdkDriver._get_disk_type(volume))
+ get_volume_type_extra_specs.assert_called_once_with(volume_type_id,
+ 'vmware:vmdk_type')
+ # Test with invalid vmdk_type.
+ get_volume_type_extra_specs.return_value = 'sparse'
+
+ self.assertRaises(error_util.InvalidDiskTypeException,
+ vmdk.VMwareEsxVmdkDriver._get_disk_type,
+ volume)
def test_init_conn_with_instance_no_backing(self):
"""Test initialize_connection with instance and without backing."""
m.UnsetStubs()
m.VerifyAll()
+ @mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
+ @mock.patch('cinder.openstack.common.uuidutils.generate_uuid')
+ @mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
+ @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs')
+ @mock.patch.object(VMDK_DRIVER, 'volumeops')
+ @mock.patch.object(VMDK_DRIVER, 'ds_sel')
+ def test_retype(self, ds_sel, vops, get_volume_type_extra_specs,
+ get_volume_group_folder, generate_uuid,
+ delete_temp_backing):
+ self._test_retype(ds_sel, vops, get_volume_type_extra_specs,
+ get_volume_group_folder, generate_uuid,
+ delete_temp_backing)
+
+ def _test_retype(self, ds_sel, vops, get_volume_type_extra_specs,
+ get_volume_group_folder, genereate_uuid,
+ delete_temp_backing):
+ self._driver._storage_policy_enabled = True
+ context = mock.sentinel.context
+ diff = mock.sentinel.diff
+ host = mock.sentinel.host
+ new_type = {'id': 'abc'}
+
+ # Test with in-use volume.
+ vol = {'size': 1, 'status': 'retyping', 'name': 'vol-1',
+ 'volume_type_id': 'def', 'instance_uuid': '583a8dbb'}
+ self.assertFalse(self._driver.retype(context, vol, new_type, diff,
+ host))
+
+ # Test with no backing.
+ vops.get_backing.return_value = None
+ vol['instance_uuid'] = None
+ self.assertTrue(self._driver.retype(context, vol, new_type, diff,
+ host))
+
+ # Test with no disk type conversion, no profile change and
+ # compliant datastore.
+ ds_value = mock.sentinel.datastore_value
+ datastore = mock.Mock(value=ds_value)
+ vops.get_datastore.return_value = datastore
+
+ backing = mock.sentinel.backing
+ vops.get_backing.return_value = backing
+
+ get_volume_type_extra_specs.side_effect = [vmdk.THIN_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ None,
+ None]
+ ds_sel.is_datastore_compliant.return_value = True
+ self.assertTrue(self._driver.retype(context, vol, new_type, diff,
+ host))
+
+ # Test with no disk type conversion, profile change and
+ # compliant datastore.
+ new_profile = mock.sentinel.new_profile
+ get_volume_type_extra_specs.side_effect = [vmdk.THIN_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ 'gold-1',
+ new_profile]
+ ds_sel.is_datastore_compliant.return_value = True
+ profile_id = mock.sentinel.profile_id
+ ds_sel.get_profile_id.return_value = profile_id
+
+ self.assertTrue(self._driver.retype(context, vol, new_type, diff,
+ host))
+ vops.change_backing_profile.assert_called_once_with(backing,
+ profile_id)
+
+ # Test with disk type conversion, profile change and a backing with
+ # snapshots. Also test the no candidate datastore case.
+ get_volume_type_extra_specs.side_effect = [vmdk.THICK_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ 'gold-1',
+ new_profile]
+ vops.snapshot_exists.return_value = True
+ ds_sel.select_datastore.return_value = ()
+
+ self.assertFalse(self._driver.retype(context, vol, new_type, diff,
+ host))
+ exp_req = {hub.DatastoreSelector.HARD_ANTI_AFFINITY_DS: [ds_value],
+ hub.DatastoreSelector.PROFILE_NAME: new_profile,
+ hub.DatastoreSelector.SIZE_BYTES: units.Gi}
+ ds_sel.select_datastore.assert_called_once_with(exp_req)
+
+ # Modify the previous case with a candidate datastore which is
+ # different than the backing's current datastore.
+ get_volume_type_extra_specs.side_effect = [vmdk.THICK_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ 'gold-1',
+ new_profile]
+ vops.snapshot_exists.return_value = True
+
+ host = mock.sentinel.host
+ rp = mock.sentinel.rp
+ candidate_ds = mock.Mock(value=mock.sentinel.candidate_ds_value)
+ summary = mock.Mock(datastore=candidate_ds)
+ ds_sel.select_datastore.return_value = (host, rp, summary)
+
+ folder = mock.sentinel.folder
+ get_volume_group_folder.return_value = folder
+
+ vops.change_backing_profile.reset_mock()
+
+ self.assertTrue(self._driver.retype(context, vol, new_type, diff,
+ host))
+ vops.relocate_backing.assert_called_once_with(
+ backing, candidate_ds, rp, host, vmdk.THIN_VMDK_TYPE)
+ vops.move_backing_to_folder.assert_called_once_with(backing, folder)
+ vops.change_backing_profile.assert_called_once_with(backing,
+ profile_id)
+
+ # Test with disk type conversion, profile change, backing with
+ # no snapshots and candidate datastore which is same as the backing
+ # datastore.
+ get_volume_type_extra_specs.side_effect = [vmdk.THICK_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ 'gold-1',
+ new_profile]
+ vops.snapshot_exists.return_value = False
+ summary.datastore = datastore
+
+ uuid = '025b654b-d4ed-47f9-8014-b71a7744eafc'
+ genereate_uuid.return_value = uuid
+
+ clone = mock.sentinel.clone
+ vops.clone_backing.return_value = clone
+
+ vops.change_backing_profile.reset_mock()
+
+ self.assertTrue(self._driver.retype(context, vol, new_type, diff,
+ host))
+ vops.rename_backing.assert_called_once_with(backing, uuid)
+ vops.clone_backing.assert_called_once_with(
+ vol['name'], backing, None, volumeops.FULL_CLONE_TYPE,
+ datastore, vmdk.THIN_VMDK_TYPE)
+ delete_temp_backing.assert_called_once_with(backing)
+ vops.change_backing_profile.assert_called_once_with(clone,
+ profile_id)
+
+ # Modify the previous case with exception during clone.
+ get_volume_type_extra_specs.side_effect = [vmdk.THICK_VMDK_TYPE,
+ vmdk.THIN_VMDK_TYPE,
+ 'gold-1',
+ new_profile]
+
+ vops.clone_backing.side_effect = error_util.VimException('error')
+
+ vops.rename_backing.reset_mock()
+ vops.change_backing_profile.reset_mock()
+
+ self.assertRaises(
+ error_util.VimException, self._driver.retype, context, vol,
+ new_type, diff, host)
+ exp_rename_calls = [mock.call(backing, uuid),
+ mock.call(backing, vol['name'])]
+ self.assertEqual(exp_rename_calls, vops.rename_backing.call_args_list)
+ self.assertFalse(vops.change_backing_profile.called)
+
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_vmdk_virtual_disk(self, volume_ops):
"""Test vmdk._extend_vmdk_virtual_disk."""
driver._select_datastore_summary.assert_called_once_with(size,
filtered_dss)
+ # Clear side effects.
+ driver._filter_ds_by_profile.side_effect = None
+
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_extend_vmdk_virtual_disk(self, volume_ops):
"""Test vmdk._extend_vmdk_virtual_disk."""
_extend_virtual_disk,
fetch_optimized_image)
+ @mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
+ @mock.patch('cinder.openstack.common.uuidutils.generate_uuid')
+ @mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
+ @mock.patch('cinder.volume.volume_types.get_volume_type_extra_specs')
+ @mock.patch.object(VMDK_DRIVER, 'volumeops')
+ @mock.patch.object(VMDK_DRIVER, 'ds_sel')
+ def test_retype(self, ds_sel, vops, get_volume_type_extra_specs,
+ get_volume_group_folder, generate_uuid,
+ delete_temp_backing):
+ self._test_retype(ds_sel, vops, get_volume_type_extra_specs,
+ get_volume_group_folder, generate_uuid,
+ delete_temp_backing)
+
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_extend_vmdk_virtual_disk')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
self.assertEqual('', backing.fileName)
self.assertEqual('persistent', backing.diskMode)
+ @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+ '_get_disk_device')
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
'_get_relocate_spec')
- def test_relocate_backing(self, get_relocate_spec):
+ def test_relocate_backing(self, get_relocate_spec, get_disk_device):
+ disk_device = mock.sentinel.disk_device
+ get_disk_device.return_value = disk_device
+
spec = mock.sentinel.relocate_spec
get_relocate_spec.return_value = spec
+
task = mock.sentinel.task
self.session.invoke_api.return_value = task
+
backing = mock.sentinel.backing
datastore = mock.sentinel.datastore
resource_pool = mock.sentinel.resource_pool
host = mock.sentinel.host
- self.vops.relocate_backing(backing, datastore, resource_pool, host)
+ disk_type = mock.sentinel.disk_type
+ self.vops.relocate_backing(backing, datastore, resource_pool, host,
+ disk_type)
# Verify calls
disk_move_type = 'moveAllDiskBackingsAndAllowSharing'
+ get_disk_device.assert_called_once_with(backing)
get_relocate_spec.assert_called_once_with(datastore, resource_pool,
- host, disk_move_type)
+ host, disk_move_type,
+ disk_type, disk_device)
self.session.invoke_api.assert_called_once_with(self.session.vim,
'RelocateVM_Task',
backing,
newName=new_name)
self.session.wait_for_task.assert_called_once_with(task)
+ def test_change_backing_profile(self):
+ # Test change to empty profile.
+ reconfig_spec = mock.Mock()
+ empty_profile_spec = mock.sentinel.empty_profile_spec
+ self.session.vim.client.factory.create.side_effect = [
+ reconfig_spec, empty_profile_spec]
+
+ task = mock.sentinel.task
+ self.session.invoke_api.return_value = task
+
+ backing = mock.sentinel.backing
+ unique_profile_id = mock.sentinel.unique_profile_id
+ profile_id = mock.Mock(uniqueId=unique_profile_id)
+ self.vops.change_backing_profile(backing, profile_id)
+
+ self.assertEqual([empty_profile_spec], reconfig_spec.vmProfile)
+ self.session.invoke_api.assert_called_once_with(self.session.vim,
+ "ReconfigVM_Task",
+ backing,
+ spec=reconfig_spec)
+ self.session.wait_for_task.assert_called_once_with(task)
+
+ # Test change to non-empty profile.
+ profile_spec = mock.Mock()
+ self.session.vim.client.factory.create.side_effect = [
+ reconfig_spec, profile_spec]
+
+ self.session.invoke_api.reset_mock()
+ self.session.wait_for_task.reset_mock()
+
+ self.vops.change_backing_profile(backing, profile_id)
+
+ self.assertEqual([profile_spec], reconfig_spec.vmProfile)
+ self.assertEqual(unique_profile_id,
+ reconfig_spec.vmProfile[0].profileId)
+ self.session.invoke_api.assert_called_once_with(self.session.vim,
+ "ReconfigVM_Task",
+ backing,
+ spec=reconfig_spec)
+ self.session.wait_for_task.assert_called_once_with(task)
+
+ # Clear side effects.
+ self.session.vim.client.factory.create.side_effect = None
+
def test_delete_file(self):
file_mgr = mock.sentinel.file_manager
self.session.vim.service_content.fileManager = file_mgr
from cinder.openstack.common import uuidutils
from cinder.volume import driver
from cinder.volume.drivers.vmware import api
+from cinder.volume.drivers.vmware import datastore as hub
from cinder.volume.drivers.vmware import error_util
from cinder.volume.drivers.vmware import vim
from cinder.volume.drivers.vmware import vim_util
# No storage policy based placement possible when connecting
# directly to ESX
self._storage_policy_enabled = False
+ self._ds_sel = None
@property
def session(self):
max_objects)
return self._volumeops
+ @property
+ def ds_sel(self):
+ if not self._ds_sel:
+ self._ds_sel = hub.DatastoreSelector(self.volumeops,
+ self.session)
+ return self._ds_sel
+
def do_setup(self, context):
"""Perform validations and establish connection to server.
{'datastore': best_summary, 'host_count': max_host_count})
return best_summary
+ def _get_extra_spec_storage_profile(self, type_id):
+ """Get storage profile name in the given volume type's extra spec.
+
+ If there is no storage profile in the extra spec, default is None.
+ """
+ return _get_volume_type_extra_spec(type_id, 'storage_profile')
+
def _get_storage_profile(self, volume):
"""Get storage profile associated with the given volume's volume_type.
:return: String value of storage profile if volume type is associated
and contains storage_profile extra_spec option; None otherwise
"""
- type_id = volume['volume_type_id']
- if type_id is None:
- return None
- return _get_volume_type_extra_spec(type_id, 'storage_profile')
+ return self._get_extra_spec_storage_profile(volume['volume_type_id'])
def _filter_ds_by_profile(self, datastores, storage_profile):
"""Filter out datastores that do not match given storage profile.
datastores)
return (folder, datastore_summary)
+ @staticmethod
+ def _get_extra_spec_disk_type(type_id):
+ """Get disk type from the given volume type's extra spec.
+
+ If there is no disk type option, default is THIN_VMDK_TYPE.
+ """
+ disk_type = _get_volume_type_extra_spec(type_id,
+ 'vmdk_type',
+ default_value=THIN_VMDK_TYPE)
+ volumeops.VirtualDiskType.validate(disk_type)
+ return disk_type
+
@staticmethod
def _get_disk_type(volume):
- """Get disk type from volume type.
+ """Get disk type from the given volume's volume type.
:param volume: Volume object
:return: Disk type
"""
- return _get_volume_type_extra_spec(volume['volume_type_id'],
- 'vmdk_type',
- (THIN_VMDK_TYPE, THICK_VMDK_TYPE,
- EAGER_ZEROED_THICK_VMDK_TYPE),
- THIN_VMDK_TYPE)
+ return VMwareEsxVmdkDriver._get_extra_spec_disk_type(
+ volume['volume_type_id'])
def _get_storage_profile_id(self, volume):
storage_profile = self._get_storage_profile(volume)
LOG.info(_("Done copying volume %(vol)s to a new image %(img)s") %
{'vol': volume['name'], 'img': image_meta['name']})
+ def _in_use(self, volume):
+ """Check if the given volume is in use."""
+ return volume['instance_uuid'] is not None
+
+ def retype(self, ctxt, volume, new_type, diff, host):
+ """Convert the volume to be of the new type.
+
+ The retype is performed only if the volume is not in use. Retype is NOP
+ if the backing doesn't exist. If disk type conversion is needed, the
+ volume is cloned. If disk type conversion is needed and the volume
+ contains snapshots, the backing is relocated instead of cloning. The
+ backing is also relocated if the current datastore is not compliant
+ with the new storage profile (if any). Finally, the storage profile of
+ the backing VM is updated.
+
+ :param ctxt: Context
+ :param volume: A dictionary describing the volume to retype
+ :param new_type: A dictionary describing the volume type to convert to
+ :param diff: A dictionary with the difference between the two types
+ :param host: A dictionary describing the host to migrate to, where
+ host['host'] is its name, and host['capabilities'] is a
+ dictionary of its reported capabilities (unused)
+ :returns: True if the retype occurred; False otherwise.
+ """
+ # Can't attempt retype if the volume is in use.
+ if self._in_use(volume):
+ LOG.warn(_("Volume: %s is in use, can't retype."),
+ volume['name'])
+ return False
+
+ # If the backing doesn't exist, retype is NOP.
+ backing = self.volumeops.get_backing(volume['name'])
+ if backing is None:
+ LOG.debug("Backing for volume: %s doesn't exist; retype is NOP.",
+ volume['name'])
+ return True
+
+ # Check whether we need disk type conversion.
+ disk_type = VMwareEsxVmdkDriver._get_disk_type(volume)
+ new_disk_type = VMwareEsxVmdkDriver._get_extra_spec_disk_type(
+ new_type['id'])
+ need_disk_type_conversion = disk_type != new_disk_type
+
+ # Check whether we need to relocate the backing. If the backing
+ # contains snapshots, relocate is the only way to achieve disk type
+ # conversion.
+ need_relocate = (need_disk_type_conversion and
+ self.volumeops.snapshot_exists(backing))
+
+ datastore = self.volumeops.get_datastore(backing)
+
+ # Check whether we need to change the storage profile.
+ need_profile_change = False
+ is_compliant = True
+ new_profile = None
+ if self._storage_policy_enabled:
+ profile = self._get_storage_profile(volume)
+ new_profile = self._get_extra_spec_storage_profile(new_type['id'])
+ need_profile_change = profile != new_profile
+ # The current datastore may be compliant with the new profile.
+ is_compliant = self.ds_sel.is_datastore_compliant(datastore,
+ new_profile)
+
+ # No need to relocate or clone if there is no disk type conversion and
+ # the current datastore is compliant with the new profile or storage
+ # policy is disabled.
+ if not need_disk_type_conversion and is_compliant:
+ LOG.debug("Backing: %(backing)s for volume: %(name)s doesn't need "
+ "disk type conversion.",
+ {'backing': backing,
+ 'name': volume['name']})
+ if self._storage_policy_enabled:
+ LOG.debug("Backing: %(backing)s for volume: %(name)s is "
+ "compliant with the new profile: %(new_profile)s.",
+ {'backing': backing,
+ 'name': volume['name'],
+ 'new_profile': new_profile})
+ else:
+ # Set requirements for datastore selection.
+ req = {}
+ req[hub.DatastoreSelector.SIZE_BYTES] = (volume['size'] *
+ units.Gi)
+
+ if need_relocate:
+ LOG.debug("Backing: %s should be relocated.", backing)
+ req[hub.DatastoreSelector.HARD_ANTI_AFFINITY_DS] = (
+ [datastore.value])
+
+ if need_profile_change:
+ LOG.debug("Backing: %(backing)s needs a profile change to: "
+ "%(profile)s.",
+ {'backing': backing,
+ 'profile': new_profile})
+ req[hub.DatastoreSelector.PROFILE_NAME] = new_profile
+
+ # Select datastore satisfying the requirements.
+ best_candidate = self.ds_sel.select_datastore(req)
+ if not best_candidate:
+ # No candidate datastores; can't retype.
+ LOG.warn(_("There are no datastores matching new requirements;"
+ " can't retype volume: %s."),
+ volume['name'])
+ return False
+
+ (host, rp, summary) = best_candidate
+ new_datastore = summary.datastore
+ if datastore.value != new_datastore.value:
+ # Datastore changed; relocate the backing.
+ LOG.debug("Backing: %s needs to be relocated for retype.",
+ backing)
+ self.volumeops.relocate_backing(
+ backing, new_datastore, rp, host, new_disk_type)
+
+ dc = self.volumeops.get_dc(rp)
+ folder = self._get_volume_group_folder(dc)
+ self.volumeops.move_backing_to_folder(backing, folder)
+ elif need_disk_type_conversion:
+ # Same datastore, but clone is needed for disk type conversion.
+ LOG.debug("Backing: %s needs to be cloned for retype.",
+ backing)
+
+ new_backing = None
+ renamed = False
+ tmp_name = uuidutils.generate_uuid()
+ try:
+ self.volumeops.rename_backing(backing, tmp_name)
+ renamed = True
+
+ new_backing = self.volumeops.clone_backing(
+ volume['name'], backing, None,
+ volumeops.FULL_CLONE_TYPE, datastore, new_disk_type)
+ self._delete_temp_backing(backing)
+ backing = new_backing
+ except error_util.VimException:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Error occurred while cloning backing:"
+ " %s during retype."),
+ backing)
+ if renamed:
+ LOG.debug("Undo rename of backing: %(backing)s; "
+ "changing name from %(new_name)s to "
+ "%(old_name)s.",
+ {'backing': backing,
+ 'new_name': tmp_name,
+ 'old_name': volume['name']})
+ try:
+ self.volumeops.rename_backing(backing,
+ volume['name'])
+ except error_util.VimException:
+ LOG.warn(_("Changing backing: %(backing)s name"
+ " from %(new_name)s to %(old_name)s"
+ " failed."),
+ {'backing': backing,
+ 'new_name': tmp_name,
+ 'old_name': volume['name']})
+
+ # Update the backing's storage profile if needed.
+ if need_profile_change:
+ profile_id = None
+ if new_profile is not None:
+ profile_id = self.ds_sel.get_profile_id(new_profile)
+ self.volumeops.change_backing_profile(backing, profile_id)
+
+ # Retype is done.
+ LOG.debug("Volume: %s retype is done.", volume['name'])
+ return True
+
def extend_volume(self, volume, new_size):
"""Extend vmdk to new_size.
LOG.debug("Spec for relocating the backing: %s.", relocate_spec)
return relocate_spec
- def relocate_backing(self, backing, datastore, resource_pool, host):
+ def relocate_backing(
+ self, backing, datastore, resource_pool, host, disk_type=None):
"""Relocates backing to the input datastore and resource pool.
The implementation uses moveAllDiskBackingsAndAllowSharing disk move
:param datastore: Reference to the datastore
:param resource_pool: Reference to the resource pool
:param host: Reference to the host
+ :param disk_type: destination disk type
"""
LOG.debug("Relocating backing: %(backing)s to datastore: %(ds)s "
- "and resource pool: %(rp)s." %
- {'backing': backing, 'ds': datastore, 'rp': resource_pool})
+ "and resource pool: %(rp)s with destination disk type: "
+ "%(disk_type)s.",
+ {'backing': backing,
+ 'ds': datastore,
+ 'rp': resource_pool,
+ 'disk_type': disk_type})
# Relocate the volume backing
disk_move_type = 'moveAllDiskBackingsAndAllowSharing'
+
+ disk_device = None
+ if disk_type is not None:
+ disk_device = self._get_disk_device(backing)
+
relocate_spec = self._get_relocate_spec(datastore, resource_pool, host,
- disk_move_type)
+ disk_move_type, disk_type,
+ disk_device)
+
task = self._session.invoke_api(self._session.vim, 'RelocateVM_Task',
backing, spec=relocate_spec)
LOG.debug("Initiated relocation of volume backing: %s." % backing)
LOG.info(_("Successfully created clone: %s.") % new_backing)
return new_backing
+ def _reconfigure_backing(self, backing, reconfig_spec):
+ """Reconfigure backing VM with the given spec."""
+ LOG.debug("Reconfiguring backing VM: %(backing)s with spec: %(spec)s.",
+ {'backing': backing,
+ 'spec': reconfig_spec})
+ reconfig_task = self._session.invoke_api(self._session.vim,
+ "ReconfigVM_Task",
+ backing,
+ spec=reconfig_spec)
+ LOG.debug("Task: %s created for reconfiguring backing VM.",
+ reconfig_task)
+ self._session.wait_for_task(reconfig_task)
+
def attach_disk_to_backing(self, backing, size_in_kb, disk_type,
adapter_type, vmdk_ds_file_path):
"""Attach an existing virtual disk to the backing VM.
:param vmdk_ds_file_path: datastore file path of the virtual disk to
be attached
"""
+ LOG.debug("Reconfiguring backing VM: %(backing)s to add new disk: "
+ "%(path)s with size (KB): %(size)d and adapter type: "
+ "%(adapter_type)s.",
+ {'backing': backing,
+ 'path': vmdk_ds_file_path,
+ 'size': size_in_kb,
+ 'adapter_type': adapter_type})
cf = self._session.vim.client.factory
reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec')
specs = self._create_specs_for_disk_add(size_in_kb,
adapter_type,
vmdk_ds_file_path)
reconfig_spec.deviceChange = specs
- LOG.debug("Reconfiguring backing VM: %(backing)s with spec: %(spec)s.",
- {'backing': backing,
- 'spec': reconfig_spec})
- reconfig_task = self._session.invoke_api(self._session.vim,
- "ReconfigVM_Task",
- backing,
- spec=reconfig_spec)
- LOG.debug("Task: %s created for reconfiguring backing VM.",
- reconfig_task)
- self._session.wait_for_task(reconfig_task)
+ self._reconfigure_backing(backing, reconfig_spec)
LOG.debug("Backing VM: %s reconfigured with new disk.", backing)
def rename_backing(self, backing, new_name):
{'backing': backing,
'new_name': new_name})
+ def change_backing_profile(self, backing, profile_id):
+ """Change storage profile of the backing VM.
+
+ The current profile is removed if the new profile is None.
+ """
+ LOG.debug("Reconfiguring backing VM: %(backing)s to change profile to:"
+ " %(profile)s.",
+ {'backing': backing,
+ 'profile': profile_id})
+ cf = self._session.vim.client.factory
+ reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec')
+
+ if profile_id is None:
+ vm_profile = cf.create('ns0:VirtualMachineEmptyProfileSpec')
+ vm_profile.dynamicType = 'profile'
+ else:
+ vm_profile = cf.create('ns0:VirtualMachineDefinedProfileSpec')
+ vm_profile.profileId = profile_id.uniqueId
+
+ reconfig_spec.vmProfile = [vm_profile]
+ self._reconfigure_backing(backing, reconfig_spec)
+ LOG.debug("Backing VM: %(backing)s reconfigured with new profile: "
+ "%(profile)s.",
+ {'backing': backing,
+ 'profile': profile_id})
+
def delete_file(self, file_path, datacenter=None):
"""Delete file or folder on the datastore.