From a8e965c3d4723ecb1fb0db2c85439522f07d46cd Mon Sep 17 00:00:00 2001 From: Vipin Balachandran Date: Fri, 29 Jan 2016 04:19:17 -0800 Subject: [PATCH] VMware: manage_existing for VMDK driver This patch implements manage_existing for the VMDK driver. Manage existing creates a volume backing from the vmdk identified by the source-name option. The source-name format is: "vmdk_path@vm_inventory_path". This patch detaches the disk device at vmdk_path from the VM at vCenter inventory path given by vm_inventory_path and attaches it to the volume backing. Implements: blueprint vmdk-manage-existing Change-Id: I03501bb9bcc9932211174d5a00511af347befd96 --- cinder/tests/unit/test_vmware_vmdk.py | 47 +++++++++++++++ cinder/tests/unit/test_vmware_volumeops.py | 58 +++++++++++++++++++ cinder/volume/drivers/vmware/vmdk.py | 39 +++++++++++++ cinder/volume/drivers/vmware/volumeops.py | 46 +++++++++++++++ ...vmdk-manage-existing-0edc20d9d4d19172.yaml | 3 + 5 files changed, 193 insertions(+) create mode 100644 releasenotes/notes/vmware-vmdk-manage-existing-0edc20d9d4d19172.yaml diff --git a/cinder/tests/unit/test_vmware_vmdk.py b/cinder/tests/unit/test_vmware_vmdk.py index 2e514bbdc..94e338cc5 100644 --- a/cinder/tests/unit/test_vmware_vmdk.py +++ b/cinder/tests/unit/test_vmware_vmdk.py @@ -2413,6 +2413,53 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): existing_ref)) get_existing.assert_called_once_with(existing_ref) + @mock.patch.object(VMDK_DRIVER, '_get_existing') + @mock.patch.object(VMDK_DRIVER, '_create_backing') + @mock.patch.object(VMDK_DRIVER, 'volumeops') + @mock.patch.object(VMDK_DRIVER, '_get_ds_name_folder_path') + @mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.' + '_get_disk_type') + def test_manage_existing( + self, get_disk_type, get_ds_name_folder_path, vops, + create_backing, get_existing): + + vm = mock.sentinel.vm + src_path = mock.sentinel.src_path + disk_backing = mock.Mock(fileName=src_path) + disk_device = mock.Mock(backing=disk_backing, capacityInKB=1048576) + get_existing.return_value = (vm, disk_device) + + backing = mock.sentinel.backing + create_backing.return_value = backing + + src_dc = mock.sentinel.src_dc + dest_dc = mock.sentinel.dest_dc + vops.get_dc.side_effect = [src_dc, dest_dc] + + volume = self._create_volume_dict() + ds_name = "ds1" + folder_path = "%s/" % volume['name'] + get_ds_name_folder_path.return_value = (ds_name, folder_path) + + disk_type = mock.sentinel.disk_type + get_disk_type.return_value = disk_type + + existing_ref = mock.sentinel.existing_ref + self._driver.manage_existing(volume, existing_ref) + + get_existing.assert_called_once_with(existing_ref) + create_backing.assert_called_once_with( + volume, create_params={vmdk.CREATE_PARAM_DISK_LESS: True}) + vops.detach_disk_from_backing.assert_called_once_with(vm, disk_device) + dest_path = "[%s] %s%s.vmdk" % (ds_name, folder_path, volume['name']) + vops.move_vmdk_file.assert_called_once_with( + src_dc, src_path, dest_path, dest_dc_ref=dest_dc) + vops.attach_disk_to_backing.assert_called_once_with( + backing, disk_device.capacityInKB, disk_type, 'lsiLogic', + dest_path) + vops.update_backing_disk_uuid.assert_called_once_with(backing, + volume['id']) + @mock.patch('oslo_vmware.api.VMwareAPISession') def test_session(self, apiSession): self._session = None diff --git a/cinder/tests/unit/test_vmware_volumeops.py b/cinder/tests/unit/test_vmware_volumeops.py index 056a065ff..154d64a19 100644 --- a/cinder/tests/unit/test_vmware_volumeops.py +++ b/cinder/tests/unit/test_vmware_volumeops.py @@ -1326,6 +1326,39 @@ class VolumeOpsTestCase(test.TestCase): spec=reconfig_spec) self.session.wait_for_task.assert_called_once_with(task) + def test_create_spec_for_disk_remove(self): + disk_spec = mock.Mock() + self.session.vim.client.factory.create.return_value = disk_spec + + disk_device = mock.sentinel.disk_device + self.vops._create_spec_for_disk_remove(disk_device) + + self.session.vim.client.factory.create.assert_called_once_with( + 'ns0:VirtualDeviceConfigSpec') + self.assertEqual('remove', disk_spec.operation) + self.assertEqual(disk_device, disk_spec.device) + + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' + '_create_spec_for_disk_remove') + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' + '_reconfigure_backing') + def test_detach_disk_from_backing(self, reconfigure_backing, create_spec): + disk_spec = mock.sentinel.disk_spec + create_spec.return_value = disk_spec + + reconfig_spec = mock.Mock() + self.session.vim.client.factory.create.return_value = reconfig_spec + + backing = mock.sentinel.backing + disk_device = mock.sentinel.disk_device + self.vops.detach_disk_from_backing(backing, disk_device) + + create_spec.assert_called_once_with(disk_device) + self.session.vim.client.factory.create.assert_called_once_with( + 'ns0:VirtualMachineConfigSpec') + self.assertEqual([disk_spec], reconfig_spec.deviceChange) + reconfigure_backing.assert_called_once_with(backing, reconfig_spec) + def test_rename_backing(self): task = mock.sentinel.task self.session.invoke_api.return_value = task @@ -1653,6 +1686,31 @@ class VolumeOpsTestCase(test.TestCase): force=True) self.session.wait_for_task.assert_called_once_with(task) + def test_move_vmdk_file(self): + task = mock.sentinel.task + invoke_api = self.session.invoke_api + invoke_api.return_value = task + + disk_mgr = self.session.vim.service_content.virtualDiskManager + src_dc_ref = mock.sentinel.src_dc_ref + src_vmdk_file_path = mock.sentinel.src_vmdk_file_path + dest_dc_ref = mock.sentinel.dest_dc_ref + dest_vmdk_file_path = mock.sentinel.dest_vmdk_file_path + self.vops.move_vmdk_file(src_dc_ref, + src_vmdk_file_path, + dest_vmdk_file_path, + dest_dc_ref=dest_dc_ref) + + invoke_api.assert_called_once_with(self.session.vim, + 'MoveVirtualDisk_Task', + disk_mgr, + sourceName=src_vmdk_file_path, + sourceDatacenter=src_dc_ref, + destName=dest_vmdk_file_path, + destDatacenter=dest_dc_ref, + force=True) + self.session.wait_for_task.assert_called_once_with(task) + def test_delete_vmdk_file(self): task = mock.sentinel.task invoke_api = self.session.invoke_api diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index e5fb5abc5..dd1327ee4 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -1694,6 +1694,45 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): (_vm, disk) = self._get_existing(existing_ref) return int(math.ceil(disk.capacityInKB * units.Ki / float(units.Gi))) + def manage_existing(self, volume, existing_ref): + """Brings an existing virtual disk under Cinder management. + + Detaches the virtual disk identified by existing_ref and attaches + it to a volume backing. + + :param volume: Cinder volume to manage + :param existing_ref: Driver-specific information used to identify a + volume + """ + (vm, disk) = self._get_existing(existing_ref) + + # Create a backing for the volume. + create_params = {CREATE_PARAM_DISK_LESS: True} + backing = self._create_backing(volume, create_params=create_params) + + # Detach the disk to be managed from the source VM. + self.volumeops.detach_disk_from_backing(vm, disk) + + # Move the disk to the datastore folder of volume backing. + src_dc = self.volumeops.get_dc(vm) + dest_dc = self.volumeops.get_dc(backing) + (ds_name, folder_path) = self._get_ds_name_folder_path(backing) + dest_path = volumeops.VirtualDiskPath( + ds_name, folder_path, volume['name']) + self.volumeops.move_vmdk_file(src_dc, + disk.backing.fileName, + dest_path.get_descriptor_ds_file_path(), + dest_dc_ref=dest_dc) + + # Attach the disk to be managed to volume backing. + self.volumeops.attach_disk_to_backing( + backing, + disk.capacityInKB, + VMwareVcVmdkDriver._get_disk_type(volume), + 'lsiLogic', + dest_path.get_descriptor_ds_file_path()) + self.volumeops.update_backing_disk_uuid(backing, volume['id']) + @property def session(self): if not self._session: diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py index 4454b92af..f9d8a9743 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -1228,6 +1228,26 @@ class VMwareVolumeOps(object): self._reconfigure_backing(backing, reconfig_spec) LOG.debug("Backing VM: %s reconfigured with new disk.", backing) + def _create_spec_for_disk_remove(self, disk_device): + cf = self._session.vim.client.factory + disk_spec = cf.create('ns0:VirtualDeviceConfigSpec') + disk_spec.operation = 'remove' + disk_spec.device = disk_device + return disk_spec + + def detach_disk_from_backing(self, backing, disk_device): + """Detach the given disk from backing.""" + + LOG.debug("Reconfiguring backing VM: %(backing)s to remove disk: " + "%(disk_device)s.", + {'backing': backing, 'disk_device': disk_device}) + + cf = self._session.vim.client.factory + reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec') + spec = self._create_spec_for_disk_remove(disk_device) + reconfig_spec.deviceChange = [spec] + self._reconfigure_backing(backing, reconfig_spec) + def rename_backing(self, backing, new_name): """Rename backing VM. @@ -1495,6 +1515,32 @@ class VMwareVolumeOps(object): LOG.info(_LI("Successfully copied disk at: %(src)s to: %(dest)s."), {'src': src_vmdk_file_path, 'dest': dest_vmdk_file_path}) + def move_vmdk_file(self, src_dc_ref, src_vmdk_file_path, + dest_vmdk_file_path, dest_dc_ref=None): + """Move the given vmdk file to another datastore location. + + :param src_dc_ref: Reference to datacenter containing src datastore + :param src_vmdk_file_path: Source vmdk file path + :param dest_vmdk_file_path: Destination vmdk file path + :param dest_dc_ref: Reference to datacenter of dest datastore. + If unspecified, source datacenter is used. + """ + LOG.debug('Moving disk: %(src)s to %(dest)s.', + {'src': src_vmdk_file_path, 'dest': dest_vmdk_file_path}) + + dest_dc_ref = dest_dc_ref or src_dc_ref + diskMgr = self._session.vim.service_content.virtualDiskManager + task = self._session.invoke_api(self._session.vim, + 'MoveVirtualDisk_Task', + diskMgr, + sourceName=src_vmdk_file_path, + sourceDatacenter=src_dc_ref, + destName=dest_vmdk_file_path, + destDatacenter=dest_dc_ref, + force=True) + + self._session.wait_for_task(task) + def delete_vmdk_file(self, vmdk_file_path, dc_ref): """Delete given vmdk files. diff --git a/releasenotes/notes/vmware-vmdk-manage-existing-0edc20d9d4d19172.yaml b/releasenotes/notes/vmware-vmdk-manage-existing-0edc20d9d4d19172.yaml new file mode 100644 index 000000000..f26ecb19f --- /dev/null +++ b/releasenotes/notes/vmware-vmdk-manage-existing-0edc20d9d4d19172.yaml @@ -0,0 +1,3 @@ +--- +features: +- Added support for manage volume in the VMware VMDK driver. \ No newline at end of file -- 2.45.2