From be033eef7296d132c8f7eb5e42203f5a3a947de6 Mon Sep 17 00:00:00 2001 From: Feng Xi Yan Date: Tue, 26 Jan 2016 15:13:36 +0800 Subject: [PATCH] Allocate free bus for new SCSI controller There are at most 16 virtual disks that can be attached onto a SCSI controller, while if the limitation is reached, error occurs with message "VMwareDriverException: Invalid configuration for device '0'". The reason is that when the controller's quota is used up, a new controller will be created automatically, but the newly-created one will try to use bus number 0, which is already used my existing controller. This change will allocate a new free bus number for the newly-created controller. Co-Authored-By: Qiaowei Ren Closes bug: #1522232 Change-Id: I2ced1358e682cda979d1d5bddb6359253699280b (cherry picked from commit b36da5dd037219ceb71c4c6ea6925d2eba6cd59b) --- nova/tests/unit/virt/vmwareapi/fake.py | 3 +- .../tests/unit/virt/vmwareapi/test_vm_util.py | 45 +++++++++++++++++++ nova/virt/vmwareapi/constants.py | 6 +++ nova/virt/vmwareapi/vm_util.py | 31 ++++++++++--- 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/nova/tests/unit/virt/vmwareapi/fake.py b/nova/tests/unit/virt/vmwareapi/fake.py index 6364cf528fc9..714dc29df530 100644 --- a/nova/tests/unit/virt/vmwareapi/fake.py +++ b/nova/tests/unit/virt/vmwareapi/fake.py @@ -366,8 +366,9 @@ class VirtualIDEController(DataObject): class VirtualLsiLogicController(DataObject): """VirtualLsiLogicController class.""" - def __init__(self, key=0, scsiCtlrUnitNumber=0): + def __init__(self, key=0, scsiCtlrUnitNumber=0, busNumber=0): self.key = key + self.busNumber = busNumber self.scsiCtlrUnitNumber = scsiCtlrUnitNumber self.device = [] diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index aeae9897d738..2005ed6b0997 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -215,6 +215,13 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual("ns0:ParaVirtualSCSIController", config_spec.device.obj_name) + def test_create_controller_spec_with_specfic_bus_number(self): + # Test controller spec with specifc bus number rather default 0 + config_spec = vm_util.create_controller_spec(fake.FakeFactory(), -101, + adapter_type=constants.ADAPTER_TYPE_LSILOGICSAS, + bus_number=1) + self.assertEqual(1, config_spec.device.busNumber) + def _vmdk_path_and_adapter_type_devices(self, filename, parent=None): # Test the adapter_type returned for a lsiLogic sas controller controller_key = 1000 @@ -333,6 +340,27 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual([1], taken[201]) self.assertEqual([7], taken[1000]) + def test_get_bus_number_for_scsi_controller(self): + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=7, + busNumber=0), + fake.VirtualLsiLogicController(1002, scsiCtlrUnitNumber=7, + busNumber=2)] + bus_number = vm_util._get_bus_number_for_scsi_controller(devices) + self.assertEqual(1, bus_number) + + def test_get_bus_number_for_scsi_controller_buses_used_up(self): + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=7, + busNumber=0), + fake.VirtualLsiLogicController(1001, scsiCtlrUnitNumber=7, + busNumber=1), + fake.VirtualLsiLogicController(1002, scsiCtlrUnitNumber=7, + busNumber=2), + fake.VirtualLsiLogicController(1003, scsiCtlrUnitNumber=7, + busNumber=3)] + self.assertRaises(vexc.VMwareDriverException, + vm_util._get_bus_number_for_scsi_controller, + devices) + def test_allocate_controller_key_and_unit_number_ide_default(self): # Test that default IDE controllers are used when there is a free slot # on them @@ -387,6 +415,23 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual(8, unit_number) self.assertIsNone(controller_spec) + def test_allocate_controller_key_and_unit_number_scsi_new_controller(self): + # Test that we allocate on existing SCSI controller if there is a free + # slot on it + devices = [fake.VirtualLsiLogicController(1000, scsiCtlrUnitNumber=15)] + for unit_number in range(15): + disk = fake.VirtualDisk(1000, unit_number) + devices.append(disk) + factory = fake.FakeFactory() + (controller_key, unit_number, + controller_spec) = vm_util.allocate_controller_key_and_unit_number( + factory, + devices, + constants.DEFAULT_ADAPTER_TYPE) + self.assertEqual(-101, controller_key) + self.assertEqual(0, unit_number) + self.assertEqual(1, controller_spec.device.busNumber) + def test_get_vnc_config_spec(self): fake_factory = fake.FakeFactory() result = vm_util.get_vnc_config_spec(fake_factory, diff --git a/nova/virt/vmwareapi/constants.py b/nova/virt/vmwareapi/constants.py index 5d331bf0dc69..ba4a2effc573 100644 --- a/nova/virt/vmwareapi/constants.py +++ b/nova/virt/vmwareapi/constants.py @@ -57,6 +57,9 @@ ADAPTER_TYPE_IDE = "ide" ADAPTER_TYPE_LSILOGICSAS = "lsiLogicsas" ADAPTER_TYPE_PARAVIRTUAL = "paraVirtual" +SCSI_ADAPTER_TYPES = [DEFAULT_ADAPTER_TYPE, ADAPTER_TYPE_LSILOGICSAS, + ADAPTER_TYPE_BUSLOGIC, ADAPTER_TYPE_PARAVIRTUAL] + SUPPORTED_FLAT_VARIANTS = ["thin", "preallocated", "thick", "eagerZeroedThick"] EXTENSION_KEY = 'org.openstack.compute' @@ -66,6 +69,9 @@ EXTENSION_TYPE_INSTANCE = 'instance' # One adapter has 16 slots but one reserved for controller SCSI_MAX_CONNECT_NUMBER = 15 +# The max number of SCSI adaptors that could be created on one instance. +SCSI_MAX_CONTROLLER_NUMBER = 4 + # This list was extracted from the installation iso image for ESX 6.0. # 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 713ec6e51ed0..05bd74c418c6 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -361,7 +361,8 @@ def get_vm_resize_spec(client_factory, vcpus, memory_mb, extra_specs, def create_controller_spec(client_factory, key, - adapter_type=constants.DEFAULT_ADAPTER_TYPE): + adapter_type=constants.DEFAULT_ADAPTER_TYPE, + bus_number=0): """Builds a Config Spec for the LSI or Bus Logic Controller's addition which acts as the controller for the virtual hard disk to be attached to the VM. @@ -383,7 +384,7 @@ def create_controller_spec(client_factory, key, virtual_controller = client_factory.create( 'ns0:VirtualLsiLogicController') virtual_controller.key = key - virtual_controller.busNumber = 0 + virtual_controller.busNumber = bus_number virtual_controller.sharedBus = "noSharing" virtual_device_config.device = virtual_controller return virtual_device_config @@ -739,6 +740,19 @@ def _find_allocated_slots(devices): return taken +def _get_bus_number_for_scsi_controller(devices): + """Return usable bus number when create new SCSI controller.""" + # Every SCSI controller will take a unique bus number + taken = [dev.busNumber for dev in devices if _is_scsi_controller(dev)] + # The max bus number for SCSI controllers is 3 + for i in range(constants.SCSI_MAX_CONTROLLER_NUMBER): + if i not in taken: + return i + msg = _('Only %d SCSI controllers are allowed to be ' + 'created on this instance.') % constants.SCSI_MAX_CONTROLLER_NUMBER + raise vexc.VMwareDriverException(msg) + + def allocate_controller_key_and_unit_number(client_factory, devices, adapter_type): """This function inspects the current set of hardware devices and returns @@ -754,10 +768,7 @@ def allocate_controller_key_and_unit_number(client_factory, devices, if adapter_type == constants.ADAPTER_TYPE_IDE: ide_keys = [dev.key for dev in devices if _is_ide_controller(dev)] ret = _find_controller_slot(ide_keys, taken, 2) - elif adapter_type in [constants.DEFAULT_ADAPTER_TYPE, - constants.ADAPTER_TYPE_LSILOGICSAS, - constants.ADAPTER_TYPE_BUSLOGIC, - constants.ADAPTER_TYPE_PARAVIRTUAL]: + elif adapter_type in constants.SCSI_ADAPTER_TYPES: scsi_keys = [dev.key for dev in devices if _is_scsi_controller(dev)] ret = _find_controller_slot(scsi_keys, taken, 16) if ret: @@ -765,8 +776,14 @@ def allocate_controller_key_and_unit_number(client_factory, devices, # create new controller with the specified type and return its spec controller_key = -101 + + # Get free bus number for new SCSI controller. + bus_number = 0 + if adapter_type in constants.SCSI_ADAPTER_TYPES: + bus_number = _get_bus_number_for_scsi_controller(devices) + controller_spec = create_controller_spec(client_factory, controller_key, - adapter_type) + adapter_type, bus_number) return controller_key, 0, controller_spec