From 451d17d4166d0ccf9c5c280af65172fbea62a216 Mon Sep 17 00:00:00 2001 From: Vipin Balachandran Date: Fri, 29 Jan 2016 03:22:28 -0800 Subject: [PATCH] VMware: manage_existing_get_size for VMDK driver This patch implements manage_existing_get_size 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 checks for the existence of a disk device at path vmdk_path and attached to the VM at vCenter inventory path given by vm_inventory_path. If the disk device exists it returns its size. Implements: blueprint vmdk-manage-existing Change-Id: Id5698c2452aed454f22ac68fee8dd90046d858e1 --- cinder/tests/unit/test_vmware_vmdk.py | 60 ++++++++++++++++++++++ cinder/tests/unit/test_vmware_volumeops.py | 47 +++++++++++++++++ cinder/volume/drivers/vmware/vmdk.py | 44 ++++++++++++++++ cinder/volume/drivers/vmware/volumeops.py | 44 ++++++++++++++++ 4 files changed, 195 insertions(+) diff --git a/cinder/tests/unit/test_vmware_vmdk.py b/cinder/tests/unit/test_vmware_vmdk.py index 4cb0580a2..2e514bbdc 100644 --- a/cinder/tests/unit/test_vmware_vmdk.py +++ b/cinder/tests/unit/test_vmware_vmdk.py @@ -2353,6 +2353,66 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): {hub.DatastoreSelector.SIZE_BYTES: volume['size'] * units.Gi, hub.DatastoreSelector.PROFILE_NAME: None}, hosts=[host]) + @mock.patch.object(VMDK_DRIVER, 'volumeops') + def test_get_disk_device(self, vops): + vm = mock.sentinel.vm + vops.get_entity_by_inventory_path.return_value = vm + + dev = mock.sentinel.dev + vops.get_disk_device.return_value = dev + + vm_inv_path = mock.sentinel.vm_inv_path + vmdk_path = mock.sentinel.vmdk_path + ret = self._driver._get_disk_device(vmdk_path, vm_inv_path) + + self.assertEqual((vm, dev), ret) + vops.get_entity_by_inventory_path.assert_called_once_with(vm_inv_path) + vops.get_disk_device.assert_called_once_with(vm, vmdk_path) + + def test_get_existing_with_empty_source_name(self): + self.assertRaises(cinder_exceptions.InvalidInput, + self._driver._get_existing, + {}) + + def test_get_existing_with_invalid_source_name(self): + self.assertRaises(cinder_exceptions.InvalidInput, + self._driver._get_existing, + {'source-name': 'foo'}) + + @mock.patch.object(VMDK_DRIVER, '_get_disk_device', return_value=None) + def test_get_existing_with_invalid_existing_ref(self, get_disk_device): + self.assertRaises(cinder_exceptions.ManageExistingInvalidReference, + self._driver._get_existing, + {'source-name': '[ds1] foo/foo.vmdk@/dc-1/vm/foo'}) + get_disk_device.assert_called_once_with('[ds1] foo/foo.vmdk', + '/dc-1/vm/foo') + + @mock.patch.object(VMDK_DRIVER, '_get_disk_device') + def test_get_existing(self, get_disk_device): + vm = mock.sentinel.vm + disk_device = mock.sentinel.disk_device + get_disk_device.return_value = (vm, disk_device) + self.assertEqual( + (vm, disk_device), + self._driver._get_existing({'source-name': + '[ds1] foo/foo.vmdk@/dc-1/vm/foo'})) + get_disk_device.assert_called_once_with('[ds1] foo/foo.vmdk', + '/dc-1/vm/foo') + + @mock.patch.object(VMDK_DRIVER, '_get_existing') + @ddt.data((16384, 1), (1048576, 1), (1572864, 2)) + def test_manage_existing_get_size(self, test_data, get_existing): + (capacity_kb, exp_size) = test_data + disk_device = mock.Mock(capacityInKB=capacity_kb) + get_existing.return_value = (mock.sentinel.vm, disk_device) + + volume = mock.sentinel.volume + existing_ref = mock.sentinel.existing_ref + self.assertEqual(exp_size, + self._driver.manage_existing_get_size(volume, + existing_ref)) + get_existing.assert_called_once_with(existing_ref) + @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 d1b98c823..056a065ff 100644 --- a/cinder/tests/unit/test_vmware_volumeops.py +++ b/cinder/tests/unit/test_vmware_volumeops.py @@ -1791,6 +1791,53 @@ class VolumeOpsTestCase(test.TestCase): 'ClusterComputeResource', self.MAX_OBJECTS) continue_retrieval.assert_called_once_with(retrieve_result) + def test_get_entity_by_inventory_path(self): + self.session.invoke_api.return_value = mock.sentinel.ref + + path = mock.sentinel.path + ret = self.vops.get_entity_by_inventory_path(path) + self.assertEqual(mock.sentinel.ref, ret) + self.session.invoke_api.assert_called_once_with( + self.session.vim, + "FindByInventoryPath", + self.session.vim.service_content.searchIndex, + inventoryPath=path) + + def test_get_disk_devices(self): + disk_device = mock.Mock() + disk_device.__class__.__name__ = 'VirtualDisk' + + controller_device = mock.Mock() + controller_device.__class__.__name__ = 'VirtualLSILogicController' + + devices = mock.Mock() + devices.__class__.__name__ = "ArrayOfVirtualDevice" + devices.VirtualDevice = [disk_device, controller_device] + self.session.invoke_api.return_value = devices + + vm = mock.sentinel.vm + self.assertEqual([disk_device], self.vops._get_disk_devices(vm)) + self.session.invoke_api.assert_called_once_with( + vim_util, 'get_object_property', self.session.vim, + vm, 'config.hardware.device') + + def _create_disk_device(self, file_name): + backing = mock.Mock(fileName=file_name) + backing.__class__.__name__ = 'VirtualDiskFlatVer2BackingInfo' + return mock.Mock(backing=backing) + + @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.' + '_get_disk_devices') + def test_get_disk_device(self, get_disk_devices): + dev_1 = self._create_disk_device('[ds1] foo/foo.vmdk') + dev_2 = self._create_disk_device('[ds1] foo/foo_1.vmdk') + get_disk_devices.return_value = [dev_1, dev_2] + + vm = mock.sentinel.vm + self.assertEqual(dev_2, + self.vops.get_disk_device(vm, '[ds1] foo/foo_1.vmdk')) + get_disk_devices.assert_called_once_with(vm) + class VirtualDiskPathTest(test.TestCase): """Unit tests for VirtualDiskPath.""" diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index 6903e0322..e5fb5abc5 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -24,6 +24,7 @@ machine is never powered on and is often referred as the shadow VM. import contextlib import distutils.version as dist_version # pylint: disable=E0611 +import math import os import tempfile @@ -1650,6 +1651,49 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): {'backup_id': backup['id'], 'name': volume['name']}) + def _get_disk_device(self, vmdk_path, vm_inv_path): + # Get the VM that corresponds to the given inventory path. + vm = self.volumeops.get_entity_by_inventory_path(vm_inv_path) + if vm: + # Get the disk device that corresponds to the given vmdk path. + disk_device = self.volumeops.get_disk_device(vm, vmdk_path) + if disk_device: + return (vm, disk_device) + + def _get_existing(self, existing_ref): + src_name = existing_ref.get('source-name') + if not src_name: + raise exception.InvalidInput( + reason=_("source-name cannot be empty.")) + + # source-name format: vmdk_path@vm_inventory_path + parts = src_name.split('@') + if len(parts) != 2: + raise exception.InvalidInput( + reason=_("source-name format should be: " + "'vmdk_path@vm_inventory_path'.")) + + (vmdk_path, vm_inv_path) = parts + existing = self._get_disk_device(vmdk_path, vm_inv_path) + if not existing: + reason = _("%s does not exist.") % src_name + raise exception.ManageExistingInvalidReference( + existing_ref=existing_ref, reason=reason) + + return existing + + def manage_existing_get_size(self, volume, existing_ref): + """Return size of the volume to be managed by manage_existing. + + When calculating the size, round up to the next GB. + + :param volume: Cinder volume to manage + :param existing_ref: Driver-specific information used to identify a + volume + """ + (_vm, disk) = self._get_existing(existing_ref) + return int(math.ceil(disk.capacityInKB * units.Ki / float(units.Gi))) + @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 6579a6829..4454b92af 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -1569,3 +1569,47 @@ class VMwareVolumeOps(object): host_refs.extend(hosts.ManagedObjectReference) return host_refs + + def get_entity_by_inventory_path(self, path): + """Returns the managed object identified by the given inventory path. + + :param path: Inventory path + :return: Reference to the managed object + """ + return self._session.invoke_api( + self._session.vim, + "FindByInventoryPath", + self._session.vim.service_content.searchIndex, + inventoryPath=path) + + def _get_disk_devices(self, vm): + disk_devices = [] + hardware_devices = self._session.invoke_api(vim_util, + 'get_object_property', + self._session.vim, + vm, + 'config.hardware.device') + + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": + hardware_devices = hardware_devices.VirtualDevice + + for device in hardware_devices: + if device.__class__.__name__ == "VirtualDisk": + disk_devices.append(device) + + return disk_devices + + def get_disk_device(self, vm, vmdk_path): + """Get the disk device of the VM which corresponds to the given path. + + :param vm: VM reference + :param vmdk_path: Datastore path of virtual disk + :return: Matching disk device + """ + disk_devices = self._get_disk_devices(vm) + + for disk_device in disk_devices: + backing = disk_device.backing + if (backing.__class__.__name__ == "VirtualDiskFlatVer2BackingInfo" + and backing.fileName == vmdk_path): + return disk_device -- 2.45.2