diff --git a/nova/tests/unit/virt/vmwareapi/fake.py b/nova/tests/unit/virt/vmwareapi/fake.py index c2ded60a5973..d357122bfa7c 100644 --- a/nova/tests/unit/virt/vmwareapi/fake.py +++ b/nova/tests/unit/virt/vmwareapi/fake.py @@ -370,6 +370,7 @@ class VirtualLsiLogicController(DataObject): def __init__(self, key=0, scsiCtlrUnitNumber=0): self.key = key self.scsiCtlrUnitNumber = scsiCtlrUnitNumber + self.device = [] class VirtualLsiLogicSASController(DataObject): diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index 66ca991ed76b..11211588972a 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -28,6 +28,7 @@ from nova import test from nova.tests.unit import fake_instance from nova.tests.unit.virt.vmwareapi import fake from nova.tests.unit.virt.vmwareapi import stubs +from nova.virt.vmwareapi import constants from nova.virt.vmwareapi import driver from nova.virt.vmwareapi import ds_util from nova.virt.vmwareapi import vm_util @@ -283,6 +284,36 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): vmdk_adapter_type = vm_util.get_vmdk_adapter_type("dummyAdapter") self.assertEqual("dummyAdapter", vmdk_adapter_type) + def test_get_scsi_adapter_type(self): + vm = fake.VirtualMachine() + devices = vm.get("config.hardware.device").VirtualDevice + scsi_controller = fake.VirtualLsiLogicController() + ide_controller = fake.VirtualIDEController() + devices.append(scsi_controller) + devices.append(ide_controller) + fake._update_object("VirtualMachine", vm) + # return the scsi type, not ide + hardware_device = vm.get("config.hardware.device") + self.assertEqual(constants.DEFAULT_ADAPTER_TYPE, + vm_util.get_scsi_adapter_type(hardware_device)) + + def test_get_scsi_adapter_type_with_error(self): + vm = fake.VirtualMachine() + devices = vm.get("config.hardware.device").VirtualDevice + scsi_controller = fake.VirtualLsiLogicController() + ide_controller = fake.VirtualIDEController() + devices.append(scsi_controller) + devices.append(ide_controller) + fake._update_object("VirtualMachine", vm) + # the controller is not suitable since the device under this controller + # has exceeded SCSI_MAX_CONNECT_NUMBER + for i in range(0, constants.SCSI_MAX_CONNECT_NUMBER): + scsi_controller.device.append('device' + str(i)) + hardware_device = vm.get("config.hardware.device") + self.assertRaises(exception.StorageError, + vm_util.get_scsi_adapter_type, + hardware_device) + def test_find_allocated_slots(self): disk1 = fake.VirtualDisk(200, 0) disk2 = fake.VirtualDisk(200, 1) diff --git a/nova/tests/unit/virt/vmwareapi/test_volumeops.py b/nova/tests/unit/virt/vmwareapi/test_volumeops.py index c82f6785cf8c..cf2b351f9791 100644 --- a/nova/tests/unit/virt/vmwareapi/test_volumeops.py +++ b/nova/tests/unit/virt/vmwareapi/test_volumeops.py @@ -216,9 +216,6 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase): 'volume_id': 'volume-fake-id'}} vm_ref = 'fake-vm-ref' default_adapter_type = constants.DEFAULT_ADAPTER_TYPE - vmdk_info = vm_util.VmdkInfo('fake-path', default_adapter_type, - constants.DISK_TYPE_PREALLOCATED, 1024, - 'fake-device') adapter_type = adapter_type or default_adapter_type with contextlib.nested( @@ -226,10 +223,10 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase): mock.patch.object(self._volumeops, '_iscsi_discover_target', return_value=(mock.sentinel.device_name, mock.sentinel.uuid)), - mock.patch.object(vm_util, 'get_vmdk_info', - return_value=vmdk_info), + mock.patch.object(vm_util, 'get_scsi_adapter_type', + return_value=adapter_type), mock.patch.object(self._volumeops, 'attach_disk_to_vm') - ) as (get_vm_ref, iscsi_discover_target, get_vmdk_info, + ) as (get_vm_ref, iscsi_discover_target, get_scsi_adapter_type, attach_disk_to_vm): self._volumeops.attach_volume(connection_info, self._instance, adapter_type) @@ -238,7 +235,8 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase): self._instance) iscsi_discover_target.assert_called_once_with( connection_info['data']) - self.assertTrue(get_vmdk_info.called) + if adapter_type is None: + self.assertTrue(get_scsi_adapter_type.called) attach_disk_to_vm.assert_called_once_with(vm_ref, self._instance, adapter_type, 'rdmp', device_name=mock.sentinel.device_name) diff --git a/nova/virt/vmwareapi/constants.py b/nova/virt/vmwareapi/constants.py index 4f53fc145585..1febc81b62c0 100644 --- a/nova/virt/vmwareapi/constants.py +++ b/nova/virt/vmwareapi/constants.py @@ -56,6 +56,10 @@ SUPPORTED_FLAT_VARIANTS = ["thin", "preallocated", "thick", "eagerZeroedThick"] EXTENSION_KEY = 'org.openstack.compute' EXTENSION_TYPE_INSTANCE = 'instance' +# The max number of devices that can be connnected to one adapter +# One adapter has 16 slots but one reserved for controller +SCSI_MAX_CONNECT_NUMBER = 15 + # This list was extracted from the installation iso image for ESX 5.5 Update 1. # It is contained in s.v00, which is gzipped. The list was obtained by # searching for the string 'otherGuest' in the uncompressed contents of that diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 42a265f1e0d5..48e92474f2ab 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -554,6 +554,31 @@ def get_vmdk_info(session, vm_ref, uuid=None): capacity_in_bytes, vmdk_device) +scsi_controller_classes = { + 'ParaVirtualSCSIController': constants.ADAPTER_TYPE_PARAVIRTUAL, + 'VirtualLsiLogicController': constants.DEFAULT_ADAPTER_TYPE, + 'VirtualLsiLogicSASController': constants.ADAPTER_TYPE_LSILOGICSAS, + 'VirtualBusLogicController': constants.ADAPTER_TYPE_PARAVIRTUAL, +} + + +def get_scsi_adapter_type(hardware_devices): + """Selects a proper iscsi adapter type from the existing + hardware devices + """ + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": + hardware_devices = hardware_devices.VirtualDevice + + for device in hardware_devices: + if device.__class__.__name__ in scsi_controller_classes: + # find the controllers which still have available slots + if len(device.device) < constants.SCSI_MAX_CONNECT_NUMBER: + # return the first match one + return scsi_controller_classes[device.__class__.__name__] + raise exception.StorageError( + reason=_("Unable to find iSCSI Target")) + + def _find_controller_slot(controller_keys, taken, max_unit_number): for controller_key in controller_keys: for unit_number in range(max_unit_number): diff --git a/nova/virt/vmwareapi/volumeops.py b/nova/virt/vmwareapi/volumeops.py index 2aeb43f537fe..73900e7c100d 100644 --- a/nova/virt/vmwareapi/volumeops.py +++ b/nova/virt/vmwareapi/volumeops.py @@ -350,9 +350,12 @@ class VMwareVolumeOps(object): if device_name is None: raise exception.StorageError( reason=_("Unable to find iSCSI Target")) - - vmdk = vm_util.get_vmdk_info(self._session, vm_ref) - adapter_type = adapter_type or vmdk.adapter_type + if adapter_type is None: + # Get the vmdk file name that the VM is pointing to + hardware_devices = self._session._call_method( + vim_util, "get_dynamic_property", vm_ref, + "VirtualMachine", "config.hardware.device") + adapter_type = vm_util.get_scsi_adapter_type(hardware_devices) self.attach_disk_to_vm(vm_ref, instance, adapter_type, 'rdmp',