volume = FakeObject()
volume['name'] = 'vol_name'
backing = FakeMor('VirtualMachine', 'my_back')
- mux = self._driver._create_backing(volume, host1.obj)
+ mux = self._driver._create_backing(volume, host1.obj, {})
mux.AndRaise(error_util.VimException('Maintenance mode'))
- mux = self._driver._create_backing(volume, host2.obj)
+ mux = self._driver._create_backing(volume, host2.obj, {})
mux.AndReturn(backing)
m.StubOutWithMock(self._volumeops, 'cancel_retrieval')
self._volumeops.cancel_retrieval(retrieve_result)
mox.IgnoreArg(), folder,
resource_pool, host,
mox.IgnoreArg(),
+ mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(backing)
m.ReplayAll()
timeout = self._config.vmware_image_transfer_timeout_secs
image_service.show.return_value = fake_image_meta
- volumeops._get_create_spec.return_value = fake_vm_create_spec
+ volumeops.get_create_spec.return_value = fake_vm_create_spec
volumeops.get_backing.return_value = fake_backing
- # If _select_ds_for_volume raises an exception, _get_create_spec
+ # If _select_ds_for_volume raises an exception, get_create_spec
# will not be called.
_select_ds_for_volume.side_effect = error_util.VimException('Error')
self.assertRaises(exception.VolumeBackendAPIException,
self._driver.copy_image_to_volume,
fake_context, fake_volume,
image_service, fake_image_id)
- self.assertFalse(volumeops._get_create_spec.called)
+ self.assertFalse(volumeops.get_create_spec.called)
# If the volume size is greater then than the image size,
# _extend_vmdk_virtual_disk will be called.
image_service, fake_image_id)
image_service.show.assert_called_with(fake_context, fake_image_id)
_select_ds_for_volume.assert_called_with(fake_volume)
- volumeops._get_create_spec.assert_called_with(fake_volume['name'],
- 0,
- fake_disk_type,
- fake_summary.name)
+ volumeops.get_create_spec.assert_called_with(fake_volume['name'],
+ 0,
+ fake_disk_type,
+ fake_summary.name)
self.assertTrue(fetch_optimized_image.called)
fetch_optimized_image.assert_called_with(fake_context, timeout,
image_service,
"""Test extend_volume."""
self._test_extend_volume(volume_ops, _extend_virtual_disk,
_select_ds_for_volume)
+
+ @mock.patch.object(VMDK_DRIVER, '_get_folder_ds_summary')
+ @mock.patch.object(VMDK_DRIVER, 'volumeops')
+ def test_create_backing_with_params(self, vops, get_folder_ds_summary):
+ resource_pool = mock.sentinel.resource_pool
+ vops.get_dss_rp.return_value = (mock.Mock(), resource_pool)
+ folder = mock.sentinel.folder
+ summary = mock.sentinel.summary
+ get_folder_ds_summary.return_value = (folder, summary)
+
+ volume = {'name': 'vol-1', 'volume_type_id': None, 'size': 1}
+ host = mock.Mock()
+ create_params = {vmdk.CREATE_PARAM_DISK_LESS: True}
+ self._driver._create_backing(volume, host, create_params)
+
+ vops.create_backing_disk_less.assert_called_once_with('vol-1',
+ folder,
+ resource_pool,
+ host,
+ summary.name,
+ None)
+
+ create_params = {vmdk.CREATE_PARAM_ADAPTER_TYPE: 'ide'}
+ self._driver._create_backing(volume, host, create_params)
+
+ vops.create_backing.assert_called_once_with('vol-1',
+ units.Mi,
+ vmdk.THIN_VMDK_TYPE,
+ folder,
+ resource_pool,
+ host,
+ summary.name,
+ None,
+ 'ide')
self.assertEqual(expected_invoke_api,
self.session.invoke_api.mock_calls)
- def test_get_create_spec(self):
+ def test_create_specs_for_ide_disk_add(self):
factory = self.session.vim.client.factory
factory.create.return_value = mock.Mock(spec=object)
- name = mock.sentinel.name
+
size_kb = 0.5
disk_type = 'thin'
- ds_name = mock.sentinel.ds_name
- ret = self.vops._get_create_spec(name, size_kb, disk_type, ds_name)
- self.assertEqual(name, ret.name)
- self.assertEqual('[%s]' % ds_name, ret.files.vmPathName)
- self.assertEqual(1, ret.deviceChange[1].device.capacityInKB)
- self.assertEqual("vmx-08", ret.version)
- expected = [mock.call.create('ns0:VirtualLsiLogicController'),
+ adapter_type = 'ide'
+ ret = self.vops._create_specs_for_disk_add(size_kb, disk_type,
+ adapter_type)
+ self.assertFalse(hasattr(ret[0].device, 'sharedBus'))
+ self.assertEqual(1, ret[1].device.capacityInKB)
+ expected = [mock.call.create('ns0:VirtualIDEController'),
mock.call.create('ns0:VirtualDeviceConfigSpec'),
mock.call.create('ns0:VirtualDisk'),
mock.call.create('ns0:VirtualDiskFlatVer2BackingInfo'),
+ mock.call.create('ns0:VirtualDeviceConfigSpec')]
+ factory.create.assert_has_calls(expected, any_order=True)
+
+ def test_create_specs_for_scsi_disk_add(self):
+ factory = self.session.vim.client.factory
+ factory.create.return_value = mock.Mock(spec=object)
+
+ size_kb = 2
+ disk_type = 'thin'
+ adapter_type = 'lsiLogicsas'
+ ret = self.vops._create_specs_for_disk_add(size_kb, disk_type,
+ adapter_type)
+ self.assertEqual('noSharing', ret[0].device.sharedBus)
+ self.assertEqual(size_kb, ret[1].device.capacityInKB)
+ expected = [mock.call.create('ns0:VirtualLsiLogicSASController'),
mock.call.create('ns0:VirtualDeviceConfigSpec'),
- mock.call.create('ns0:VirtualMachineFileInfo'),
- mock.call.create('ns0:VirtualMachineConfigSpec')]
+ mock.call.create('ns0:VirtualDisk'),
+ mock.call.create('ns0:VirtualDiskFlatVer2BackingInfo'),
+ mock.call.create('ns0:VirtualDeviceConfigSpec')]
+ factory.create.assert_has_calls(expected, any_order=True)
+
+ def test_get_create_spec_disk_less(self):
+ factory = self.session.vim.client.factory
+ factory.create.return_value = mock.Mock(spec=object)
+ name = mock.sentinel.name
+ ds_name = mock.sentinel.ds_name
+ profile_id = mock.sentinel.profile_id
+ ret = self.vops._get_create_spec_disk_less(name, ds_name, profile_id)
+ self.assertEqual(name, ret.name)
+ self.assertEqual('[%s]' % ds_name, ret.files.vmPathName)
+ self.assertEqual("vmx-08", ret.version)
+ self.assertEqual(profile_id, ret.vmProfile[0].profileId)
+ expected = [mock.call.create('ns0:VirtualMachineFileInfo'),
+ mock.call.create('ns0:VirtualMachineConfigSpec'),
+ mock.call.create('ns0:VirtualMachineDefinedProfileSpec')]
factory.create.assert_has_calls(expected, any_order=True)
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
- '_get_create_spec')
+ 'get_create_spec')
def test_create_backing(self, get_create_spec):
create_spec = mock.sentinel.create_spec
get_create_spec.return_value = create_spec
name = 'backing_name'
size_kb = mock.sentinel.size_kb
disk_type = mock.sentinel.disk_type
+ adapter_type = mock.sentinel.adapter_type
folder = mock.sentinel.folder
resource_pool = mock.sentinel.resource_pool
host = mock.sentinel.host
ds_name = mock.sentinel.ds_name
+ profile_id = mock.sentinel.profile_id
ret = self.vops.create_backing(name, size_kb, disk_type, folder,
- resource_pool, host, ds_name)
+ resource_pool, host, ds_name,
+ profile_id, adapter_type)
self.assertEqual(mock.sentinel.result, ret)
get_create_spec.assert_called_once_with(name, size_kb, disk_type,
- ds_name, None)
+ ds_name, profile_id,
+ adapter_type)
+ self.session.invoke_api.assert_called_once_with(self.session.vim,
+ 'CreateVM_Task',
+ folder,
+ config=create_spec,
+ pool=resource_pool,
+ host=host)
+ self.session.wait_for_task.assert_called_once_with(task)
+
+ @mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
+ '_get_create_spec_disk_less')
+ def test_create_backing_disk_less(self, get_create_spec_disk_less):
+ create_spec = mock.sentinel.create_spec
+ get_create_spec_disk_less.return_value = create_spec
+ task = mock.sentinel.task
+ self.session.invoke_api.return_value = task
+ task_info = mock.Mock(spec=object)
+ task_info.result = mock.sentinel.result
+ self.session.wait_for_task.return_value = task_info
+ name = 'backing_name'
+ folder = mock.sentinel.folder
+ resource_pool = mock.sentinel.resource_pool
+ host = mock.sentinel.host
+ ds_name = mock.sentinel.ds_name
+ profile_id = mock.sentinel.profile_id
+ ret = self.vops.create_backing_disk_less(name, folder, resource_pool,
+ host, ds_name, profile_id)
+
+ self.assertEqual(mock.sentinel.result, ret)
+ get_create_spec_disk_less.assert_called_once_with(name, ds_name,
+ profile_id)
self.session.invoke_api.assert_called_once_with(self.session.vim,
'CreateVM_Task',
folder,
newCapacityKb=fake_size_in_kb,
eagerZero=False)
self.session.wait_for_task.assert_called_once_with(task)
+
+
+class ControllerTypeTest(test.TestCase):
+ """Unit tests for ControllerType."""
+
+ def test_get_controller_type(self):
+ self.assertEqual(volumeops.ControllerType.LSI_LOGIC,
+ volumeops.ControllerType.get_controller_type(
+ 'lsiLogic'))
+ self.assertEqual(volumeops.ControllerType.BUS_LOGIC,
+ volumeops.ControllerType.get_controller_type(
+ 'busLogic'))
+ self.assertEqual(volumeops.ControllerType.LSI_LOGIC_SAS,
+ volumeops.ControllerType.get_controller_type(
+ 'lsiLogicsas'))
+ self.assertEqual(volumeops.ControllerType.IDE,
+ volumeops.ControllerType.get_controller_type(
+ 'ide'))
+ self.assertRaises(error_util.InvalidAdapterTypeException,
+ volumeops.ControllerType.get_controller_type,
+ 'invalid_type')
+
+ def test_is_scsi_controller(self):
+ self.assertTrue(volumeops.ControllerType.is_scsi_controller(
+ volumeops.ControllerType.LSI_LOGIC))
+ self.assertTrue(volumeops.ControllerType.is_scsi_controller(
+ volumeops.ControllerType.BUS_LOGIC))
+ self.assertTrue(volumeops.ControllerType.is_scsi_controller(
+ volumeops.ControllerType.LSI_LOGIC_SAS))
+ self.assertFalse(volumeops.ControllerType.is_scsi_controller(
+ volumeops.ControllerType.IDE))
"""Base class for all configuration exceptions.
"""
message = _("VMware VMDK driver configuration error.")
+
+
+class InvalidAdapterTypeException(VMwareDriverException):
+ """Thrown when the disk adapter type is invalid."""
+ message = _("Invalid disk adapter type: %(invalid_type)s.")
THICK_VMDK_TYPE = 'thick'
EAGER_ZEROED_THICK_VMDK_TYPE = 'eagerZeroedThick'
+CREATE_PARAM_ADAPTER_TYPE = 'adapter_type'
+CREATE_PARAM_DISK_LESS = 'disk_less'
+
vmdk_opts = [
cfg.StrOpt('vmware_host_ip',
default=None,
EAGER_ZEROED_THICK_VMDK_TYPE),
THIN_VMDK_TYPE)
- def _create_backing(self, volume, host):
+ def _create_backing(self, volume, host, create_params={}):
"""Create volume backing under the given host.
:param volume: Volume object
:param host: Reference of the host
+ :param create_params: Dictionary specifying optional parameters for
+ backing VM creation
:return: Reference to the created backing
"""
# Get datastores and resource pool of the host
(folder, summary) = self._get_folder_ds_summary(volume,
resource_pool,
datastores)
- disk_type = VMwareEsxVmdkDriver._get_disk_type(volume)
- size_kb = volume['size'] * units.Mi
+
+ # check if a storage profile needs to be associated with the backing VM
storage_profile = self._get_storage_profile(volume)
profileId = None
if self._storage_policy_enabled and storage_profile:
profile = self.volumeops.retrieve_profile_id(storage_profile)
if profile:
profileId = profile.uniqueId
+
+ # default is a backing with single disk
+ disk_less = create_params.get(CREATE_PARAM_DISK_LESS, False)
+ if disk_less:
+ # create a disk-less backing-- disk can be added later; for e.g.,
+ # by copying an image
+ return self.volumeops.create_backing_disk_less(volume['name'],
+ folder,
+ resource_pool,
+ host,
+ summary.name,
+ profileId)
+
+ # create a backing with single disk
+ disk_type = VMwareEsxVmdkDriver._get_disk_type(volume)
+ size_kb = volume['size'] * units.Mi
+ adapter_type = create_params.get(CREATE_PARAM_ADAPTER_TYPE,
+ 'lsiLogic')
return self.volumeops.create_backing(volume['name'],
size_kb,
- disk_type, folder,
+ disk_type,
+ folder,
resource_pool,
host,
summary.name,
- profileId)
+ profileId,
+ adapter_type)
def _relocate_backing(self, volume, backing, host):
pass
LOG.error(msg)
raise error_util.VimException(msg)
- def _create_backing_in_inventory(self, volume):
+ def _create_backing_in_inventory(self, volume, create_params={}):
"""Creates backing under any suitable host.
The method tries to pick datastore that can fit the volume under
any host in the inventory.
:param volume: Volume object
+ :param create_params: Dictionary specifying optional parameters for
+ backing VM creation
:return: Reference to the created backing
"""
backing = None
for host in hosts:
try:
- backing = self._create_backing(volume, host.obj)
+ backing = self._create_backing(volume,
+ host.obj,
+ create_params)
if backing:
break
except error_util.VimException as excep:
# The size of stream optimized glance image is often suspect,
# so better let VC figure out the disk capacity during import.
dummy_disk_size = 0
- vm_create_spec = self.volumeops._get_create_spec(volume['name'],
- dummy_disk_size,
- disk_type,
- summary.name)
+ vm_create_spec = self.volumeops.get_create_spec(volume['name'],
+ dummy_disk_size,
+ disk_type,
+ summary.name)
# convert vm_create_spec to vm_import_spec
cf = self.session.vim.client.factory
vm_import_spec = cf.create('ns0:VirtualMachineImportSpec')
return (datastore_name.strip(), folder_path.strip(), file_name.strip())
+class ControllerType:
+ """Encapsulate various controller types."""
+
+ LSI_LOGIC = 'VirtualLsiLogicController'
+ BUS_LOGIC = 'VirtualBusLogicController'
+ LSI_LOGIC_SAS = 'VirtualLsiLogicSASController'
+ IDE = 'VirtualIDEController'
+
+ CONTROLLER_TYPE_DICT = {'lsiLogic': LSI_LOGIC,
+ 'busLogic': BUS_LOGIC,
+ 'lsiLogicsas': LSI_LOGIC_SAS,
+ 'ide': IDE}
+
+ @staticmethod
+ def get_controller_type(adapter_type):
+ """Get the disk controller type based on the given adapter type.
+
+ :param adapter_type: disk adapter type
+ :return: controller type corresponding to the given adapter type
+ :raises: InvalidAdapterTypeException
+ """
+ if adapter_type in ControllerType.CONTROLLER_TYPE_DICT:
+ return ControllerType.CONTROLLER_TYPE_DICT[adapter_type]
+ raise error_util.InvalidAdapterTypeException(invalid_type=adapter_type)
+
+ @staticmethod
+ def is_scsi_controller(controller_type):
+ """Check if the given controller is a SCSI controller.
+
+ :param controller_type: controller type
+ :return: True if the controller is a SCSI controller
+ """
+ return controller_type in [ControllerType.LSI_LOGIC,
+ ControllerType.BUS_LOGIC,
+ ControllerType.LSI_LOGIC_SAS]
+
+
class VMwareVolumeOps(object):
"""Manages volume operations."""
"%(size)s GB."),
{'name': name, 'size': requested_size_in_gb})
- def _get_create_spec(self, name, size_kb, disk_type, ds_name,
- profileId=None):
- """Return spec for creating volume backing.
+ def _create_specs_for_disk_add(self, size_kb, disk_type, adapter_type):
+ """Create controller and disk specs for adding a new disk.
- :param name: Name of the backing
- :param size_kb: Size in KB of the backing
- :param disk_type: VMDK type for the disk
- :param ds_name: Datastore name where the disk is to be provisioned
- :param profileId: storage profile ID for the backing
- :return: Spec for creation
+ :param size_kb: disk size in KB
+ :param disk_type: disk provisioning type
+ :param adapter_type: disk adapter type
+ :return: list containing controller and disk specs
"""
cf = self._session.vim.client.factory
- controller_device = cf.create('ns0:VirtualLsiLogicController')
+ controller_type = ControllerType.get_controller_type(adapter_type)
+ controller_device = cf.create('ns0:%s' % controller_type)
controller_device.key = -100
controller_device.busNumber = 0
- controller_device.sharedBus = 'noSharing'
+ if ControllerType.is_scsi_controller(controller_type):
+ controller_device.sharedBus = 'noSharing'
controller_spec = cf.create('ns0:VirtualDeviceConfigSpec')
controller_spec.operation = 'add'
controller_spec.device = controller_device
disk_spec.fileOperation = 'create'
disk_spec.device = disk_device
+ return [controller_spec, disk_spec]
+
+ def _get_create_spec_disk_less(self, name, ds_name, profileId=None):
+ """Return spec for creating disk-less backing.
+
+ :param name: Name of the backing
+ :param ds_name: Datastore name where the disk is to be provisioned
+ :param profileId: storage profile ID for the backing
+ :return: Spec for creation
+ """
+ cf = self._session.vim.client.factory
vm_file_info = cf.create('ns0:VirtualMachineFileInfo')
vm_file_info.vmPathName = '[%s]' % ds_name
create_spec.guestId = 'otherGuest'
create_spec.numCPUs = 1
create_spec.memoryMB = 128
- create_spec.deviceChange = [controller_spec, disk_spec]
create_spec.files = vm_file_info
# set the Hardware version to the lowest version supported by ESXi5.0
# and compatible with vCenter Server 5.0
vmProfile.profileId = profileId
create_spec.vmProfile = [vmProfile]
- LOG.debug("Spec for creating the backing: %s." % create_spec)
return create_spec
+ def get_create_spec(self, name, size_kb, disk_type, ds_name,
+ profileId=None, adapter_type='lsiLogic'):
+ """Return spec for creating backing with a single disk.
+
+ :param name: name of the backing
+ :param size_kb: disk size in KB
+ :param disk_type: disk provisioning type
+ :param ds_name: datastore name where the disk is to be provisioned
+ :param profileId: storage profile ID for the backing
+ :param adapter_type: disk adapter type
+ :return: spec for creation
+ """
+ create_spec = self._get_create_spec_disk_less(name, ds_name, profileId)
+ create_spec.deviceChange = self._create_specs_for_disk_add(
+ size_kb, disk_type, adapter_type)
+ return create_spec
+
+ def _create_backing_int(self, folder, resource_pool, host, create_spec):
+ """Helper for create backing methods."""
+ LOG.debug("Creating volume backing with spec: %s.", create_spec)
+ task = self._session.invoke_api(self._session.vim, 'CreateVM_Task',
+ folder, config=create_spec,
+ pool=resource_pool, host=host)
+ task_info = self._session.wait_for_task(task)
+ backing = task_info.result
+ LOG.info(_("Successfully created volume backing: %s."), backing)
+ return backing
+
def create_backing(self, name, size_kb, disk_type, folder, resource_pool,
- host, ds_name, profileId=None):
+ host, ds_name, profileId=None, adapter_type='lsiLogic'):
"""Create backing for the volume.
Creates a VM with one VMDK based on the given inputs.
:param host: Host reference
:param ds_name: Datastore name where the disk is to be provisioned
:param profileId: storage profile ID to be associated with backing
+ :param adapter_type: Disk adapter type
:return: Reference to the created backing entity
"""
- LOG.debug("Creating volume backing name: %(name)s "
- "disk_type: %(disk_type)s size_kb: %(size_kb)s at "
- "folder: %(folder)s resourse pool: %(resource_pool)s "
- "datastore name: %(ds_name)s profileId: %(profile)s." %
+ LOG.debug("Creating volume backing with name: %(name)s "
+ "disk_type: %(disk_type)s size_kb: %(size_kb)s "
+ "adapter_type: %(adapter_type)s profileId: %(profile)s at "
+ "folder: %(folder)s resource_pool: %(resource_pool)s "
+ "host: %(host)s datastore_name: %(ds_name)s.",
{'name': name, 'disk_type': disk_type, 'size_kb': size_kb,
'folder': folder, 'resource_pool': resource_pool,
- 'ds_name': ds_name, 'profile': profileId})
+ 'ds_name': ds_name, 'profile': profileId, 'host': host,
+ 'adapter_type': adapter_type})
- create_spec = self._get_create_spec(name, size_kb, disk_type, ds_name,
- profileId)
- task = self._session.invoke_api(self._session.vim, 'CreateVM_Task',
- folder, config=create_spec,
- pool=resource_pool, host=host)
- LOG.debug("Initiated creation of volume backing: %s." % name)
- task_info = self._session.wait_for_task(task)
- backing = task_info.result
- LOG.info(_("Successfully created volume backing: %s.") % backing)
- return backing
+ create_spec = self.get_create_spec(name, size_kb, disk_type, ds_name,
+ profileId, adapter_type)
+ return self._create_backing_int(folder, resource_pool, host,
+ create_spec)
+
+ def create_backing_disk_less(self, name, folder, resource_pool,
+ host, ds_name, profileId=None):
+ """Create disk-less volume backing.
+
+ This type of backing is useful for creating volume from image. The
+ downloaded image from the image service can be copied to a virtual
+ disk of desired provisioning type and added to the backing VM.
+
+ :param name: Name of the backing
+ :param folder: Folder where the backing is created
+ :param resource_pool: Resource pool reference
+ :param host: Host reference
+ :param ds_name: Name of the datastore used for VM storage
+ :param profileId: Storage profile ID to be associated with backing
+ :return: Reference to the created backing entity
+ """
+ LOG.debug("Creating disk-less volume backing with name: %(name)s "
+ "profileId: %(profile)s at folder: %(folder)s "
+ "resource pool: %(resource_pool)s host: %(host)s "
+ "datastore_name: %(ds_name)s.",
+ {'name': name, 'profile': profileId, 'folder': folder,
+ 'resource_pool': resource_pool, 'host': host,
+ 'ds_name': ds_name})
+
+ create_spec = self._get_create_spec_disk_less(name, ds_name, profileId)
+ return self._create_backing_int(folder, resource_pool, host,
+ create_spec)
def get_datastore(self, backing):
"""Get datastore where the backing resides.