]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VMware:Support for create disk and disk descriptor
authorVipin Balachandran <vbala@vmware.com>
Sun, 29 Jun 2014 13:27:00 +0000 (18:57 +0530)
committerVipin Balachandran <vbala@vmware.com>
Thu, 24 Jul 2014 06:50:02 +0000 (12:20 +0530)
This change adds utility methods for creating a virtual disk and virtual
disk descriptor. Currently volume creation from preallocated image doesn't
honour the disk type in the volume extra spec and adapter type in the image
meta-data. The workflow to address these issues require support for the
above mentioned methods.

Change-Id: If8ec1fd6c60d26552e5fb58b8bb16a8cc87d9d28
Partial-Bug: #1284284
Partial-Bug: #1287185

cinder/tests/test_vmware_volumeops.py
cinder/volume/drivers/vmware/error_util.py
cinder/volume/drivers/vmware/volumeops.py

index ab589896c17cb01f8007ed3e4f76ee12ca1fa406..6b080a16d4c7bb79903b8572ad1da3d6d60f5851 100644 (file)
@@ -840,6 +840,58 @@ class VolumeOpsTestCase(test.TestCase):
                                            self.session.vim, backing,
                                            'config.hardware.device')
 
+    def test_create_virtual_disk(self):
+        task = mock.Mock()
+        invoke_api = self.session.invoke_api
+        invoke_api.return_value = task
+        spec = mock.Mock()
+        factory = self.session.vim.client.factory
+        factory.create.return_value = spec
+        disk_mgr = self.session.vim.service_content.virtualDiskManager
+
+        dc_ref = mock.Mock()
+        vmdk_ds_file_path = mock.Mock()
+        size_in_kb = 1024
+        adapter_type = 'ide'
+        disk_type = 'thick'
+        self.vops.create_virtual_disk(dc_ref, vmdk_ds_file_path, size_in_kb,
+                                      adapter_type, disk_type)
+
+        self.assertEqual(volumeops.VirtualDiskAdapterType.IDE,
+                         spec.adapterType)
+        self.assertEqual(volumeops.VirtualDiskType.PREALLOCATED, spec.diskType)
+        self.assertEqual(size_in_kb, spec.capacityKb)
+        invoke_api.assert_called_once_with(self.session.vim,
+                                           'CreateVirtualDisk_Task',
+                                           disk_mgr,
+                                           name=vmdk_ds_file_path,
+                                           datacenter=dc_ref,
+                                           spec=spec)
+        self.session.wait_for_task.assert_called_once_with(task)
+
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                'create_virtual_disk')
+    @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+                'delete_file')
+    def test_create_flat_extent_virtual_disk_descriptor(self, delete_file,
+                                                        create_virtual_disk):
+        dc_ref = mock.Mock()
+        path = mock.Mock()
+        size_in_kb = 1024
+        adapter_type = 'ide'
+        disk_type = 'thick'
+
+        self.vops.create_flat_extent_virtual_disk_descriptor(dc_ref,
+                                                             path,
+                                                             size_in_kb,
+                                                             adapter_type,
+                                                             disk_type)
+        create_virtual_disk.assert_called_once_with(
+            dc_ref, path.get_descriptor_ds_file_path(), size_in_kb,
+            adapter_type, disk_type)
+        delete_file.assert_called_once_with(
+            path.get_flat_extent_ds_file_path(), dc_ref)
+
     def test_copy_vmdk_file(self):
         task = mock.sentinel.task
         invoke_api = self.session.invoke_api
@@ -897,6 +949,113 @@ class VolumeOpsTestCase(test.TestCase):
         self.session.wait_for_task.assert_called_once_with(task)
 
 
