From: Vipin Balachandran Date: Sun, 29 Jun 2014 13:27:00 +0000 (+0530) Subject: VMware:Support for create disk and disk descriptor X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=97fbe770bc4dc97218e170103bfe16a1ad632976;p=openstack-build%2Fcinder-build.git VMware:Support for create disk and disk descriptor 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 --- diff --git a/cinder/tests/test_vmware_volumeops.py b/cinder/tests/test_vmware_volumeops.py index ab589896c..6b080a16d 100644 --- a/cinder/tests/test_vmware_volumeops.py +++ b/cinder/tests/test_vmware_volumeops.py @@ -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.""" diff --git a/cinder/volume/drivers/vmware/error_util.py b/cinder/volume/drivers/vmware/error_util.py index 4bc4880da..fa65b47d0 100644 --- a/cinder/volume/drivers/vmware/error_util.py +++ b/cinder/volume/drivers/vmware/error_util.py @@ -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.") diff --git a/cinder/volume/drivers/vmware/volumeops.py b/cinder/volume/drivers/vmware/volumeops.py index f02c1188a..33649b501 100644 --- a/cinder/volume/drivers/vmware/volumeops.py +++ b/cinder/volume/drivers/vmware/volumeops.py @@ -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.