]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware: manage_existing_get_size for VMDK driver
authorVipin Balachandran <vbala@vmware.com>
Fri, 29 Jan 2016 11:22:28 +0000 (03:22 -0800)
committerVipin Balachandran <vbala@vmware.com>
Tue, 16 Feb 2016 06:32:04 +0000 (12:02 +0530)
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
cinder/tests/unit/test_vmware_volumeops.py
cinder/volume/drivers/vmware/vmdk.py
cinder/volume/drivers/vmware/volumeops.py

index 4cb0580a21663eabf30d8f6db5f7014e33a5cfda..2e514bbdc19a1c7d798d749e0e75773d7bc75b8d 100644 (file)
@@ -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
index d1b98c823aa33e736e2d502275d92a2153d5e612..056a065ff7cbf0466a3813820d64aa66e71b7ecf 100644 (file)
@@ -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."""
index 6903e03223cdb120f27891ce5366938c035ad7cf..e5fb5abc51c1503b662563f3705578bf3c689273 100644 (file)
@@ -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:
index 6579a68293f59d1238f0de3ccd6ccb6f95297af9..4454b92af6c93c25156ef772378a66a1ca09ea07 100644 (file)
@@ -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