+class VirtualDiskPathTest(test.TestCase):
+    """Unit tests for VirtualDiskPath."""
+
+    def setUp(self):
+        super(VirtualDiskPathTest, self).setUp()
+        self._path = volumeops.VirtualDiskPath("nfs", "A/B/", "disk")
+
+    def test_get_datastore_file_path(self):
+        self.assertEqual("[nfs] A/B/disk.vmdk",
+                         self._path.get_datastore_file_path("nfs",
+                                                            "A/B/disk.vmdk"))
+
+    def test_get_descriptor_file_path(self):
+        self.assertEqual("A/B/disk.vmdk",
+                         self._path.get_descriptor_file_path())
+
+    def test_get_descriptor_ds_file_path(self):
+        self.assertEqual("[nfs] A/B/disk.vmdk",
+                         self._path.get_descriptor_ds_file_path())
+
+
+class FlatExtentVirtualDiskPathTest(test.TestCase):
+    """Unit tests for FlatExtentVirtualDiskPath."""
+
+    def setUp(self):
+        super(FlatExtentVirtualDiskPathTest, self).setUp()
+        self._path = volumeops.FlatExtentVirtualDiskPath("nfs", "A/B/", "disk")
+
+    def test_get_flat_extent_file_path(self):
+        self.assertEqual("A/B/disk-flat.vmdk",
+                         self._path.get_flat_extent_file_path())
+
+    def test_get_flat_extent_ds_file_path(self):
+        self.assertEqual("[nfs] A/B/disk-flat.vmdk",
+                         self._path.get_flat_extent_ds_file_path())
+
+
+class VirtualDiskTypeTest(test.TestCase):
+    """Unit tests for VirtualDiskType."""
+
+    def test_is_valid(self):
+        self.assertTrue(volumeops.VirtualDiskType.is_valid("thick"))
+        self.assertTrue(volumeops.VirtualDiskType.is_valid("thin"))
+        self.assertTrue(volumeops.VirtualDiskType.is_valid("eagerZeroedThick"))
+        self.assertFalse(volumeops.VirtualDiskType.is_valid("preallocated"))
+
+    def test_validate(self):
+        volumeops.VirtualDiskType.validate("thick")
+        volumeops.VirtualDiskType.validate("thin")
+        volumeops.VirtualDiskType.validate("eagerZeroedThick")
+        self.assertRaises(error_util.InvalidDiskTypeException,
+                          volumeops.VirtualDiskType.validate,
+                          "preallocated")
+
+    def test_get_virtual_disk_type(self):
+        self.assertEqual("preallocated",
+                         volumeops.VirtualDiskType.get_virtual_disk_type(
+                             "thick"))
+        self.assertEqual("thin",
+                         volumeops.VirtualDiskType.get_virtual_disk_type(
+                             "thin"))
+        self.assertEqual("eagerZeroedThick",
+                         volumeops.VirtualDiskType.get_virtual_disk_type(
+                             "eagerZeroedThick"))
+        self.assertRaises(error_util.InvalidDiskTypeException,
+                          volumeops.VirtualDiskType.get_virtual_disk_type,
+                          "preallocated")
+
+
+class VirtualDiskAdapterTypeTest(test.TestCase):
+    """Unit tests for VirtualDiskAdapterType."""
+
+    def test_is_valid(self):
+        self.assertTrue(volumeops.VirtualDiskAdapterType.is_valid("lsiLogic"))
+        self.assertTrue(volumeops.VirtualDiskAdapterType.is_valid("busLogic"))
+        self.assertTrue(volumeops.VirtualDiskAdapterType.is_valid(
+                        "lsiLogicsas"))
+        self.assertTrue(volumeops.VirtualDiskAdapterType.is_valid("ide"))
+        self.assertFalse(volumeops.VirtualDiskAdapterType.is_valid("pvscsi"))
+
+    def test_validate(self):
+        volumeops.VirtualDiskAdapterType.validate("lsiLogic")
+        volumeops.VirtualDiskAdapterType.validate("busLogic")
+        volumeops.VirtualDiskAdapterType.validate("lsiLogicsas")
+        volumeops.VirtualDiskAdapterType.validate("ide")
+        self.assertRaises(error_util.InvalidAdapterTypeException,
+                          volumeops.VirtualDiskAdapterType.validate,
+                          "pvscsi")
+
+    def test_get_adapter_type(self):
+        self.assertEqual("lsiLogic",
+                         volumeops.VirtualDiskAdapterType.get_adapter_type(
+                             "lsiLogic"))
+        self.assertEqual("busLogic",
+                         volumeops.VirtualDiskAdapterType.get_adapter_type(
+                             "busLogic"))
+        self.assertEqual("lsiLogic",
+                         volumeops.VirtualDiskAdapterType.get_adapter_type(
+                             "lsiLogicsas"))
+        self.assertEqual("ide",
+                         volumeops.VirtualDiskAdapterType.get_adapter_type(
+                             "ide"))
+        self.assertRaises(error_util.InvalidAdapterTypeException,
+                          volumeops.VirtualDiskAdapterType.get_adapter_type,
+                          "pvscsi")
+
+
 class ControllerTypeTest(test.TestCase):
     """Unit tests for ControllerType."""
 
