From cb98bb48f1af590ecdc0f5e064fdad31ef61eec8 Mon Sep 17 00:00:00 2001 From: Vipin Balachandran Date: Mon, 22 Jun 2015 19:56:09 +0530 Subject: [PATCH] VMware: Add volume ID in vCenter's volume config HP data protector's backup of vCenter inventory is failing for 'in-use' Cinder volumes because the vmdk is attached to two VMs (Nova instance and the backing VM corresponding to Cinder volume). The solution is to skip the backing VM during data protector's backup. This patch adds key 'cinder.volume.id' with value set to volume UUID in volume's vCenter config file so that other vCenter solutions can uniquely identify Cinder volumes. Closes-Bug: #1484012 Change-Id: I08a5f1a39def14f164ab3ca08a480310ce0b79ca --- cinder/tests/unit/test_vmware_vmdk.py | 54 ++++++++---- cinder/tests/unit/test_vmware_volumeops.py | 97 +++++++++++++++++----- cinder/volume/drivers/vmware/vmdk.py | 57 ++++++++----- cinder/volume/drivers/vmware/volumeops.py | 75 +++++++++++++---- 4 files changed, 209 insertions(+), 74 deletions(-) diff --git a/cinder/tests/unit/test_vmware_vmdk.py b/cinder/tests/unit/test_vmware_vmdk.py index d28c5eb8b..e05f44f58 100644 --- a/cinder/tests/unit/test_vmware_vmdk.py +++ b/cinder/tests/unit/test_vmware_vmdk.py @@ -1056,7 +1056,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase): fake_vm_create_spec = mock.sentinel.spec fake_disk_type = 'thin' vol_name = 'fake_volume name' - vol_id = '12345' + vol_id = 'd11a82de-ddaa-448d-b50a-a255a7e61a1e' fake_volume = {'name': vol_name, 'id': vol_id, 'size': fake_volume_size, @@ -1095,12 +1095,14 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase): image_service.show.assert_called_with(fake_context, fake_image_id) _select_ds_for_volume.assert_called_with(fake_volume) get_profile_id.assert_called_once_with(fake_volume) + extra_config = {vmdk.EXTRA_CONFIG_VOLUME_ID_KEY: vol_id} volumeops.get_create_spec.assert_called_with(fake_volume['name'], 0, fake_disk_type, fake_summary.name, - profile_id, - adapter_type) + profileId=profile_id, + adapter_type=adapter_type, + extra_config=extra_config) self.assertTrue(download_image.called) download_image.assert_called_with(fake_context, timeout, image_service, @@ -1726,15 +1728,19 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase): context = mock.sentinel.context name = 'vm-1' - volume = {'name': 'vol-1', 'id': 1, 'size': 1} + volume = {'name': 'vol-1', + 'id': 'd11a82de-ddaa-448d-b50a-a255a7e61a1e', + 'size': 1} tmp_file_path = mock.sentinel.tmp_file_path file_size_bytes = units.Gi ret = self._driver._create_backing_from_stream_optimized_file( context, name, volume, tmp_file_path, file_size_bytes) self.assertEqual(vm_ref, ret) + extra_config = {vmdk.EXTRA_CONFIG_VOLUME_ID_KEY: volume['id']} vops.get_create_spec.assert_called_once_with( - name, 0, disk_type, summary.name, profile_id) + name, 0, disk_type, summary.name, profileId=profile_id, + extra_config=extra_config) file_open.assert_called_once_with(tmp_file_path, "rb") download_data.assert_called_once_with( context, self.IMG_TX_TIMEOUT, tmp_file, session=session, @@ -2054,6 +2060,7 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): """Test _clone_backing with clone type - linked.""" fake_size = 3 fake_volume = {'volume_type_id': None, 'name': 'fake_name', + 'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53', 'size': fake_size} fake_snapshot = {'volume_name': 'volume_name', 'name': 'snapshot_name', @@ -2063,13 +2070,15 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): self._driver._clone_backing(fake_volume, fake_backing, fake_snapshot, volumeops.LINKED_CLONE_TYPE, fake_snapshot['volume_size']) + extra_config = {vmdk.EXTRA_CONFIG_VOLUME_ID_KEY: fake_volume['id']} volume_ops.clone_backing.assert_called_with(fake_volume['name'], fake_backing, fake_snapshot, fake_type, None, host=None, - resource_pool=None) + resource_pool=None, + extra_config=extra_config) # 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'], @@ -2100,6 +2109,7 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): fake_summary.datastore = fake_datastore fake_size = 3 fake_volume = {'volume_type_id': None, 'name': 'fake_name', + 'id': '51e47214-8e3c-475d-b44b-aea6cd3eef53', 'size': fake_size} fake_snapshot = {'volume_name': 'volume_name', 'name': 'snapshot_name', 'volume_size': 2} @@ -2110,6 +2120,7 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): volumeops.FULL_CLONE_TYPE, fake_snapshot['volume_size']) _select_ds_for_volume.assert_called_with(fake_volume) + extra_config = {vmdk.EXTRA_CONFIG_VOLUME_ID_KEY: fake_volume['id']} volume_ops.clone_backing.assert_called_with(fake_volume['name'], fake_backing, fake_snapshot, @@ -2117,7 +2128,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): fake_datastore, host=fake_host, resource_pool= - fake_resource_pool) + fake_resource_pool, + extra_config=extra_config) # 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'], @@ -2552,16 +2564,20 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): select_ds_for_volume.return_value = (host, resource_pool, folder, summary) - volume = {'name': 'vol-1', 'volume_type_id': None, 'size': 1} + volume = {'name': 'vol-1', 'volume_type_id': None, 'size': 1, + 'id': 'd11a82de-ddaa-448d-b50a-a255a7e61a1e'} create_params = {vmdk.CREATE_PARAM_DISK_LESS: True} self._driver._create_backing(volume, host, create_params) - vops.create_backing_disk_less.assert_called_once_with('vol-1', - folder, - resource_pool, - host, - summary.name, - None) + extra_config = {vmdk.EXTRA_CONFIG_VOLUME_ID_KEY: volume['id']} + vops.create_backing_disk_less.assert_called_once_with( + 'vol-1', + folder, + resource_pool, + host, + summary.name, + profileId=None, + extra_config=extra_config) create_params = {vmdk.CREATE_PARAM_ADAPTER_TYPE: 'ide'} self._driver._create_backing(volume, host, create_params) @@ -2573,8 +2589,9 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): resource_pool, host, summary.name, - None, - 'ide') + profileId=None, + adapter_type='ide', + extra_config=extra_config) vops.create_backing.reset_mock() backing_name = "temp-vol" @@ -2588,8 +2605,9 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase): resource_pool, host, summary.name, - None, - 'lsiLogic') + profileId=None, + adapter_type='lsiLogic', + extra_config=extra_config) @mock.patch('cinder.openstack.common.fileutils.ensure_tree') @mock.patch('cinder.openstack.common.fileutils.delete_if_exists') diff --git a/cinder/tests/unit/test_vmware_volumeops.py b/cinder/tests/unit/test_vmware_volumeops.py index 1b871cfc5..d2510ad1c 100644 --- a/cinder/tests/unit/test_vmware_volumeops.py +++ b/cinder/tests/unit/test_vmware_volumeops.py @@ -661,18 +661,48 @@ class VolumeOpsTestCase(test.TestCase): name = mock.sentinel.name ds_name = mock.sentinel.ds_name profile_id = mock.sentinel.profile_id - ret = self.vops._get_create_spec_disk_less(name, ds_name, profile_id) + option_key = mock.sentinel.key + option_value = mock.sentinel.value + extra_config = {option_key: option_value} + ret = self.vops._get_create_spec_disk_less(name, ds_name, profile_id, + extra_config) factory.create.side_effect = None self.assertEqual(name, ret.name) self.assertEqual('[%s]' % ds_name, ret.files.vmPathName) self.assertEqual("vmx-08", ret.version) self.assertEqual(profile_id, ret.vmProfile[0].profileId) + self.assertEqual(1, len(ret.extraConfig)) + self.assertEqual(option_key, ret.extraConfig[0].key) + self.assertEqual(option_value, ret.extraConfig[0].value) expected = [mock.call.create('ns0:VirtualMachineFileInfo'), mock.call.create('ns0:VirtualMachineConfigSpec'), - mock.call.create('ns0:VirtualMachineDefinedProfileSpec')] + mock.call.create('ns0:VirtualMachineDefinedProfileSpec'), + mock.call.create('ns0:OptionValue')] factory.create.assert_has_calls(expected, any_order=True) + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' + '_get_create_spec_disk_less') + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' + '_create_specs_for_disk_add') + def test_get_create_spec(self, create_specs_for_disk_add, + get_create_spec_disk_less): + name = 'vol-1' + size_kb = 1024 + disk_type = 'thin' + ds_name = 'nfs-1' + profileId = mock.sentinel.profile_id + adapter_type = 'busLogic' + extra_config = mock.sentinel.extra_config + + self.vops.get_create_spec(name, size_kb, disk_type, ds_name, + profileId, adapter_type, extra_config) + + get_create_spec_disk_less.assert_called_once_with( + name, ds_name, profileId=profileId, extra_config=extra_config) + create_specs_for_disk_add.assert_called_once_with( + size_kb, disk_type, adapter_type) + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' 'get_create_spec') def test_create_backing(self, get_create_spec): @@ -692,13 +722,14 @@ class VolumeOpsTestCase(test.TestCase): host = mock.sentinel.host ds_name = mock.sentinel.ds_name profile_id = mock.sentinel.profile_id + extra_config = mock.sentinel.extra_config ret = self.vops.create_backing(name, size_kb, disk_type, folder, resource_pool, host, ds_name, - profile_id, adapter_type) + profile_id, adapter_type, extra_config) self.assertEqual(mock.sentinel.result, ret) - get_create_spec.assert_called_once_with(name, size_kb, disk_type, - ds_name, profile_id, - adapter_type) + get_create_spec.assert_called_once_with( + name, size_kb, disk_type, ds_name, profileId=profile_id, + adapter_type=adapter_type, extra_config=extra_config) self.session.invoke_api.assert_called_once_with(self.session.vim, 'CreateVM_Task', folder, @@ -723,12 +754,14 @@ class VolumeOpsTestCase(test.TestCase): host = mock.sentinel.host ds_name = mock.sentinel.ds_name profile_id = mock.sentinel.profile_id + extra_config = mock.sentinel.extra_config ret = self.vops.create_backing_disk_less(name, folder, resource_pool, - host, ds_name, profile_id) + host, ds_name, profile_id, + extra_config) self.assertEqual(mock.sentinel.result, ret) - get_create_spec_disk_less.assert_called_once_with(name, ds_name, - profile_id) + get_create_spec_disk_less.assert_called_once_with( + name, ds_name, profileId=profile_id, extra_config=extra_config) self.session.invoke_api.assert_called_once_with(self.session.vim, 'CreateVM_Task', folder, @@ -988,6 +1021,11 @@ class VolumeOpsTestCase(test.TestCase): self.assertEqual(folder, ret) get_parent.assert_called_once_with(backing, 'Folder') + def _verify_extra_config(self, option_values, key, value): + self.assertEqual(1, len(option_values)) + self.assertEqual(key, option_values[0].key) + self.assertEqual(value, option_values[0].value) + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' '_get_relocate_spec') @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' @@ -998,28 +1036,38 @@ class VolumeOpsTestCase(test.TestCase): relocate_spec = mock.sentinel.relocate_spec get_relocate_spec.return_value = relocate_spec + # Test with empty disk type. datastore = mock.sentinel.datastore disk_move_type = mock.sentinel.disk_move_type snapshot = mock.sentinel.snapshot disk_type = None backing = mock.sentinel.backing + host = mock.sentinel.host + rp = mock.sentinel.rp + key = mock.sentinel.key + value = mock.sentinel.value + extra_config = {key: value} ret = self.vops._get_clone_spec(datastore, disk_move_type, snapshot, - backing, disk_type) + backing, disk_type, host, rp, + extra_config) self.assertEqual(relocate_spec, ret.location) self.assertFalse(ret.powerOn) self.assertFalse(ret.template) self.assertEqual(snapshot, ret.snapshot) - get_relocate_spec.assert_called_once_with(datastore, None, None, + get_relocate_spec.assert_called_once_with(datastore, rp, host, disk_move_type, disk_type, None) + self._verify_extra_config(ret.config.extraConfig, key, value) + # Test with non-empty disk type. disk_device = mock.sentinel.disk_device get_disk_device.return_value = disk_device disk_type = 'thin' ret = self.vops._get_clone_spec(datastore, disk_move_type, snapshot, - backing, disk_type) + backing, disk_type, host, rp, + extra_config) factory.create.side_effect = None self.assertEqual(relocate_spec, ret.location) @@ -1027,9 +1075,10 @@ class VolumeOpsTestCase(test.TestCase): self.assertFalse(ret.template) self.assertEqual(snapshot, ret.snapshot) get_disk_device.assert_called_once_with(backing) - get_relocate_spec.assert_called_with(datastore, None, None, + get_relocate_spec.assert_called_with(datastore, rp, host, disk_move_type, disk_type, disk_device) + self._verify_extra_config(ret.config.extraConfig, key, value) @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' '_get_clone_spec') @@ -1056,8 +1105,9 @@ class VolumeOpsTestCase(test.TestCase): # verify calls self.assertEqual(mock.sentinel.new_backing, ret) disk_move_type = 'moveAllDiskBackingsAndDisallowSharing' - get_clone_spec.assert_called_with(datastore, disk_move_type, snapshot, - backing, None, None, None) + get_clone_spec.assert_called_with( + datastore, disk_move_type, snapshot, backing, None, host=None, + resource_pool=None, extra_config=None) expected = [mock.call(vim_util, 'get_object_property', self.session.vim, backing, 'parent'), mock.call(self.session.vim, 'CloneVM_Task', backing, @@ -1072,27 +1122,32 @@ class VolumeOpsTestCase(test.TestCase): # verify calls self.assertEqual(mock.sentinel.new_backing, ret) disk_move_type = 'createNewChildDiskBacking' - get_clone_spec.assert_called_with(datastore, disk_move_type, snapshot, - backing, None, None, None) + get_clone_spec.assert_called_with( + datastore, disk_move_type, snapshot, backing, None, host=None, + resource_pool=None, extra_config=None) expected = [mock.call(vim_util, 'get_object_property', self.session.vim, backing, 'parent'), mock.call(self.session.vim, 'CloneVM_Task', backing, folder=folder, name=name, spec=clone_spec)] self.assertEqual(expected, self.session.invoke_api.mock_calls) - # Test disk type conversion and target host. + # Test with optional params (disk_type, host, resource_pool and + # extra_config). clone_type = None disk_type = 'thin' host = mock.sentinel.host rp = mock.sentinel.rp + extra_config = mock.sentinel.extra_config self.session.invoke_api.reset_mock() ret = self.vops.clone_backing(name, backing, snapshot, clone_type, - datastore, disk_type, host, rp) + datastore, disk_type, host, rp, + extra_config) self.assertEqual(mock.sentinel.new_backing, ret) disk_move_type = 'moveAllDiskBackingsAndDisallowSharing' - get_clone_spec.assert_called_with(datastore, disk_move_type, snapshot, - backing, disk_type, host, rp) + get_clone_spec.assert_called_with( + datastore, disk_move_type, snapshot, backing, disk_type, host=host, + resource_pool=rp, extra_config=extra_config) expected = [mock.call(vim_util, 'get_object_property', self.session.vim, backing, 'parent'), mock.call(self.session.vim, 'CloneVM_Task', backing, diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index 79edb36f0..d29a056d8 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -60,6 +60,8 @@ CREATE_PARAM_BACKING_NAME = 'name' TMP_IMAGES_DATASTORE_FOLDER_PATH = "cinder_temp/" +EXTRA_CONFIG_VOLUME_ID_KEY = "cinder.volume.id" + vmdk_opts = [ cfg.StrOpt('vmware_host_ip', default=None, @@ -414,6 +416,9 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): profile_id = profile.uniqueId return profile_id + def _get_extra_config(self, volume): + return {EXTRA_CONFIG_VOLUME_ID_KEY: volume['id']} + def _create_backing(self, volume, host=None, create_params=None): """Create volume backing under the given host. @@ -436,17 +441,21 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): backing_name = create_params.get(CREATE_PARAM_BACKING_NAME, volume['name']) + extra_config = self._get_extra_config(volume) + # default is a backing with single disk disk_less = create_params.get(CREATE_PARAM_DISK_LESS, False) if disk_less: # create a disk-less backing-- disk can be added later; for e.g., # by copying an image - return self.volumeops.create_backing_disk_less(backing_name, - folder, - resource_pool, - host_ref, - summary.name, - profile_id) + return self.volumeops.create_backing_disk_less( + backing_name, + folder, + resource_pool, + host_ref, + summary.name, + profileId=profile_id, + extra_config=extra_config) # create a backing with single disk disk_type = VMwareEsxVmdkDriver._get_disk_type(volume) @@ -460,8 +469,9 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): resource_pool, host_ref, summary.name, - profile_id, - adapter_type) + profileId=profile_id, + adapter_type=adapter_type, + extra_config=extra_config) def _relocate_backing(self, volume, backing, host): pass @@ -1113,12 +1123,15 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): # The size of stream optimized glance image is often suspect, # so better let VC figure out the disk capacity during import. dummy_disk_size = 0 - vm_create_spec = self.volumeops.get_create_spec(volume['name'], - dummy_disk_size, - disk_type, - summary.name, - profile_id, - adapter_type) + extra_config = self._get_extra_config(volume) + vm_create_spec = self.volumeops.get_create_spec( + volume['name'], + dummy_disk_size, + disk_type, + summary.name, + profileId=profile_id, + adapter_type=adapter_type, + extra_config=extra_config) # convert vm_create_spec to vm_import_spec cf = self.session.vim.client.factory vm_import_spec = cf.create('ns0:VirtualMachineImportSpec') @@ -1619,11 +1632,13 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver): profile_id = self._get_storage_profile_id(volume) disk_type = VMwareEsxVmdkDriver._get_disk_type(volume) - vm_create_spec = self.volumeops.get_create_spec(name, - 0, - disk_type, - summary.name, - profile_id) + extra_config = self._get_extra_config(volume) + # We cannot determine the size of a virtual disk created from + # streamOptimized disk image. Set size to 0 and let vCenter + # figure out the size after virtual disk creation. + vm_create_spec = self.volumeops.get_create_spec( + name, 0, disk_type, summary.name, profileId=profile_id, + extra_config=extra_config) vm_import_spec.configSpec = vm_create_spec timeout = self.configuration.vmware_image_transfer_timeout_secs @@ -1954,9 +1969,11 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver): # Pick a datastore where to create the full clone under any host (host, rp, _folder, summary) = self._select_ds_for_volume(volume) datastore = summary.datastore + extra_config = self._get_extra_config(volume) clone = self.volumeops.clone_backing(volume['name'], backing, snapshot, clone_type, datastore, - host=host, resource_pool=rp) + host=host, resource_pool=rp, + extra_config=extra_config) # 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 diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py index 4e4e61e0b..0f0b30bd8 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -23,6 +23,7 @@ from oslo_utils import units from oslo_vmware import exceptions from oslo_vmware import pbm from oslo_vmware import vim_util +import six from six.moves import urllib from cinder.i18n import _, _LE, _LI @@ -682,12 +683,28 @@ class VMwareVolumeOps(object): specs.append(controller_spec) return specs - def _get_create_spec_disk_less(self, name, ds_name, profileId=None): + def _get_extra_config_option_values(self, extra_config): + + cf = self._session.vim.client.factory + option_values = [] + + for key, value in six.iteritems(extra_config): + opt = cf.create('ns0:OptionValue') + opt.key = key + opt.value = value + option_values.append(opt) + + return option_values + + def _get_create_spec_disk_less(self, name, ds_name, profileId=None, + extra_config=None): """Return spec for creating disk-less backing. :param name: Name of the backing :param ds_name: Datastore name where the disk is to be provisioned - :param profileId: storage profile ID for the backing + :param profileId: Storage profile ID for the backing + :param extra_config: Key-value pairs to be written to backing's + extra-config :return: Spec for creation """ cf = self._session.vim.client.factory @@ -711,10 +728,15 @@ class VMwareVolumeOps(object): vmProfile.profileId = profileId create_spec.vmProfile = [vmProfile] + if extra_config: + create_spec.extraConfig = self._get_extra_config_option_values( + extra_config) + return create_spec def get_create_spec(self, name, size_kb, disk_type, ds_name, - profileId=None, adapter_type='lsiLogic'): + profileId=None, adapter_type='lsiLogic', + extra_config=None): """Return spec for creating backing with a single disk. :param name: name of the backing @@ -723,9 +745,12 @@ class VMwareVolumeOps(object): :param ds_name: datastore name where the disk is to be provisioned :param profileId: storage profile ID for the backing :param adapter_type: disk adapter type + :param extra_config: key-value pairs to be written to backing's + extra-config :return: spec for creation """ - create_spec = self._get_create_spec_disk_less(name, ds_name, profileId) + create_spec = self._get_create_spec_disk_less( + name, ds_name, profileId=profileId, extra_config=extra_config) create_spec.deviceChange = self._create_specs_for_disk_add( size_kb, disk_type, adapter_type) return create_spec @@ -742,7 +767,8 @@ class VMwareVolumeOps(object): return backing def create_backing(self, name, size_kb, disk_type, folder, resource_pool, - host, ds_name, profileId=None, adapter_type='lsiLogic'): + host, ds_name, profileId=None, adapter_type='lsiLogic', + extra_config=None): """Create backing for the volume. Creates a VM with one VMDK based on the given inputs. @@ -754,8 +780,10 @@ class VMwareVolumeOps(object): :param resource_pool: Resource pool reference :param host: Host reference :param ds_name: Datastore name where the disk is to be provisioned - :param profileId: storage profile ID to be associated with backing + :param profileId: Storage profile ID to be associated with backing :param adapter_type: Disk adapter type + :param extra_config: Key-value pairs to be written to backing's + extra-config :return: Reference to the created backing entity """ LOG.debug("Creating volume backing with name: %(name)s " @@ -768,13 +796,15 @@ class VMwareVolumeOps(object): 'ds_name': ds_name, 'profile': profileId, 'host': host, 'adapter_type': adapter_type}) - create_spec = self.get_create_spec(name, size_kb, disk_type, ds_name, - profileId, adapter_type) + create_spec = self.get_create_spec( + name, size_kb, disk_type, ds_name, profileId=profileId, + adapter_type=adapter_type, extra_config=extra_config) return self._create_backing_int(folder, resource_pool, host, create_spec) def create_backing_disk_less(self, name, folder, resource_pool, - host, ds_name, profileId=None): + host, ds_name, profileId=None, + extra_config=None): """Create disk-less volume backing. This type of backing is useful for creating volume from image. The @@ -787,6 +817,8 @@ class VMwareVolumeOps(object): :param host: Host reference :param ds_name: Name of the datastore used for VM storage :param profileId: Storage profile ID to be associated with backing + :param extra_config: Key-value pairs to be written to backing's + extra-config :return: Reference to the created backing entity """ LOG.debug("Creating disk-less volume backing with name: %(name)s " @@ -797,7 +829,8 @@ class VMwareVolumeOps(object): 'resource_pool': resource_pool, 'host': host, 'ds_name': ds_name}) - create_spec = self._get_create_spec_disk_less(name, ds_name, profileId) + create_spec = self._get_create_spec_disk_less( + name, ds_name, profileId=profileId, extra_config=extra_config) return self._create_backing_int(folder, resource_pool, host, create_spec) @@ -1023,7 +1056,8 @@ class VMwareVolumeOps(object): return self._get_parent(backing, 'Folder') def _get_clone_spec(self, datastore, disk_move_type, snapshot, backing, - disk_type, host=None, resource_pool=None): + disk_type, host=None, resource_pool=None, + extra_config=None): """Get the clone spec. :param datastore: Reference to datastore @@ -1033,6 +1067,8 @@ class VMwareVolumeOps(object): :param disk_type: Disk type of clone :param host: Target host :param resource_pool: Target resource pool + :param extra_config: Key-value pairs to be written to backing's + extra-config :return: Clone spec """ if disk_type is not None: @@ -1050,11 +1086,18 @@ class VMwareVolumeOps(object): clone_spec.template = False clone_spec.snapshot = snapshot + if extra_config: + config_spec = cf.create('ns0:VirtualMachineConfigSpec') + config_spec.extraConfig = self._get_extra_config_option_values( + extra_config) + clone_spec.config = config_spec + LOG.debug("Spec for cloning the backing: %s.", clone_spec) return clone_spec def clone_backing(self, name, backing, snapshot, clone_type, datastore, - disk_type=None, host=None, resource_pool=None): + disk_type=None, host=None, resource_pool=None, + extra_config=None): """Clone backing. If the clone_type is 'full', then a full clone of the source volume @@ -1069,6 +1112,8 @@ class VMwareVolumeOps(object): :param disk_type: Disk type of the clone :param host: Target host :param resource_pool: Target resource pool + :param extra_config: Key-value pairs to be written to backing's + extra-config """ LOG.debug("Creating a clone of backing: %(back)s, named: %(name)s, " "clone type: %(type)s from snapshot: %(snap)s on " @@ -1082,9 +1127,9 @@ class VMwareVolumeOps(object): disk_move_type = 'createNewChildDiskBacking' else: disk_move_type = 'moveAllDiskBackingsAndDisallowSharing' - clone_spec = self._get_clone_spec(datastore, disk_move_type, snapshot, - backing, disk_type, host, - resource_pool) + clone_spec = self._get_clone_spec( + datastore, disk_move_type, snapshot, backing, disk_type, host=host, + resource_pool=resource_pool, extra_config=extra_config) task = self._session.invoke_api(self._session.vim, 'CloneVM_Task', backing, folder=folder, name=name, spec=clone_spec) -- 2.45.2