VMware: Optional create backing parameters

The current create backing methods do not support specifying an adapter
type for the backing VM. These methods always create a backing VM with
a single disk and LSI logic adapter. This change adds optional parameters
to create backing methods so that a backing VM can be created without a
disk or with a specific adapter type.

Partial-Bug: #1284284
Partial-Bug: #1287185
Partial-Bug: #1287176
Change-Id: Ifff64eb2be4af1c4218e810366e25dbecdc5847f
This commit is contained in:
Vipin Balachandran
2014-06-05 12:25:53 +05:30
parent 5278eeca51
commit f9402ffe9f
5 changed files with 326 additions and 63 deletions

View File

@@ -353,9 +353,9 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
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)
@@ -537,6 +537,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
mox.IgnoreArg(), folder,
resource_pool, host,
mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(backing)
m.ReplayAll()
@@ -1074,17 +1075,17 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
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.
@@ -1095,10 +1096,10 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
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,
@@ -1906,3 +1907,37 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
"""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')

View File

@@ -402,29 +402,60 @@ class VolumeOpsTestCase(test.TestCase):
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
@@ -436,15 +467,49 @@ class VolumeOpsTestCase(test.TestCase):
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,
@@ -814,3 +879,34 @@ class VolumeOpsTestCase(test.TestCase):
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))

View File

@@ -67,3 +67,8 @@ class VMwaredriverConfigurationException(VMwareDriverException):
"""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.")

View File

@@ -46,6 +46,9 @@ THIN_VMDK_TYPE = 'thin'
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,
@@ -426,11 +429,13 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
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
@@ -439,21 +444,41 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
(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
@@ -495,13 +520,15 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
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
"""
@@ -513,7 +540,9 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
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:
@@ -879,10 +908,10 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
# 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')

View File

@@ -57,6 +57,43 @@ def split_datastore_path(datastore_path):
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."""
@@ -348,22 +385,21 @@ class VMwareVolumeOps(object):
"%(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
@@ -387,6 +423,17 @@ class VMwareVolumeOps(object):
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
@@ -395,7 +442,6 @@ class VMwareVolumeOps(object):
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
@@ -408,11 +454,38 @@ class VMwareVolumeOps(object):
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.
@@ -425,26 +498,51 @@ class VMwareVolumeOps(object):
: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.