index 4bc4880dad5f73829a7d4f4fcda4be7729c977ed..fa65b47d0fdd3f45715271507a50bcd60ea1e43a 100644 (file)
@@ -73,3 +73,8 @@ class VMwaredriverConfigurationException(VMwareDriverException):
 class InvalidAdapterTypeException(VMwareDriverException):
     """Thrown when the disk adapter type is invalid."""
     message = _("Invalid disk adapter type: %(invalid_type)s.")
+
+
+class InvalidDiskTypeException(VMwareDriverException):
+    """Thrown when the disk type is invalid."""
+    message = _("Invalid disk type: %(disk_type)s.")
index f02c1188a710f7a8c80d00d7bb9edc0ec38b5816..33649b5019d453257cfea99c42e8411fa332b836 100644 (file)
@@ -58,7 +58,172 @@ def split_datastore_path(datastore_path):
     return (datastore_name.strip(), folder_path.strip(), file_name.strip())
 
 
-class ControllerType:
+class VirtualDiskPath(object):
+    """Class representing paths of files comprising a virtual disk."""
+
+    def __init__(self, ds_name, folder_path, disk_name):
+        """Creates path object for the given disk.
+
+        :param ds_name: name of the datastore where disk is stored
+        :param folder_path: absolute path of the folder containing the disk
+        :param disk_name: name of the virtual disk
+        """
+        self._descriptor_file_path = "%s%s.vmdk" % (folder_path, disk_name)
+        self._descriptor_ds_file_path = self.get_datastore_file_path(
+            ds_name, self._descriptor_file_path)
+
+    def get_datastore_file_path(self, ds_name, file_path):
+        """Get datastore path corresponding to the given file path.
+
+        :param ds_name: name of the datastore containing the file represented
+                        by the given file path
+        :param file_path: absolute path of the file
+        :return: datastore file path
+        """
+        return "[%s] %s" % (ds_name, file_path)
+
+    def get_descriptor_file_path(self):
+        """Get absolute file path of the virtual disk descriptor."""
+        return self._descriptor_file_path
+
+    def get_descriptor_ds_file_path(self):
+        """Get datastore file path of the virtual disk descriptor."""
+        return self._descriptor_ds_file_path
+
+
+class FlatExtentVirtualDiskPath(VirtualDiskPath):
+    """Paths of files in a non-monolithic disk with a single flat extent."""
+
+    def __init__(self, ds_name, folder_path, disk_name):
+        """Creates path object for the given disk.
+
+        :param ds_name: name of the datastore where disk is stored
+        :param folder_path: absolute path of the folder containing the disk
+        :param disk_name: name of the virtual disk
+        """
+        super(FlatExtentVirtualDiskPath, self).__init__(
+            ds_name, folder_path, disk_name)
+        self._flat_extent_file_path = "%s%s-flat.vmdk" % (folder_path,
+                                                          disk_name)
+        self._flat_extent_ds_file_path = self.get_datastore_file_path(
+            ds_name, self._flat_extent_file_path)
+
+    def get_flat_extent_file_path(self):
+        """Get absolute file path of the flat extent."""
+        return self._flat_extent_file_path
+
+    def get_flat_extent_ds_file_path(self):
+        """Get datastore file path of the flat extent."""
+        return self._flat_extent_ds_file_path
+
+
+class MonolithicSparseVirtualDiskPath(VirtualDiskPath):
+    """Paths of file comprising a monolithic sparse disk."""
+    pass
+
+
+class VirtualDiskType(object):
+    """Supported virtual disk types."""
+
+    EAGER_ZEROED_THICK = "eagerZeroedThick"
+    PREALLOCATED = "preallocated"
+    THIN = "thin"
+
+    # thick in extra_spec means lazy-zeroed thick disk
+    EXTRA_SPEC_DISK_TYPE_DICT = {'eagerZeroedThick': EAGER_ZEROED_THICK,
+                                 'thick': PREALLOCATED,
+                                 'thin': THIN
+                                 }
+
+    @staticmethod
+    def is_valid(extra_spec_disk_type):
+        """Check if the given disk type in extra_spec is valid.
+
+        :param extra_spec_disk_type: disk type in extra_spec
+        :return: True if valid
+        """
+        return (extra_spec_disk_type in
+                VirtualDiskType.EXTRA_SPEC_DISK_TYPE_DICT)
+
+    @staticmethod
+    def validate(extra_spec_disk_type):
+        """Validate the given disk type in extra_spec.
+
+        This method throws an instance of InvalidDiskTypeException if the given
+        disk type is invalid.
+
+        :param extra_spec_disk_type: disk type in extra_spec
+        :raises: InvalidDiskTypeException
+        """
+        if not VirtualDiskType.is_valid(extra_spec_disk_type):
+            raise error_util.InvalidDiskTypeException(
+                disk_type=extra_spec_disk_type)
+
+    @staticmethod
+    def get_virtual_disk_type(extra_spec_disk_type):
+        """Return disk type corresponding to the extra_spec disk type.
+
+        :param extra_spec_disk_type: disk type in extra_spec
+        :return: virtual disk type
+        :raises: InvalidDiskTypeException
+        """
+        VirtualDiskType.validate(extra_spec_disk_type)
+        return (VirtualDiskType.EXTRA_SPEC_DISK_TYPE_DICT[
+                extra_spec_disk_type])
+
+
+class VirtualDiskAdapterType(object):
+    """Supported virtual disk adapter types."""
+
+    LSI_LOGIC = "lsiLogic"
+    BUS_LOGIC = "busLogic"
+    LSI_LOGIC_SAS = "lsiLogicsas"
+    IDE = "ide"
+
+    @staticmethod
+    def is_valid(adapter_type):
+        """Check if the given adapter type is valid.
+
+        :param adapter_type: adapter type to check
+        :return: True if valid
+        """
+        return adapter_type in [VirtualDiskAdapterType.LSI_LOGIC,
+                                VirtualDiskAdapterType.BUS_LOGIC,
+                                VirtualDiskAdapterType.LSI_LOGIC_SAS,
+                                VirtualDiskAdapterType.IDE]
+
+    @staticmethod
+    def validate(extra_spec_adapter_type):
+        """Validate the given adapter type in extra_spec.
+
+        This method throws an instance of InvalidAdapterTypeException if the
+        given adapter type is invalid.
+
+        :param extra_spec_adapter_type: adapter type in extra_spec
+        :raises: InvalidAdapterTypeException
+        """
+        if not VirtualDiskAdapterType.is_valid(extra_spec_adapter_type):
+            raise error_util.InvalidAdapterTypeException(
+                invalid_type=extra_spec_adapter_type)
+
+    @staticmethod
+    def get_adapter_type(extra_spec_adapter_type):
+        """Get the adapter type to be used in VirtualDiskSpec.
+
+        :param extra_spec_adapter_type: adapter type in the extra_spec
+        :return: adapter type to be used in VirtualDiskSpec
+        """
+        VirtualDiskAdapterType.validate(extra_spec_adapter_type)
+        # We set the adapter type as lsiLogic for lsiLogicsas since it is not
+        # supported by VirtualDiskManager APIs. This won't be a problem because
+        # we attach the virtual disk to the correct controller type and the
+        # disk adapter type is always resolved using its controller key.
+        if extra_spec_adapter_type == VirtualDiskAdapterType.LSI_LOGIC_SAS:
+            return VirtualDiskAdapterType.LSI_LOGIC
+        return extra_spec_adapter_type
+
+
+class ControllerType(object):
     """Encapsulate various controller types."""
 
     LSI_LOGIC = 'VirtualLsiLogicController'
@@ -66,10 +231,11 @@ class ControllerType:
     LSI_LOGIC_SAS = 'VirtualLsiLogicSASController'
     IDE = 'VirtualIDEController'
 
-    CONTROLLER_TYPE_DICT = {'lsiLogic': LSI_LOGIC,
-                            'busLogic': BUS_LOGIC,
-                            'lsiLogicsas': LSI_LOGIC_SAS,
-                            'ide': IDE}
+    CONTROLLER_TYPE_DICT = {
+        VirtualDiskAdapterType.LSI_LOGIC: LSI_LOGIC,
+        VirtualDiskAdapterType.BUS_LOGIC: BUS_LOGIC,
+        VirtualDiskAdapterType.LSI_LOGIC_SAS: LSI_LOGIC_SAS,
+        VirtualDiskAdapterType.IDE: IDE}
 
     @staticmethod
     def get_controller_type(adapter_type):
@@ -851,6 +1017,69 @@ class VMwareVolumeOps(object):
                 if bkng.__class__.__name__ == "VirtualDiskFlatVer2BackingInfo":
                     return bkng.fileName
 
+    def _get_virtual_disk_create_spec(self, size_in_kb, adapter_type,
+                                      disk_type):
+        """Return spec for file-backed virtual disk creation."""
+        cf = self._session.vim.client.factory
+        spec = cf.create('ns0:FileBackedVirtualDiskSpec')
+        spec.capacityKb = size_in_kb
+        spec.adapterType = VirtualDiskAdapterType.get_adapter_type(
+            adapter_type)
+        spec.diskType = VirtualDiskType.get_virtual_disk_type(disk_type)
+        return spec
+
+    def create_virtual_disk(self, dc_ref, vmdk_ds_file_path, size_in_kb,
+                            adapter_type='busLogic', disk_type='preallocated'):
+        """Create virtual disk with the given settings.
+
+        :param dc_ref: datacenter reference
+        :param vmdk_ds_file_path: datastore file path of the virtual disk
+        :param size_in_kb: disk size in KB
+        :param adapter_type: disk adapter type
+        :param disk_type: vmdk type
+        """
+        virtual_disk_spec = self._get_virtual_disk_create_spec(size_in_kb,
+                                                               adapter_type,
+                                                               disk_type)
+        LOG.debug("Creating virtual disk with spec: %s.", virtual_disk_spec)
+        disk_manager = self._session.vim.service_content.virtualDiskManager
+        task = self._session.invoke_api(self._session.vim,
+                                        'CreateVirtualDisk_Task',
+                                        disk_manager,
+                                        name=vmdk_ds_file_path,
+                                        datacenter=dc_ref,
+                                        spec=virtual_disk_spec)
+        LOG.debug("Task: %s created for virtual disk creation.", task)
+        self._session.wait_for_task(task)
+        LOG.debug("Created virtual disk with spec: %s.", virtual_disk_spec)
+
+    def create_flat_extent_virtual_disk_descriptor(
+            self, dc_ref, path, size_in_kb, adapter_type, disk_type):
+        """Create descriptor for a single flat extent virtual disk.
+
+        To create the descriptor, we create a virtual disk and delete its flat
+        extent.
+
+        :param dc_ref: reference to the datacenter
+        :param path: descriptor datastore file path
+        :param size_in_kb: size of the virtual disk in KB
+        :param adapter_type: virtual disk adapter type
+        :param disk_type: type of the virtual disk
+        """
+        LOG.debug("Creating descriptor: %(path)s with size (KB): %(size)s, "
+                  "adapter_type: %(adapter_type)s and disk_type: "
+                  "%(disk_type)s.",
+                  {'path': path.get_descriptor_ds_file_path(),
+                   'size': size_in_kb,
+                   'adapter_type': adapter_type,
+                   'disk_type': disk_type
+                   })
+        self.create_virtual_disk(dc_ref, path.get_descriptor_ds_file_path(),
+                                 size_in_kb, adapter_type, disk_type)
+        self.delete_file(path.get_flat_extent_ds_file_path(), dc_ref)
+        LOG.debug("Created descriptor: %s.",
+                  path.get_descriptor_ds_file_path())
+
     def copy_vmdk_file(self, dc_ref, src_vmdk_file_path, dest_vmdk_file_path):
         """Copy contents of the src vmdk file to dest vmdk file.