From 664fb7175e84082a5b2300334aebff1110aeca71 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Mon, 9 Nov 2026 13:32:35 -0300 Subject: [PATCH] vmware: add support for SATA and NVMe disk controllers + refactorings --- .../agent/api/SecStorageVMSetupCommand.java | 1 - .../cloudstack/util/vmware/VMwareUtil.java | 6 + .../hypervisor/guru/VmwareVmImplementer.java | 2 +- .../vmware/manager/VmwareManager.java | 1 - .../vmware/manager/VmwareManagerImpl.java | 9 - .../vmware/resource/VmwareResource.java | 400 ++++++--- .../resource/VmwareStorageProcessor.java | 27 +- .../vmware/resource/VmwareResourceTest.java | 475 ++++++++++ .../com/cloud/api/query/QueryManagerImpl.java | 5 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 10 +- .../cloud/hypervisor/vmware/mo/BaseMO.java | 3 + .../cloud/hypervisor/vmware/mo/ClusterMO.java | 10 +- .../vmware/mo/DiskControllerType.java | 105 ++- .../cloud/hypervisor/vmware/mo/HostMO.java | 4 +- .../vmware/mo/HypervisorHostHelper.java | 192 ++--- .../vmware/mo/ScsiDiskControllerType.java | 26 - .../vmware/mo/VirtualMachineMO.java | 808 +++++++----------- .../hypervisor/vmware/mo/VmdkAdapterType.java | 12 - .../vmware/mo/VmwareHypervisorHost.java | 2 +- .../hypervisor/vmware/util/VmwareHelper.java | 301 +++++-- .../vmware/mo/VirtualMachineMOTest.java | 332 ++++++- .../vmware/util/VmwareHelperTest.java | 362 ++++++++ 22 files changed, 2128 insertions(+), 965 deletions(-) delete mode 100644 vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ScsiDiskControllerType.java diff --git a/core/src/main/java/com/cloud/agent/api/SecStorageVMSetupCommand.java b/core/src/main/java/com/cloud/agent/api/SecStorageVMSetupCommand.java index 400fc5aacdda..aaf9bee8bc4d 100644 --- a/core/src/main/java/com/cloud/agent/api/SecStorageVMSetupCommand.java +++ b/core/src/main/java/com/cloud/agent/api/SecStorageVMSetupCommand.java @@ -59,5 +59,4 @@ public String getCopyPassword() { public void setCopyPassword(String copyPassword) { this.copyPassword = copyPassword; } - } diff --git a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/util/vmware/VMwareUtil.java b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/util/vmware/VMwareUtil.java index ae93c3431fc9..b663e727daeb 100644 --- a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/util/vmware/VMwareUtil.java +++ b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/util/vmware/VMwareUtil.java @@ -63,12 +63,14 @@ import com.vmware.vim25.UpdateSet; import com.vmware.vim25.VimPortType; import com.vmware.vim25.VimService; +import com.vmware.vim25.VirtualAHCIController; import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceBackingInfo; import com.vmware.vim25.VirtualDisk; import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; import com.vmware.vim25.VirtualIDEController; import com.vmware.vim25.VirtualMachineConfigSpec; +import com.vmware.vim25.VirtualNVMEController; import com.vmware.vim25.VirtualSCSIController; public class VMwareUtil { @@ -294,6 +296,10 @@ public static String getDeviceBusName(List allDevices, VirtualDis return String.format("ide%d:%d", ((VirtualIDEController)device).getBusNumber(), disk.getUnitNumber()); } else if (device instanceof VirtualSCSIController) { return String.format("scsi%d:%d", ((VirtualSCSIController)device).getBusNumber(), disk.getUnitNumber()); + } else if (device instanceof VirtualAHCIController) { + return String.format("sata%d:%d", ((VirtualAHCIController)device).getBusNumber(), disk.getUnitNumber()); + } else if (device instanceof VirtualNVMEController) { + return String.format("nvme%d:%d", ((VirtualNVMEController)device).getBusNumber(), disk.getUnitNumber()); } else { throw new Exception("The device controller is not supported."); } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java index 3885e06e7408..7c652f68aed0 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VmwareVmImplementer.java @@ -322,7 +322,7 @@ private void setDiskControllers(VirtualMachineProfile vm, Map de } if (vm.getType() == VirtualMachine.Type.NetScalerVm) { - details.put(VmDetailConstants.ROOT_DISK_CONTROLLER, "scsi"); + details.put(VmDetailConstants.ROOT_DISK_CONTROLLER, DiskControllerType.scsi.toString()); } } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java index 8be881b94940..43a49d0a7cf9 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManager.java @@ -109,6 +109,5 @@ public interface VmwareManager { boolean isLegacyZone(long dcId); - public String getDataDiskController(); boolean hasNexusVSM(Long clusterId); } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java index 8052317817ad..7a0f231ba4e3 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java @@ -261,8 +261,6 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw private int _vCenterSessionTimeout = 1200000; // Timeout in milliseconds private String _rootDiskController = DiskControllerType.ide.toString(); - private final String _dataDiskController = DiskControllerType.osdefault.toString(); - private final Map _storageMounts = new HashMap<>(); private final Random _rand = new Random(System.currentTimeMillis()); @@ -635,8 +633,6 @@ public void setupResourceStartupParams(Map params) { params.put("vm.instancename.flag", _instanceNameFlag); params.put("service.console.name", _serviceConsoleName); params.put("management.portgroup.name", _managementPortGroupName); - params.put("vmware.root.disk.controller", _rootDiskController); - params.put("vmware.data.disk.controller", _dataDiskController); params.put("vmware.recycle.hung.wokervm", _recycleHungWorker); params.put("ports.per.dvportgroup", _portsPerDvPortGroup); } @@ -1095,11 +1091,6 @@ public String getRootDiskController() { return _rootDiskController; } - @Override - public String getDataDiskController() { - return _dataDiskController; - } - @Override public int getVcenterSessionTimeout() { return _vCenterSessionTimeout; diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 89b5b111e14f..afb23a65143e 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -58,10 +58,13 @@ import com.vmware.vim25.HostDatastoreBrowserSearchResults; import com.vmware.vim25.HostDatastoreBrowserSearchSpec; import com.vmware.vim25.VirtualCdromIsoBackingInfo; +import com.vmware.vim25.VirtualController; +import com.vmware.vim25.VirtualIDEController; import com.vmware.vim25.VirtualMachineConfigSummary; import com.vmware.vim25.VirtualTPM; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.backup.PrepareForBackupRestorationCommand; +import com.cloud.hypervisor.vmware.mo.DiskControllerType; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer; @@ -246,7 +249,6 @@ import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.DatastoreFile; import com.cloud.hypervisor.vmware.mo.DatastoreMO; -import com.cloud.hypervisor.vmware.mo.DiskControllerType; import com.cloud.hypervisor.vmware.mo.FeatureKeyConstants; import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO; import com.cloud.hypervisor.vmware.mo.HostMO; @@ -374,7 +376,6 @@ import com.vmware.vim25.VirtualMachineRuntimeInfo; import com.vmware.vim25.VirtualMachineToolsStatus; import com.vmware.vim25.VirtualMachineVideoCard; -import com.vmware.vim25.VirtualSCSIController; import com.vmware.vim25.VirtualUSBController; import com.vmware.vim25.VmConfigInfo; import com.vmware.vim25.VmConfigSpec; @@ -417,7 +418,6 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes protected boolean _instanceNameFlag = false; protected boolean _recycleHungWorker = false; - protected DiskControllerType _rootDiskController = DiskControllerType.ide; protected ManagedObjectReference _morHyperHost; protected final static ThreadLocal s_serviceContext = new ThreadLocal<>(); @@ -1973,63 +1973,162 @@ protected ScaleVmAnswer execute(ScaleVmCommand cmd) { return new ScaleVmAnswer(cmd, true, null); } - protected void ensureDiskControllers(VirtualMachineMO vmMo, Pair controllerInfo) throws Exception { - if (vmMo == null) { + /** + * Validates whether the instance has the required disk controllers, and creates them if it does not. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. + * @throws CloudRuntimeException if the instance does not have the required controllers, but this method is unable to create them. + */ + protected void ensureDiskControllers(VirtualMachineMO vmMo, Pair controllerInfo, + boolean isSystemVm, int numberOfDisks) throws Exception { + Pair chosenDiskControllers = VmwareHelper.chooseDiskControllers(controllerInfo, vmMo, null, null); + Set requiredDiskControllers = VmwareHelper.getRequiredDiskControllers(chosenDiskControllers, isSystemVm); + + if (vmHasRequiredControllers(vmMo, requiredDiskControllers) && vmHasEnoughDiskControllerCapacity(vmMo, chosenDiskControllers, numberOfDisks, isSystemVm)) { return; } - Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(controllerInfo, vmMo, null, null); - String scsiDiskController = HypervisorHostHelper.getScsiController(chosenDiskControllers); - if (scsiDiskController == null) { - return; + validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + validateNewDiskControllersSupportExistingDisks(vmMo, chosenDiskControllers, numberOfDisks, isSystemVm); + teardownAllDiskControllers(vmMo); + + VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec(); + VmwareHelper.addDiskControllersToVmConfigSpec(vmConfigSpec, requiredDiskControllers, isSystemVm); + if (!vmMo.configureVm(vmConfigSpec)) { + throw new CloudRuntimeException("Unable to configure virtual machine's disk controllers."); } + logger.info("Successfully added virtual machine [{}]'s required disk controllers [{}].", vmMo, requiredDiskControllers); + } - vmMo.getScsiDeviceControllerKeyNoException(); - // This VM needs SCSI controllers. - // Get count of existing scsi controllers. Helps not to attempt to create more than the maximum allowed 4 - // Get maximum among the bus numbers in use by scsi controllers. Safe to pick maximum, because we always go sequential allocating bus numbers. - Ternary scsiControllerInfo = vmMo.getScsiControllerInfo(); - int requiredNumScsiControllers = VmwareHelper.MAX_SCSI_CONTROLLER_COUNT - scsiControllerInfo.first(); - int availableBusNum = scsiControllerInfo.second() + 1; // method returned current max. bus number + protected boolean vmHasRequiredControllers(VirtualMachineMO vmMo, Set requiredDiskControllers) throws Exception { + Set requiredDiskControllerClasspaths = requiredDiskControllers.stream() + .map(DiskControllerType::getControllerReference) + .collect(Collectors.toSet()); - if (DiskControllerType.getType(scsiDiskController) != scsiControllerInfo.third()) { - logger.debug(String.format("Change controller type from: %s to: %s", scsiControllerInfo.third().toString(), - scsiDiskController)); - vmMo.tearDownDevices(new Class[]{VirtualSCSIController.class}); - vmMo.addScsiDeviceControllers(DiskControllerType.getType(scsiDiskController)); - return; + List existingControllers = vmMo.getControllers(); + for (VirtualController controller : existingControllers) { + String classpath = controller.getClass().getName(); + requiredDiskControllerClasspaths.remove(classpath); } - if (requiredNumScsiControllers == 0) { - return; + if (requiredDiskControllerClasspaths.isEmpty()) { + logger.debug("Virtual machine [{}] has all the required controllers [{}].", vmMo, requiredDiskControllers); + return true; } - if (scsiControllerInfo.first() > 0) { - // For VMs which already have a SCSI controller, do NOT attempt to add any more SCSI controllers & return the sub type. - // For Legacy VMs would have only 1 LsiLogic Parallel SCSI controller, and doesn't require more. - // For VMs created post device ordering support, 4 SCSI subtype controllers are ensured during deployment itself. No need to add more. - // For fresh VM deployment only, all required controllers should be ensured. - return; + logger.debug("Virtual machine [{}] does not have the following required controllers: [{}].", vmMo, requiredDiskControllerClasspaths); + return false; + } + + /*** + * Validates if the provided VirtualMachineMO's virtual hardware version supports the required disk controllers. + * @throws CloudRuntimeException if one of the disk controllers requires a version greater than the instance's virtual + * hardware version. + */ + protected void validateRequiredVirtualHardwareVersionForNewDiskControllers(VirtualMachineMO vmMo, Set requiredDiskControllers) throws Exception { + VmwareHelper.validateDiskControllersHardwareVersion(requiredDiskControllers, vmMo.getVirtualHardwareVersion()); + } + + /** + * Returns the number of devices that each disk controller type must support for the instance's disks. The CDROM + * device is also accounted for when the instance uses an IDE controller. + */ + protected Map getRequiredDevicesPerDiskControllerType(Pair controllerInfo, + int numberOfDisks, boolean isSystemVm) { + Map requiredDevices = new HashMap<>(); + requiredDevices.merge(controllerInfo.first(), 1, Integer::sum); + if (!isSystemVm) { + requiredDevices.merge(controllerInfo.second(), Math.max(0, numberOfDisks - 1), Integer::sum); + } + if (requiredDevices.containsKey(DiskControllerType.ide)) { + requiredDevices.merge(DiskControllerType.ide, 1, Integer::sum); } - ensureScsiDiskControllers(vmMo, scsiDiskController, requiredNumScsiControllers, availableBusNum); + return requiredDevices; } - private void ensureScsiDiskControllers(VirtualMachineMO vmMo, String scsiDiskController, int requiredNumScsiControllers, int availableBusNum) throws Exception { - // Pick the sub type of scsi - if (DiskControllerType.getType(scsiDiskController) == DiskControllerType.pvscsi) { - if (!vmMo.isPvScsiSupported()) { - String msg = "This VM doesn't support Vmware Paravirtual SCSI controller for virtual disks, because the virtual hardware version is less than 7."; - throw new Exception(msg); + /** + * Returns true if the instance's existing disk controllers have enough device nodes to support all the disks that + * will be attached. + */ + protected boolean vmHasEnoughDiskControllerCapacity(VirtualMachineMO vmMo, Pair controllerInfo, + int numberOfDisks, boolean isSystemVm) throws Exception { + Map requiredDevices = getRequiredDevicesPerDiskControllerType(controllerInfo, numberOfDisks, isSystemVm); + Map existingControllerCounts = new HashMap<>(); + for (VirtualController controller : vmMo.getControllers()) { + existingControllerCounts.merge(controller.getClass().getName(), 1, Integer::sum); + } + + for (Map.Entry requiredDevice : requiredDevices.entrySet()) { + DiskControllerType controllerType = requiredDevice.getKey(); + int existingControllerCount = Math.min(existingControllerCounts.getOrDefault(controllerType.getControllerReference(), 0), + controllerType.getMaxControllerCount()); + if (getMaxSupportedDevicesInBus(controllerType, existingControllerCount) < requiredDevice.getValue()) { + return false; + } + } + return true; + } + + /*** + * Validates if the provided disk controllers have enough device nodes to support all the disks that will be attached. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. + * @param numberOfDisks number of disks that will be attached to the instance. + * @throws CloudRuntimeException if the instance has more disks than the amount supported by the controllers. + */ + protected void validateNewDiskControllersSupportExistingDisks(VirtualMachineMO vmMo, Pair controllerInfo, + int numberOfDisks, boolean isSystemVm) throws Exception { + Map requiredDevices = getRequiredDevicesPerDiskControllerType(controllerInfo, numberOfDisks, isSystemVm); + for (Map.Entry requiredDevice : requiredDevices.entrySet()) { + DiskControllerType controllerType = requiredDevice.getKey(); + int controllerCount = isSystemVm ? 1 : controllerType.getMaxControllerCount(); + int maxDevicesInBus = getMaxSupportedDevicesInBus(controllerType, controllerCount); + if (maxDevicesInBus < requiredDevice.getValue()) { + throw new CloudRuntimeException(String.format("Virtual machine [%s] requires [%s] devices of disk controller type [%s], " + + "but the new disk controllers only support [%s] devices.", vmMo.getName(), requiredDevice.getValue(), + controllerType.getName(), maxDevicesInBus)); } - vmMo.ensurePvScsiDeviceController(requiredNumScsiControllers, availableBusNum); - } else if (DiskControllerType.getType(scsiDiskController) == DiskControllerType.lsisas1068) { - vmMo.ensureLsiLogicSasDeviceControllers(requiredNumScsiControllers, availableBusNum); - } else if (DiskControllerType.getType(scsiDiskController) == DiskControllerType.buslogic) { - vmMo.ensureBusLogicDeviceControllers(requiredNumScsiControllers, availableBusNum); - } else if (DiskControllerType.getType(scsiDiskController) == DiskControllerType.lsilogic) { - vmMo.ensureLsiLogicDeviceControllers(requiredNumScsiControllers, availableBusNum); } } + /** + * Returns the total amount of disks that the instance can have using the provided disk controller type, considering + * that the instance has the maximum amount of that disk controller type created. + */ + protected int getMaxSupportedDevicesInBus(DiskControllerType controllerType) { + return getMaxSupportedDevicesInBus(controllerType, controllerType.getMaxControllerCount()); + } + + /** + * Returns the total amount of disks that the instance can have using the provided disk controller type and the + * provided amount of controllers of that type. + */ + protected int getMaxSupportedDevicesInBus(DiskControllerType controllerType, int controllerCount) { + int devicesPerController = controllerType.getMaxDeviceCount(); + if (VmwareHelper.isControllerScsi(controllerType)) { + devicesPerController--; + } + return devicesPerController * controllerCount; + } + + /** + * Removes all disk controllers from the virtual machine. + */ + protected void teardownAllDiskControllers(VirtualMachineMO vmMo) throws Exception { + List allControllerTypes = VmwareHelper.getAllSupportedDiskControllerTypesExceptOsDefault(); + Set> classesToTeardown = new HashSet<>(); + + for (DiskControllerType controllerType : allControllerTypes) { + try { + Class diskControllerClass = Class.forName(controllerType.getControllerReference()); + if (diskControllerClass != VirtualIDEController.class) { + classesToTeardown.add(diskControllerClass); + } + } catch (ClassNotFoundException e) { + logger.debug("Could not find class for controller reference [{}]; ignoring it on teardown.", controllerType.getControllerReference()); + } + } + + vmMo.tearDownDevices(classesToTeardown.toArray(new Class[0])); + } + protected StartAnswer execute(StartCommand cmd) { VirtualMachineTO vmSpec = cmd.getVirtualMachine(); boolean vmAlreadyExistsInVcenter = false; @@ -2051,7 +2150,7 @@ protected StartAnswer execute(StartCommand cmd) { String vmNameOnVcenter = names.second(); DiskTO rootDiskTO = null; String bootMode = getBootModeFromVmSpec(vmSpec, deployAsIs); - Pair controllerInfo = getControllerInfoFromVmSpec(vmSpec); + Pair controllerInfo = getControllerInfoFromVmSpec(vmSpec); Boolean systemVm = vmSpec.getType().isUsedBySystem(); // Thus, vmInternalCSName always holds i-x-y, the cloudstack generated internal VM name. @@ -2077,6 +2176,7 @@ protected StartAnswer execute(StartCommand cmd) { DiskTO[] disks = validateDisks(vmSpec.getDisks()); assert (disks.length > 0); NicTO[] nics = vmSpec.getNics(); + int numberOfDisks = (int) Arrays.stream(disks).filter(disk -> disk.getType() != Volume.Type.ISO).count(); HashMap> dataStoresDetails = inferDatastoreDetailsFromDiskInfo(hyperHost, context, disks, cmd); if ((dataStoresDetails == null) || (dataStoresDetails.isEmpty())) { @@ -2087,9 +2187,6 @@ protected StartAnswer execute(StartCommand cmd) { VirtualMachineDiskInfoBuilder diskInfoBuilder = null; VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName); - DiskControllerType systemVmScsiControllerType = DiskControllerType.lsilogic; - int firstScsiControllerBusNum = 0; - int numScsiControllerForSystemVm = 1; boolean hasSnapshot = false; List> diskDatastores = null; @@ -2104,8 +2201,9 @@ protected StartAnswer execute(StartCommand cmd) { hasSnapshot = vmMo.hasSnapshot(); tearDownVmDevices(vmMo, hasSnapshot, deployAsIs); - ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, systemVmScsiControllerType, - numScsiControllerForSystemVm, firstScsiControllerBusNum, deployAsIs); + if (!hasSnapshot) { + ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, deployAsIs, numberOfDisks); + } } else { ManagedObjectReference morDc = hyperHost.getHyperHostDatacenter(); assert (morDc != null); @@ -2128,8 +2226,9 @@ protected StartAnswer execute(StartCommand cmd) { diskDatastores = vmMo.getAllDiskDatastores(); tearDownVmDevices(vmMo, hasSnapshot, deployAsIs); - ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, systemVmScsiControllerType, - numScsiControllerForSystemVm, firstScsiControllerBusNum, deployAsIs); + if (!hasSnapshot) { + ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, deployAsIs, numberOfDisks); + } } } else { // If a VM with the same name is found in a different cluster in the DC, unregister the old VM and configure a new VM (cold-migration). @@ -2187,6 +2286,9 @@ protected StartAnswer execute(StartCommand cmd) { } } tearDownVm(vmMo); + if (vmMo != null && !vmMo.hasSnapshot()) { + ensureDiskControllersInternal(vmMo, systemVm, controllerInfo, deployAsIs, numberOfDisks); + } } else if (!hyperHost.createBlankVm(vmNameOnVcenter, vmInternalCSName, vmSpec.getCpus(), vmSpec.getMaxSpeed().intValue(), getReservedCpuMHZ(vmSpec), vmSpec.isLimitCpuUse(), (int) (vmSpec.getMaxRam() / ResourceType.bytesToMiB), getReservedMemoryMb(vmSpec), guestOsId, rootDiskDataStoreDetails.first(), false, controllerInfo, systemVm)) { @@ -2225,10 +2327,6 @@ protected StartAnswer execute(StartCommand cmd) { VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec(); int i = 0; - int ideUnitNumber = !deployAsIs ? 0 : vmMo.getNextIDEDeviceNumber(); - int scsiUnitNumber = !deployAsIs ? 0 : vmMo.getNextScsiDiskDeviceNumber(); - int ideControllerKey = vmMo.getIDEDeviceControllerKey(); - int scsiControllerKey = vmMo.getScsiDeviceControllerKeyNoException(); VirtualDeviceConfigSpec[] deviceConfigSpecArray = new VirtualDeviceConfigSpec[totalChangeDevices]; DiskTO[] sortedDisks = sortVolumesByDeviceId(disks); VmwareHelper.setBasicVmConfig(vmConfigSpec, vmSpec.getCpus(), vmSpec.getMaxSpeed(), getReservedCpuMHZ(vmSpec), (int) (vmSpec.getMaxRam() / (1024 * 1024)), @@ -2270,8 +2368,17 @@ protected StartAnswer execute(StartCommand cmd) { // // Setup ISO device // + Pair chosenDiskControllers = VmwareHelper.chooseDiskControllers(controllerInfo, vmMo, null, null); + Map diskControllerCurrentUnitNumbers = createDiskControllerUnitNumberMap(vmMo, chosenDiskControllers, deployAsIs); // prepare systemvm patch ISO + String ideClasspath = VirtualIDEController.class.getName(); + Integer ideUnitNumberValue = diskControllerCurrentUnitNumbers.get(ideClasspath); + boolean requiresIdeDevice = vmSpec.getType() != VirtualMachine.Type.User || !deployAsIs || volIso != null; + if (requiresIdeDevice && ideUnitNumberValue == null) { + throw new CloudRuntimeException("Unable to find an available IDE disk controller."); + } + int ideUnitNumber = ideUnitNumberValue != null ? ideUnitNumberValue : 0; if (vmSpec.getType() != VirtualMachine.Type.User) { // attach ISO (for patching of system VM) Pair secStoreUrlAndId = mgr.getSecondaryStorageStoreUrlAndId(Long.parseLong(_dcId)); @@ -2290,8 +2397,12 @@ protected StartAnswer execute(StartCommand cmd) { DatastoreMO secDsMo = new DatastoreMO(hyperHost.getContext(), morSecDs); deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec(); - Pair isoInfo = VmwareHelper.prepareIsoDevice(vmMo, - null, secDsMo.getMor(), true, true, ideUnitNumber++, i + 1); + Pair isoInfo = VmwareHelper.prepareIsoDevice(vmMo, null, secDsMo.getMor(), + true, true, ideUnitNumber, i + 1); + if (isoInfo.second()) { + ideUnitNumber++; + } + diskControllerCurrentUnitNumbers.replace(ideClasspath, ideUnitNumber); deviceConfigSpecArray[i].setDevice(isoInfo.first()); if (isoInfo.second()) { if (logger.isDebugEnabled()) @@ -2308,13 +2419,20 @@ protected StartAnswer execute(StartCommand cmd) { if (volIso != null) { for (DiskTO vol : disks) { if (vol.getType() == Volume.Type.ISO) { - configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber++, i); + if (configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber, i)) { + ideUnitNumber++; + } + diskControllerCurrentUnitNumbers.replace(ideClasspath, ideUnitNumber); i++; } } } else { deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec(); - Pair isoInfo = VmwareHelper.prepareIsoDevice(vmMo, null, null, true, true, ideUnitNumber++, i + 1); + Pair isoInfo = VmwareHelper.prepareIsoDevice(vmMo, null, null, true, true, ideUnitNumber, i + 1); + if (isoInfo.second()) { + ideUnitNumber++; + } + diskControllerCurrentUnitNumbers.replace(ideClasspath, ideUnitNumber); deviceConfigSpecArray[i].setDevice(isoInfo.first()); if (isoInfo.second()) { if (logger.isDebugEnabled()) @@ -2331,9 +2449,6 @@ protected StartAnswer execute(StartCommand cmd) { } } - int controllerKey; - Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(controllerInfo,vmMo, null, null); - // // Setup ROOT/DATA disk devices // @@ -2344,7 +2459,10 @@ protected StartAnswer execute(StartCommand cmd) { for (DiskTO vol : sortedDisks) { if (vol.getType() == Volume.Type.ISO) { if (deployAsIs) { - configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber++, i); + if (configureIso(hyperHost, vmMo, vol, deviceConfigSpecArray, ideUnitNumber, i)) { + ideUnitNumber++; + } + diskControllerCurrentUnitNumbers.replace(ideClasspath, ideUnitNumber); i++; } continue; @@ -2360,48 +2478,27 @@ protected StartAnswer execute(StartCommand cmd) { continue; } - String diskController = getDiskController(vmMo, matchingExistingDisk, vol, chosenDiskControllers, deployAsIs); - if (DiskControllerType.getType(diskController) == DiskControllerType.ide) { - controllerKey = vmMo.getIDEControllerKey(ideUnitNumber); - if (vol.getType() == Volume.Type.DATADISK) { - // Could be result of flip due to user configured setting or "osdefault" for data disks - // Ensure maximum of 2 data volumes over IDE controller, 3 includeing root volume - if (vmMo.getNumberOfVirtualDisks() > 3) { - throw new CloudRuntimeException("Found more than 3 virtual disks attached to this VM [" + vmMo.getVmName() + "]. Unable to implement the disks over " - + diskController + " controller, as maximum number of devices supported over IDE controller is 4 including CDROM device."); - } - } - } else { - if (VmwareHelper.isReservedScsiDeviceNumber(scsiUnitNumber)) { - scsiUnitNumber++; + if (!hasSnapshot) { + DiskControllerType diskController = getControllerForDisk(vmMo, matchingExistingDisk, vol, chosenDiskControllers, deployAsIs); + Integer currentUnitNumber = diskControllerCurrentUnitNumbers.get(diskController.getControllerReference()); + if (currentUnitNumber == null) { + throw new CloudRuntimeException(String.format("Unable to find an available disk controller of the required type: [%s].", diskController.getName())); } + int unitNumber = currentUnitNumber; + + int diskControllerNumber = unitNumber / diskController.getMaxDeviceCount(); + VirtualController diskControllerDevice = (VirtualController) vmMo.getNthDevice(diskController.getControllerReference(), diskControllerNumber); + vmMo.validateDiskControllerIsAvailable(diskControllerDevice, diskController); // This may be unnecessary, as a validation of whether the controllers support all disks was already performed. + int deviceNumber = unitNumber % diskController.getMaxDeviceCount(); - controllerKey = vmMo.getScsiDiskControllerKeyNoException(diskController, scsiUnitNumber); - if (controllerKey == -1) { - // This may happen for ROOT legacy VMs which doesn't have recommended disk controller when global configuration parameter 'vmware.root.disk.controller' is set to "osdefault" - // Retrieve existing controller and use. - Ternary vmScsiControllerInfo = vmMo.getScsiControllerInfo(); - DiskControllerType existingControllerType = vmScsiControllerInfo.third(); - controllerKey = vmMo.getScsiDiskControllerKeyNoException(existingControllerType.toString(), scsiUnitNumber); - } - } - if (!hasSnapshot) { deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec(); String[] diskChain = syncDiskChain(dcMo, vmMo, vol, matchingExistingDisk, volumeDsDetails.second()); - int deviceNumber; - if (controllerKey == vmMo.getIDEControllerKey(ideUnitNumber)) { - deviceNumber = ideUnitNumber % VmwareHelper.MAX_ALLOWED_DEVICES_IDE_CONTROLLER; - ideUnitNumber++; - } else { - deviceNumber = scsiUnitNumber % VmwareHelper.MAX_ALLOWED_DEVICES_SCSI_CONTROLLER; - scsiUnitNumber++; - } - VolumeObjectTO volumeTO = (VolumeObjectTO) vol.getData(); Long maxIops = volumeTO.getIopsWriteRate() + volumeTO.getIopsReadRate(); - VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, controllerKey, diskChain, volumeDsDetails.first(), deviceNumber, i + 1, maxIops); + VirtualDevice device = VmwareHelper.prepareDiskDevice(vmMo, null, diskControllerDevice.getKey(), diskChain, + volumeDsDetails.first(), deviceNumber, i + 1, maxIops); logger.debug(LogUtils.logGsonWithoutException("The following definitions will be used to start the VM: virtual device [%s], volume [%s].", device, volumeTO)); diskStoragePolicyId = volumeTO.getvSphereStoragePolicyId(); @@ -2425,11 +2522,9 @@ protected StartAnswer execute(StartCommand cmd) { logger.debug("Prepare volume at new device " + _gson.toJson(device)); i++; - } else { - if (controllerKey == vmMo.getIDEControllerKey(ideUnitNumber)) - ideUnitNumber++; - else - scsiUnitNumber++; + + Integer nextUnitNumber = vmMo.getNextAvailableDeviceNumberForType(diskController, unitNumber + 1); + diskControllerCurrentUnitNumbers.replace(diskController.getControllerReference(), nextUnitNumber); } } @@ -2673,6 +2768,34 @@ protected StartAnswer execute(StartCommand cmd) { } } + /** + * Returns a hash-map with keys being the classpath of existing disk controllers, and values representing the next available + * unit number for the disk controller type. This hash-map is used in the virtual machine start workflow to associate + * the disks with their respective controller. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. Ignored for deploy-as-is templates. + */ + protected Map createDiskControllerUnitNumberMap(VirtualMachineMO vmMo, Pair controllerInfo, + boolean deployAsIs) throws Exception { + Set diskControllers = new HashSet<>(); + if (deployAsIs) { + diskControllers.addAll(vmMo.getExistingDiskControllerTypes()); + } else { + diskControllers.add(controllerInfo.first()); + diskControllers.add(controllerInfo.second()); + } + diskControllers.add(VmwareHelper.getDiskControllerType(null, VirtualIDEController.class.getName())); + + Map currentUnitNumberMap = new HashMap<>(); + for (DiskControllerType diskController : diskControllers) { + String classpath = diskController.getControllerReference(); + Integer nextAvailableDeviceNumber = vmMo.getNextAvailableDeviceNumberForType(diskController); + if (nextAvailableDeviceNumber != null) { + currentUnitNumberMap.put(classpath, nextAvailableDeviceNumber); + } + } + return currentUnitNumberMap; + } + private Pair getVolumeDatastoreDetails(DiskTO vol, HashMap> dataStoresDetails) throws Exception { boolean managed = false; String iScsiName = null; @@ -2767,7 +2890,7 @@ private DiskTO[] getDisks(DiskTO[] sortedDisks) { vol.getPath().contains(ConfigDrive.CONFIGDRIVEDIR))) || (vol.getType() != Volume.Type.ISO)).toArray(DiskTO[]::new); } - private void configureIso(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo, DiskTO vol, + private boolean configureIso(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo, DiskTO vol, VirtualDeviceConfigSpec[] deviceConfigSpecArray, int ideUnitNumber, int i) throws Exception { TemplateObjectTO iso = (TemplateObjectTO) vol.getData(); @@ -2796,7 +2919,9 @@ private void configureIso(VmwareHypervisorHost hyperHost, VirtualMachineMO vmMo, logger.debug("Prepare ISO volume at existing device " + _gson.toJson(isoInfo.first())); deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.EDIT); } + return isoInfo.second(); } + return false; } private String mapAdapterType(String adapterStringFromOVF) { @@ -2839,15 +2964,10 @@ private void configureVNC(VirtualMachineTO vmSpec, ArrayList extraO } - private void ensureDiskControllersInternal(VirtualMachineMO vmMo, Boolean systemVm, - Pair controllerInfo, - DiskControllerType systemVmScsiControllerType, - int numScsiControllerForSystemVm, - int firstScsiControllerBusNum, boolean deployAsIs) throws Exception { - if (systemVm) { - ensureScsiDiskControllers(vmMo, systemVmScsiControllerType.toString(), numScsiControllerForSystemVm, firstScsiControllerBusNum); - } else if (!deployAsIs) { - ensureDiskControllers(vmMo, controllerInfo); + protected void ensureDiskControllersInternal(VirtualMachineMO vmMo, Boolean systemVm, Pair controllerInfo, + boolean deployAsIs, int numberOfDisks) throws Exception { + if (systemVm || !deployAsIs) { + ensureDiskControllers(vmMo, controllerInfo, systemVm, numberOfDisks); } } @@ -2871,11 +2991,11 @@ private String getGuestOsIdFromVmSpec(VirtualMachineTO vmSpec, boolean deployAsI return translateGuestOsIdentifier(vmSpec.getArch(), vmSpec.getOs(), vmSpec.getPlatformEmulator()).value(); } - private Pair getControllerInfoFromVmSpec(VirtualMachineTO vmSpec) throws CloudRuntimeException { + protected Pair getControllerInfoFromVmSpec(VirtualMachineTO vmSpec) throws CloudRuntimeException { + boolean isSystemVm = vmSpec.getType().isUsedBySystem(); String rootDiskControllerDetail = vmSpec.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER); String dataDiskControllerDetail = vmSpec.getDetails().get(VmDetailConstants.DATA_DISK_CONTROLLER); - VmwareHelper.validateDiskControllerDetails(rootDiskControllerDetail, dataDiskControllerDetail); - return new Pair<>(rootDiskControllerDetail, dataDiskControllerDetail); + return VmwareHelper.getDiskControllersFromVmSettings(rootDiskControllerDetail, dataDiskControllerDetail, isSystemVm); } private String getBootModeFromVmSpec(VirtualMachineTO vmSpec, boolean deployAsIs) { @@ -3114,11 +3234,12 @@ private void resizeRootDiskOnVMStart(VirtualMachineMO vmMo, DiskTO rootDiskTO, V boolean resizingSupported = false; String deviceBusName = diskInfo.getDiskDeviceBusName(); - if (deviceBusName != null && (deviceBusName.toLowerCase().contains("scsi") || deviceBusName.toLowerCase().contains("lsi"))) { + String busName = deviceBusName == null ? "" : deviceBusName.toLowerCase(); + if (busName.contains("scsi") || busName.contains("lsi") || busName.contains("sata") || busName.contains("nvme")) { resizingSupported = true; } if (!resizingSupported) { - logger.warn("Resizing of root disk is only support for scsi device/bus, the provide VM's disk device bus name is " + diskInfo.getDiskDeviceBusName()); + logger.warn("Resizing of root disk is only supported for SCSI, SATA and NVMe device/bus, the provided VM's disk device bus name is " + diskInfo.getDiskDeviceBusName()); throw new CloudRuntimeException("Unsupported VM root disk device bus: " + diskInfo.getDiskDeviceBusName()); } @@ -3655,23 +3776,30 @@ private VirtualMachineDiskInfo getMatchingExistingDisk(VirtualMachineDiskInfoBui return getMatchingExistingDiskWithVolumeDetails(diskInfoBuilder, volume.getPath(), chainInfo, isManaged, iScsiName, datastoreUUID, hyperHost, context); } - private String getDiskController(VirtualMachineMO vmMo, VirtualMachineDiskInfo matchingExistingDisk, DiskTO vol, Pair controllerInfo, boolean deployAsIs) throws Exception { - DiskControllerType controllerType = DiskControllerType.none; - if (deployAsIs && matchingExistingDisk != null) { - String currentBusName = matchingExistingDisk.getDiskDeviceBusName(); + /** + * Returns the disk controller type that should be used for the disk. If the instance uses a deploy-as-is template, + * tries to choose based on the current bus name first, but chooses any available existing disk controller if unable + * to choose a type based on the bus name; if the instance does not use a deploy-as-is template, chooses based on + * controllerInfo. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. Ignored if the instance uses a deploy-as-is template. + * @throws CloudRuntimeException if the instance uses a deploy-as-is template, but no disk controller is available. + */ + protected DiskControllerType getControllerForDisk(VirtualMachineMO vmMo, VirtualMachineDiskInfo matchingExistingDisk, + DiskTO vol, Pair controllerInfo, + boolean deployAsIs) throws Exception { + if (deployAsIs) { + String currentBusName = matchingExistingDisk != null ? matchingExistingDisk.getDiskDeviceBusName() : null; if (currentBusName != null) { - logger.info("Chose disk controller based on existing information: " + currentBusName); - if (currentBusName.startsWith("ide")) { - controllerType = DiskControllerType.ide; - } else if (currentBusName.startsWith("scsi")) { - controllerType = DiskControllerType.scsi; + DiskControllerType controllerType = vmMo.getDiskControllerTypeByBusName(currentBusName); + if (controllerType != null) { + logger.debug("Choosing disk controller [{}] for virtual machine [{}] based on current bus name [{}].", controllerType.getName(), vmMo, currentBusName); + return controllerType; } } - if (controllerType == DiskControllerType.scsi || controllerType == DiskControllerType.none) { - Ternary vmScsiControllerInfo = vmMo.getScsiControllerInfo(); - controllerType = vmScsiControllerInfo.third(); - } - return controllerType.toString(); + DiskControllerType existingAvailableDiskController = vmMo.getAnyExistingAvailableDiskController(); + logger.debug("Falling back to existing disk controller [{}] for virtual machine [{}] as no matching controller was found based on the current bus name [{}].", + existingAvailableDiskController, vmMo, currentBusName); + return existingAvailableDiskController; } return VmwareHelper.getControllerBasedOnDiskType(controllerInfo, vol); @@ -6979,14 +7107,6 @@ public boolean configure(String name, Map params) throws Configu if (value != null && value.equalsIgnoreCase("true")) _recycleHungWorker = true; - value = (String) params.get("vmware.root.disk.controller"); - if (value != null && value.equalsIgnoreCase("scsi")) - _rootDiskController = DiskControllerType.scsi; - else if (value != null && value.equalsIgnoreCase("ide")) - _rootDiskController = DiskControllerType.ide; - else - _rootDiskController = DiskControllerType.osdefault; - Integer intObj = (Integer) params.get("ports.per.dvportgroup"); if (intObj != null) _portsPerDvPortGroup = intObj; diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java index 253bbf2d0930..8dfafb810e64 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java @@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; +import com.cloud.hypervisor.vmware.mo.DiskControllerType; import org.apache.cloudstack.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachCommand; import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplianceCommand; @@ -76,7 +77,6 @@ import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.DatastoreFile; import com.cloud.hypervisor.vmware.mo.DatastoreMO; -import com.cloud.hypervisor.vmware.mo.DiskControllerType; import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO; import com.cloud.hypervisor.vmware.mo.HostMO; import com.cloud.hypervisor.vmware.mo.HostStorageSystemMO; @@ -2102,18 +2102,19 @@ private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean AttachAnswer answer = new AttachAnswer(disk); if (isAttach) { + // Let's first find which disk controller should be used for the volume being attached. + // + // If `VmDetailConstants.ROOT_DISK_CONTROLLER` or `VmDetailConstants.DATA_DISK_CONTROLLER` are not present + // in `controllerInfo`, use the legacy defaults to preserve compatibility with older instances. String rootDiskControllerDetail = DiskControllerType.ide.toString(); - if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER))) { - rootDiskControllerDetail = controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER); - } - String dataDiskControllerDetail = getLegacyVmDataDiskController(); - if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER))) { - dataDiskControllerDetail = controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER); + String dataDiskControllerDetail = DiskControllerType.lsilogic.toString(); + if (controllerInfo != null) { + rootDiskControllerDetail = StringUtils.defaultIfEmpty(controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER), rootDiskControllerDetail); + dataDiskControllerDetail = StringUtils.defaultIfEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER), dataDiskControllerDetail); } - - VmwareHelper.validateDiskControllerDetails(rootDiskControllerDetail, dataDiskControllerDetail); - Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(new Pair<>(rootDiskControllerDetail, dataDiskControllerDetail), vmMo, null, null); - String diskController = VmwareHelper.getControllerBasedOnDiskType(chosenDiskControllers, disk); + Pair specifiedDiskControllers = VmwareHelper.getDiskControllersFromVmSettings(rootDiskControllerDetail, dataDiskControllerDetail, false); + Pair chosenDiskControllers = VmwareHelper.chooseDiskControllers(specifiedDiskControllers, vmMo, null, null); + DiskControllerType diskController = VmwareHelper.getControllerBasedOnDiskType(chosenDiskControllers, disk); vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController, storagePolicyId, volumeTO.getIopsReadRate() + volumeTO.getIopsWriteRate()); VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); @@ -3751,10 +3752,6 @@ private String deriveTemplateUuidOnHost(VmwareHypervisorHost hyperHost, String s return templateUuid; } - private String getLegacyVmDataDiskController() throws Exception { - return DiskControllerType.lsilogic.toString(); - } - void setNfsVersion(String nfsVersion){ this._nfsVersion = nfsVersion; logger.debug("VmwareProcessor instance now using NFS version: " + nfsVersion); diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java index be7ee46f6dda..dbb21016fd79 100644 --- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java +++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java @@ -38,24 +38,37 @@ import java.util.Date; import java.util.EnumMap; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import com.cloud.agent.api.Command; import com.cloud.agent.api.ScaleVmAnswer; +import com.cloud.agent.api.to.DiskTO; import com.cloud.hypervisor.vmware.mo.DatastoreMO; import com.cloud.hypervisor.vmware.mo.HostDatastoreBrowserMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; import com.cloud.hypervisor.vmware.util.VmwareHelper; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; import com.vmware.vim25.FileInfo; import com.vmware.vim25.HostDatastoreBrowserSearchResults; import com.vmware.vim25.HostDatastoreBrowserSearchSpec; +import com.vmware.vim25.VirtualAHCIController; +import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualLsiLogicController; +import com.vmware.vim25.VirtualNVMEController; import com.vmware.vim25.VirtualTPM; import org.apache.cloudstack.api.ApiConstants; +import com.cloud.hypervisor.vmware.mo.DiskControllerType; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer; import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; + +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -144,6 +157,8 @@ public VmwareHypervisorHost getHyperHost(VmwareContext context, Command cmd) { @Mock VirtualMachineTO vmSpec; @Mock + DiskTO diskTo; + @Mock VmwareHypervisorHost hyperHost; @Mock VirtualMachineMO vmMo; @@ -186,8 +201,11 @@ public VmwareHypervisorHost getHyperHost(VmwareContext context, Command cmd) { @Mock HostCapability hostCapability; @Mock + VirtualMachineDiskInfo virtualMachineDiskInfo; + @Mock ManagedObjectReference _morHyperHost; + CopyCommand storageCmd; EnumMap params = new EnumMap(VmwareStorageProcessorConfigurableFields.class); @@ -838,6 +856,463 @@ public void testExecuteWithEmptyPath() throws Exception { assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified()); } + private Set getRequiredDiskControllersForValidateRequiredVirtualHardwareVersionForNewDiskControllersTests() { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Set requiredDiskControllers = new HashSet<>(); + requiredDiskControllers.add(lsilogicController); + requiredDiskControllers.add(nvmeController); + return requiredDiskControllers; + } + + @Test + public void validateRequiredVirtualHardwareVersionForNewDiskControllersTestDoesNothingWhenControllerWithSupportedVirtualHardwareVersionIsRequired() throws Exception { + Set requiredDiskControllers = getRequiredDiskControllersForValidateRequiredVirtualHardwareVersionForNewDiskControllersTests(); + Mockito.when(vmMo.getVirtualHardwareVersion()).thenReturn(13); + + vmwareResource.validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + } + + @Test(expected = CloudRuntimeException.class) + public void validateRequiredVirtualHardwareVersionForNewDiskControllersTestThrowsExceptionWhenControllerWithGreaterVirtualHardwareVersionIsRequired() throws Exception { + Set requiredDiskControllers = getRequiredDiskControllersForValidateRequiredVirtualHardwareVersionForNewDiskControllersTests(); + Mockito.when(vmMo.getVirtualHardwareVersion()).thenReturn(8); + + vmwareResource.validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + } + + @Test + public void validateNewDiskControllersSupportExistingDisksTestDoesNothingWhenBothControllersAreTheSameAndMaximumDevicesAreOnTheBus() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(ideController, ideController); + + vmwareResource.validateNewDiskControllersSupportExistingDisks(vmMo, controllerInfo, 3, false); + } + + @Test(expected = CloudRuntimeException.class) + public void validateNewDiskControllersSupportExistingDisksTestThrowsExceptionWhenBothControllersAreTheSameAndMoreThanMaximumDevicesAreOnTheBus() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(ideController, ideController); + + vmwareResource.validateNewDiskControllersSupportExistingDisks(vmMo, controllerInfo, 4, false); + } + + @Test + public void validateNewDiskControllersSupportExistingDisksTestDoesNothingWhenControllersAreNotTheSameAndMaximumDevicesAreOnDataDiskBus() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(nvmeController, ideController); + + vmwareResource.validateNewDiskControllersSupportExistingDisks(vmMo, controllerInfo, 4, false); + } + + @Test(expected = CloudRuntimeException.class) + public void validateNewDiskControllersSupportExistingDisksTestThrowsExceptionWhenBothControllersAreNotTheSameAndMoreThanMaximumDevicesAreOnDataDiskBus() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(nvmeController, ideController); + + vmwareResource.validateNewDiskControllersSupportExistingDisks(vmMo, controllerInfo, 5, false); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsFalseWhenExistingControllersCannotHoldAllDisks() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(lsilogicController, lsilogicController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualLsiLogicController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 16, false); + + Assert.assertFalse(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsTrueWhenExistingControllersCanHoldAllDisks() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(lsilogicController, lsilogicController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualLsiLogicController(), new VirtualLsiLogicController(), + new VirtualLsiLogicController(), new VirtualLsiLogicController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 16, false); + + Assert.assertTrue(result); + } + + @Test + public void getRequiredDevicesPerDiskControllerTypeTestReturnsTotalDisksWhenRootAndDataControllersAreTheSame() { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(lsilogicController, lsilogicController); + + Map result = vmwareResource.getRequiredDevicesPerDiskControllerType(controllerInfo, 3, false); + + Assert.assertEquals(Map.of(lsilogicController, 3), result); + } + + @Test + public void getRequiredDevicesPerDiskControllerTypeTestReturnsRootAndDataDevicesWhenControllersAreDifferent() { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(nvmeController, lsilogicController); + + Map result = vmwareResource.getRequiredDevicesPerDiskControllerType(controllerInfo, 4, false); + + Assert.assertEquals(Map.of(nvmeController, 1, lsilogicController, 3), result); + } + + @Test + public void getRequiredDevicesPerDiskControllerTypeTestAccountsForCdromWhenControllerIsIde() { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(ideController, ideController); + + Map result = vmwareResource.getRequiredDevicesPerDiskControllerType(controllerInfo, 2, false); + + Assert.assertEquals(Map.of(ideController, 3), result); + } + + @Test + public void getRequiredDevicesPerDiskControllerTypeTestRequiresOnlyRootControllerForSystemVms() { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair controllerInfo = new Pair<>(lsilogicController, nvmeController); + + Map result = vmwareResource.getRequiredDevicesPerDiskControllerType(controllerInfo, 5, true); + + Assert.assertEquals(Map.of(lsilogicController, 1), result); + } + + @Test + public void getRequiredDevicesPerDiskControllerTypeTestAccountsForCdromForSystemVmsOverIde() { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair controllerInfo = new Pair<>(ideController, nvmeController); + + Map result = vmwareResource.getRequiredDevicesPerDiskControllerType(controllerInfo, 1, true); + + Assert.assertEquals(Map.of(ideController, 2), result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsFalseWhenNoControllersExist() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(lsilogicController, lsilogicController); + Mockito.when(vmMo.getControllers()).thenReturn(Collections.emptyList()); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 1, false); + + Assert.assertFalse(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsTrueWhenDifferentControllerTypesHaveEnoughCapacity() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Pair controllerInfo = new Pair<>(nvmeController, lsilogicController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualNVMEController(), new VirtualLsiLogicController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 5, false); + + Assert.assertTrue(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsFalseWhenOneControllerTypeLacksCapacity() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(nvmeController, ideController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualNVMEController(), new VirtualIDEController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 5, false); + + Assert.assertFalse(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestAccountsForCdromWhenControllersAreIde() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(ideController, ideController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualIDEController(), new VirtualIDEController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 3, false); + + Assert.assertTrue(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestReturnsFalseWhenIdeCapacityIsExceededIncludingCdrom() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Pair controllerInfo = new Pair<>(ideController, ideController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualIDEController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 3, false); + + Assert.assertFalse(result); + } + + @Test + public void vmHasEnoughDiskControllerCapacityTestIgnoresDataControllerForSystemVms() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair controllerInfo = new Pair<>(lsilogicController, nvmeController); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(new VirtualLsiLogicController())); + + boolean result = vmwareResource.vmHasEnoughDiskControllerCapacity(vmMo, controllerInfo, 5, true); + + Assert.assertTrue(result); + } + + @Test + public void getMaxSupportedDevicesInBusTestExpectedValueWhenBusIsNotScsi() { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + + int result = vmwareResource.getMaxSupportedDevicesInBus(nvmeController); + + Assert.assertEquals(nvmeController.getMaxControllerCount() * nvmeController.getMaxDeviceCount(), result); + } + + @Test + public void getMaxSupportedDevicesInBusTestExpectedValueWhenBusIsScsi() { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + + int result = vmwareResource.getMaxSupportedDevicesInBus(lsilogicController); + + Assert.assertEquals(lsilogicController.getMaxControllerCount() * (lsilogicController.getMaxDeviceCount() - 1), result); + } + + @Test + public void teardownAllDiskControllersTestTearsdownAllControllersWithValidControllerTypes() throws Exception { + vmwareResource.teardownAllDiskControllers(vmMo); + + long numberOfConfiguredDiskControllersToTeardown = VmwareHelper.getAllSupportedDiskControllerTypesExceptOsDefault().stream() + .map(DiskControllerType::getControllerReference) + .distinct() + .filter(controllerReference -> !DiskControllerType.ide.getControllerReference().equals(controllerReference)) + .count(); + Mockito.verify(vmMo, Mockito.times(1)).tearDownDevices(Mockito.argThat((Class[] c) -> c.length == numberOfConfiguredDiskControllersToTeardown)); + } + + @Test + public void createDiskControllerUnitNumberMapTestInitializesValuesAtNextAvailableUnitNumberWhenTemplateIsNotDeployAsIs() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Mockito.doReturn(10).when(vmMo).getNextAvailableDeviceNumberForType(ideController); + Mockito.doReturn(11).when(vmMo).getNextAvailableDeviceNumberForType(nvmeController); + Mockito.doReturn(12).when(vmMo).getNextAvailableDeviceNumberForType(lsilogicController); + Pair controllerInfo = new Pair<>(nvmeController, lsilogicController); + + Map result = vmwareResource.createDiskControllerUnitNumberMap(vmMo, controllerInfo, false); + + Assert.assertEquals(10, (int) result.get(ideController.getControllerReference())); + Assert.assertEquals(11, (int) result.get(nvmeController.getControllerReference())); + Assert.assertEquals(12, (int) result.get(lsilogicController.getControllerReference())); + } + + @Test + public void createDiskControllerUnitNumberMapTestInitializesValuesAtNextAvailableUnitNumberWhenTemplateIsDeployAsIs() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Mockito.when(vmMo.getExistingDiskControllerTypes()).thenReturn(new HashSet<>(Arrays.asList(ideController, nvmeController, lsilogicController))); + Mockito.doReturn(10).when(vmMo).getNextAvailableDeviceNumberForType(ideController); + Mockito.doReturn(11).when(vmMo).getNextAvailableDeviceNumberForType(nvmeController); + Mockito.doReturn(12).when(vmMo).getNextAvailableDeviceNumberForType(lsilogicController); + Pair controllerInfo = new Pair<>(nvmeController, lsilogicController); + + Map result = vmwareResource.createDiskControllerUnitNumberMap(vmMo, controllerInfo, true); + + Assert.assertEquals(10, (int) result.get(ideController.getControllerReference())); + Assert.assertEquals(11, (int) result.get(nvmeController.getControllerReference())); + Assert.assertEquals(12, (int) result.get(lsilogicController.getControllerReference())); + } + + @Test + public void ensureDiskControllersInternalTestCallsEnsureDiskControllersWhenInstanceIsSystemVm() throws Exception { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + Mockito.doNothing().when(vmwareResource).ensureDiskControllers(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyInt()); + + vmwareResource.ensureDiskControllersInternal(vmMo, true, controllerInfo, true, 1); + + Mockito.verify(vmwareResource, Mockito.times(1)).ensureDiskControllers(vmMo, controllerInfo, true, 1); + } + + @Test + public void ensureDiskControllersInternalTestCallsEnsureDiskControllersWhenTemplateIsNotDeployAsIs() throws Exception { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + Mockito.doNothing().when(vmwareResource).ensureDiskControllers(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyInt()); + + vmwareResource.ensureDiskControllersInternal(vmMo, false, controllerInfo, false, 1); + + Mockito.verify(vmwareResource, Mockito.times(1)).ensureDiskControllers(vmMo, controllerInfo, false, 1); + } + + @Test + public void ensureDiskControllersInternalTestDoesNotCallEnsureDiskControllersWhenInstanceIsNotSystemVmAndTemplateIsDeployAsIs() throws Exception { + vmwareResource.ensureDiskControllersInternal(vmMo, false, new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme), true, 1); + + Mockito.verify(vmwareResource, Mockito.never()).ensureDiskControllers(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyInt()); + } + + @Test + public void ensureDiskControllersTestVmIsConfiguredWhenRequiredDiskControllersDoNotExist() throws Exception { + boolean isSystemVm = false; + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Pair controllerInfo = new Pair<>(osdefaultController, osdefaultController); + + try (MockedStatic vmwareHelperMock = Mockito.mockStatic(VmwareHelper.class)) { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair chosenDiskControllers = new Pair<>(nvmeController, nvmeController); + vmwareHelperMock.when(() -> VmwareHelper.chooseDiskControllers(controllerInfo, vmMo, null, null)).thenReturn(chosenDiskControllers); + Set requiredDiskControllers = new HashSet<>(); + requiredDiskControllers.add(nvmeController); + vmwareHelperMock.when(() -> VmwareHelper.getRequiredDiskControllers(chosenDiskControllers, isSystemVm)).thenReturn(requiredDiskControllers); + Mockito.doReturn(false).when(vmwareResource).vmHasRequiredControllers(vmMo, requiredDiskControllers); + Mockito.doNothing().when(vmwareResource).validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + Mockito.doNothing().when(vmwareResource).validateNewDiskControllersSupportExistingDisks(vmMo, chosenDiskControllers, 1, isSystemVm); + Mockito.doNothing().when(vmwareResource).teardownAllDiskControllers(vmMo); + Mockito.doReturn(true).when(vmMo).configureVm(Mockito.any(VirtualMachineConfigSpec.class)); + + vmwareResource.ensureDiskControllers(vmMo, controllerInfo, isSystemVm, 1); + + Mockito.verify(vmwareResource, Mockito.times(1)).vmHasRequiredControllers(vmMo, requiredDiskControllers); + Mockito.verify(vmwareResource, Mockito.times(1)).validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + Mockito.verify(vmwareResource, Mockito.times(1)).validateNewDiskControllersSupportExistingDisks(vmMo, chosenDiskControllers, 1, isSystemVm); + Mockito.verify(vmwareResource, Mockito.times(1)).teardownAllDiskControllers(vmMo); + vmwareHelperMock.verify(() -> VmwareHelper.addDiskControllersToVmConfigSpec(Mockito.any(), Mockito.any(), Mockito.anyBoolean()), Mockito.times(1)); + Mockito.verify(vmMo, Mockito.times(1)).configureVm(Mockito.any(VirtualMachineConfigSpec.class)); + } + } + + @Test + public void ensureDiskControllersTestVmIsNotConfiguredWhenRequiredDiskControllersExist() throws Exception { + boolean isSystemVm = false; + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Pair controllerInfo = new Pair<>(osdefaultController, osdefaultController); + + try (MockedStatic vmwareHelperMock = Mockito.mockStatic(VmwareHelper.class)) { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair chosenDiskControllers = new Pair<>(nvmeController, nvmeController); + vmwareHelperMock.when(() -> VmwareHelper.chooseDiskControllers(controllerInfo, vmMo, null, null)).thenReturn(chosenDiskControllers); + Set requiredDiskControllers = new HashSet<>(); + requiredDiskControllers.add(nvmeController); + vmwareHelperMock.when(() -> VmwareHelper.getRequiredDiskControllers(chosenDiskControllers, isSystemVm)).thenReturn(requiredDiskControllers); + Mockito.doReturn(true).when(vmwareResource).vmHasRequiredControllers(vmMo, requiredDiskControllers); + Mockito.doReturn(true).when(vmwareResource).vmHasEnoughDiskControllerCapacity(vmMo, chosenDiskControllers, 1, isSystemVm); + + vmwareResource.ensureDiskControllers(vmMo, controllerInfo, isSystemVm, 1); + + Mockito.verify(vmwareResource, Mockito.times(1)).vmHasRequiredControllers(vmMo, requiredDiskControllers); + Mockito.verify(vmwareResource, Mockito.times(1)).vmHasEnoughDiskControllerCapacity(vmMo, chosenDiskControllers, 1, isSystemVm); + Mockito.verify(vmwareResource, Mockito.never()).validateRequiredVirtualHardwareVersionForNewDiskControllers(vmMo, requiredDiskControllers); + Mockito.verify(vmwareResource, Mockito.never()).validateNewDiskControllersSupportExistingDisks(vmMo, chosenDiskControllers, 1, isSystemVm); + Mockito.verify(vmwareResource, Mockito.never()).teardownAllDiskControllers(vmMo); + vmwareHelperMock.verify(() -> VmwareHelper.addDiskControllersToVmConfigSpec(Mockito.any(), Mockito.any(), Mockito.anyBoolean()), Mockito.never()); + Mockito.verify(vmMo, Mockito.never()).configureVm(Mockito.any(VirtualMachineConfigSpec.class)); + } + } + + @Test + public void vmHasRequiredControllersTestReturnsTrueWhenInstanceHasAllTheRequiredControllers() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(nvmeController); + requiredControllers.add(lsilogicController); + VirtualNVMEController firstExistingController = new VirtualNVMEController(); + VirtualLsiLogicController secondExistingController = new VirtualLsiLogicController(); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(firstExistingController, secondExistingController)); + + boolean result = vmwareResource.vmHasRequiredControllers(vmMo, requiredControllers); + + Assert.assertTrue(result); + } + + @Test + public void vmHasRequiredControllersTestReturnsFalseWhenInstanceDoesNotHaveAllTheRequiredControllers() throws Exception { + DiskControllerType sataController = VmwareHelper.getDiskControllerType("sata", null); + DiskControllerType pvscsiController = VmwareHelper.getDiskControllerType("pvscsi", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(sataController); + requiredControllers.add(pvscsiController); + VirtualAHCIController firstExistingController = new VirtualAHCIController(); + VirtualLsiLogicController secondExistingController = new VirtualLsiLogicController(); + Mockito.when(vmMo.getControllers()).thenReturn(List.of(firstExistingController, secondExistingController)); + + boolean result = vmwareResource.vmHasRequiredControllers(vmMo, requiredControllers); + + Assert.assertFalse(result); + } + + @Test + public void getControllerInfoFromVmSpecTestExpectedValues() { + String rootDiskControllerDetail = "nvme"; + String dataDiskControllerDetail = "sata"; + Map details = new HashMap<>(); + details.put(VmDetailConstants.ROOT_DISK_CONTROLLER, rootDiskControllerDetail); + details.put(VmDetailConstants.DATA_DISK_CONTROLLER, dataDiskControllerDetail); + Mockito.when(vmSpec.getDetails()).thenReturn(details); + Mockito.when(vmSpec.getType()).thenReturn(VirtualMachine.Type.SecondaryStorageVm); + + DiskControllerType expectedController = DiskControllerType.nvme; + Pair expectedResult = new Pair<>(expectedController, expectedController); + + try (MockedStatic vmwareHelperMock = Mockito.mockStatic(VmwareHelper.class)) { + vmwareHelperMock.when(() -> VmwareHelper.getDiskControllersFromVmSettings(rootDiskControllerDetail, dataDiskControllerDetail, true)).thenReturn(expectedResult); + + Pair result = vmwareResource.getControllerInfoFromVmSpec(vmSpec); + + Assert.assertEquals(expectedResult, result); + } + } + + @Test + public void getControllerForDiskTestChoosesControllerFromControllerInfoWhenTemplateIsNotDeployAsIs() throws Exception { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + + try (MockedStatic vmwareHelperMock = Mockito.mockStatic(VmwareHelper.class)) { + DiskControllerType expectedResult = DiskControllerType.nvme; + vmwareHelperMock.when(() -> VmwareHelper.getControllerBasedOnDiskType(controllerInfo, diskTo)).thenReturn(expectedResult); + + DiskControllerType result = vmwareResource.getControllerForDisk(vmMo, null, diskTo, controllerInfo, false); + + Assert.assertEquals(expectedResult, result); + } + } + + @Test + public void getControllerForDiskTestChoosesControllerFromCurrentBusNameWhenTemplateIsDeployAsIsAndCurrentBusNameIsValid() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.when(virtualMachineDiskInfo.getDiskDeviceBusName()).thenReturn("nvme1:3"); + Mockito.when(vmMo.getDiskControllerTypeByBusName("nvme1:3")).thenReturn(nvmeController); + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + + DiskControllerType result = vmwareResource.getControllerForDisk(vmMo, virtualMachineDiskInfo, diskTo, controllerInfo, true); + + Assert.assertEquals(nvmeController, result); + } + + @Test + public void getControllerForDiskTestChoosesAnyControllerWhenTemplateIsDeployAsIsAndBusNameIsInvalid() throws Exception { + Mockito.when(virtualMachineDiskInfo.getDiskDeviceBusName()).thenReturn("unmapped1:3"); + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + DiskControllerType expectedResult = DiskControllerType.nvme; + Mockito.when(vmMo.getAnyExistingAvailableDiskController()).thenReturn(expectedResult); + + DiskControllerType result = vmwareResource.getControllerForDisk(vmMo, virtualMachineDiskInfo, diskTo, controllerInfo, true); + + Assert.assertEquals(expectedResult, result); + } + + @Test + public void getControllerForDiskTestChoosesAnyControllerWhenTemplateIsDeployAsIsAndDiskDoesNotExist() throws Exception { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + DiskControllerType expectedResult = DiskControllerType.nvme; + Mockito.when(vmMo.getAnyExistingAvailableDiskController()).thenReturn(expectedResult); + + DiskControllerType result = vmwareResource.getControllerForDisk(vmMo, null, diskTo, controllerInfo, true); + + Assert.assertEquals(expectedResult, result); + } + @Test public void testAddVirtualTPMDevice() throws Exception { VirtualMachineMO vmMo = Mockito.mock(VirtualMachineMO.class); diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 12ee7fbf35af..e060431248d4 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -5733,8 +5733,9 @@ private void fillVMOrTemplateDetailOptions(final Map> optio if (HypervisorType.VMware.equals(hypervisorType)) { options.put(VmDetailConstants.NIC_ADAPTER, Arrays.asList("E1000", "PCNet32", "Vmxnet2", "Vmxnet3")); - options.put(VmDetailConstants.ROOT_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "lsilogic", "lsisas1068", "buslogic", "pvscsi")); - options.put(VmDetailConstants.DATA_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "lsilogic", "lsisas1068", "buslogic", "pvscsi")); + List availableDiskControllers = Arrays.asList("ahci", "buslogic", "ide", "lsilogic", "lsisas1068", "nvme", "osdefault", "pvscsi", "sata", "scsi"); + options.put(VmDetailConstants.ROOT_DISK_CONTROLLER, availableDiskControllers); + options.put(VmDetailConstants.DATA_DISK_CONTROLLER, availableDiskControllers); options.put(VmDetailConstants.NESTED_VIRTUALIZATION_FLAG, Arrays.asList("true", "false")); options.put(VmDetailConstants.SVGA_VRAM_SIZE, Collections.emptyList()); options.put(VmDetailConstants.RAM_RESERVATION, Collections.emptyList()); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index d93dae9ebdec..69ecc570b159 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5296,11 +5296,11 @@ private void updateVMDiskController(UserVmVO vm, Map customParam if (vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER) == null) { String finalRootDiskController = vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER); - // Set the data disk controller detail same as the final scsi root disk controller if VM doesn't have data disk controller detail - // This is to ensure the disk controller is available for the data disks, as all the SCSI controllers are created with same controller type - String scsiControllerPattern = "(?i)\\b(scsi|lsilogic|lsilogicsas|lsisas1068|buslogic|pvscsi)\\b"; - if (finalRootDiskController.matches(scsiControllerPattern)) { - logger.info(String.format("Data disk controller was not defined, but root disk is using SCSI controller [%s]." + + // Set the data disk controller detail same as the root disk controller when the root controller can also + // host data disks on the same bus. This ensures the controllers required by the data disks already exist. + String diskControllerPattern = "(?i)\\b(scsi|lsilogic|lsilogicsas|lsisas1068|buslogic|pvscsi|ahci|sata|nvme)\\b"; + if (finalRootDiskController.matches(diskControllerPattern)) { + logger.info(String.format("Data disk controller was not defined, but root disk is using controller [%s]." + "To ensure disk controllers are available for the data disks, the data disk controller is updated to match the root disk controller.", finalRootDiskController)); vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, finalRootDiskController); } else { diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java index 1266c04f3b4b..89afcfda11ec 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java @@ -52,6 +52,9 @@ public class BaseMO { private String _name; + protected BaseMO() { + } + public BaseMO(VmwareContext context, ManagedObjectReference mor) { assert (context != null); diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java index 9198f310b656..be405d6b10cc 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ClusterMO.java @@ -403,7 +403,7 @@ public void importVmFromOVF(String ovfFilePath, String vmName, DatastoreMO dsMo, @Override public boolean createBlankVm(String vmName, String vmInternalCSName, int cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int memoryMB, - int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, Pair controllerInfo, Boolean systemVm) throws Exception { + int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, Pair controllerInfo, Boolean systemVm) throws Exception { if (logger.isTraceEnabled()) logger.trace("vCenter API trace - createBlankVm(). target MOR: " + _mor.getValue() + ", vmName: " + vmName + ", cpuCount: " + cpuCount + ", cpuSpeedMhz: " + @@ -756,4 +756,12 @@ public List getGuestOsDescriptors() throws Exception { VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(getEnvironmentBrowser(), null, null); return vmConfigOption.getGuestOSDescriptor(); } + + public int getDefaultHardwareVersion() throws Exception { + VirtualMachineConfigOption vmConfigOption = _context.getService().queryConfigOption(getEnvironmentBrowser(), null, null); + if (vmConfigOption.getHardwareOptions() == null) { + return -1; + } + return vmConfigOption.getHardwareOptions().getHwVersion(); + } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DiskControllerType.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DiskControllerType.java index 2ee1e48aaebb..a7985aaadd40 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DiskControllerType.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DiskControllerType.java @@ -16,37 +16,80 @@ // under the License. package com.cloud.hypervisor.vmware.mo; +import com.vmware.vim25.ParaVirtualSCSIController; +import com.vmware.vim25.VirtualAHCIController; +import com.vmware.vim25.VirtualBusLogicController; +import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualLsiLogicController; +import com.vmware.vim25.VirtualLsiLogicSASController; +import com.vmware.vim25.VirtualNVMEController; + +import java.util.List; + public enum DiskControllerType { - ide, - scsi, - osdefault, - lsilogic, - lsisas1068, - buslogic, - pvscsi, - none; - public static DiskControllerType getType(String diskController) { - if (diskController == null || diskController.equalsIgnoreCase("osdefault")) { - return DiskControllerType.osdefault; - } else if (diskController.equalsIgnoreCase("vim.vm.device.VirtualLsiLogicSASController") || diskController.equalsIgnoreCase("VirtualLsiLogicSASController") - || diskController.equalsIgnoreCase(ScsiDiskControllerType.LSILOGIC_SAS_1068) - || diskController.equalsIgnoreCase(ScsiDiskControllerType.LSILOGIC_SAS)) { - return DiskControllerType.lsisas1068; - } else if (diskController.equalsIgnoreCase("vim.vm.device.VirtualLsiLogicController") || diskController.equalsIgnoreCase("VirtualLsiLogicController") - || diskController.equalsIgnoreCase(ScsiDiskControllerType.LSILOGIC_PARALLEL) || diskController.equalsIgnoreCase("scsi")) { - return DiskControllerType.lsilogic; - } else if (diskController.equalsIgnoreCase("vim.vm.device.VirtualIDEController") || diskController.equalsIgnoreCase("VirtualIDEController") - || diskController.equalsIgnoreCase("ide")) { - return DiskControllerType.ide; - } else if (diskController.equalsIgnoreCase("vim.vm.device.ParaVirtualSCSIController") || diskController.equalsIgnoreCase("ParaVirtualSCSIController") - || diskController.equalsIgnoreCase(ScsiDiskControllerType.VMWARE_PARAVIRTUAL) - || diskController.equalsIgnoreCase(ScsiDiskControllerType.VIRTUAL_SCSI)) { - return DiskControllerType.pvscsi; - } else if (diskController.equalsIgnoreCase("vim.vm.device.VirtualBusLogicController") || diskController.equalsIgnoreCase("VirtualBusLogicController") - || diskController.equalsIgnoreCase(ScsiDiskControllerType.BUSLOGIC)) { - return DiskControllerType.buslogic; - } else { - return DiskControllerType.none; - } + osdefault(null, null, null, null, null, null), + ide(VirtualIDEController.class.getName(), "ide", 2, 2, VmdkAdapterType.ide, null), + // The canonical controller type for VirtualLsiLogicController is "lsilogic"; "scsi" is kept as an alias. + lsilogic(VirtualLsiLogicController.class.getName(), "scsi", 16, 4, VmdkAdapterType.lsilogic, null), + scsi(VirtualLsiLogicController.class.getName(), "scsi", 16, 4, VmdkAdapterType.lsilogic, null), + buslogic(VirtualBusLogicController.class.getName(), "scsi", 16, 4, VmdkAdapterType.buslogic, null), + lsisas1068(VirtualLsiLogicSASController.class.getName(), "scsi", 16, 4, VmdkAdapterType.lsilogic, null), + pvscsi(ParaVirtualSCSIController.class.getName(), "scsi", 16, 4, VmdkAdapterType.lsilogic, "7"), + // The canonical controller type for VirtualAHCIController is "ahci"; "sata" is kept as an alias. + ahci(VirtualAHCIController.class.getName(), "sata", 30, 4, VmdkAdapterType.ide, "10"), + sata(VirtualAHCIController.class.getName(), "sata", 30, 4, VmdkAdapterType.ide, "10"), + nvme(VirtualNVMEController.class.getName(), "nvme", 15, 4, VmdkAdapterType.ide, "13"), + none(null, null, null, null, null, null); + + private static final List SUPPORTED_DISK_CONTROLLERS = List.of( + osdefault, ide, lsilogic, scsi, buslogic, lsisas1068, pvscsi, ahci, sata, nvme); + + private final String controllerReference; + private final String busName; + private final Integer maxDeviceCount; + private final Integer maxControllerCount; + private final VmdkAdapterType vmdkAdapterType; + private final String minHardwareVersion; + + DiskControllerType(String controllerReference, String busName, Integer maxDeviceCount, Integer maxControllerCount, + VmdkAdapterType vmdkAdapterType, String minHardwareVersion) { + this.controllerReference = controllerReference; + this.busName = busName; + this.maxDeviceCount = maxDeviceCount; + this.maxControllerCount = maxControllerCount; + this.vmdkAdapterType = vmdkAdapterType; + this.minHardwareVersion = minHardwareVersion; + } + + public static List getSupportedDiskControllerTypes() { + return SUPPORTED_DISK_CONTROLLERS; + } + + public String getName() { + return name(); + } + + public String getControllerReference() { + return controllerReference; + } + + public String getBusName() { + return busName; + } + + public Integer getMaxDeviceCount() { + return maxDeviceCount; + } + + public Integer getMaxControllerCount() { + return maxControllerCount; + } + + public VmdkAdapterType getVmdkAdapterType() { + return vmdkAdapterType; + } + + public String getMinHardwareVersion() { + return minHardwareVersion; } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java index d3c3a9d465e8..2cbbe67c095e 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HostMO.java @@ -672,7 +672,7 @@ public List> getLocalDatastoreOnHost() thro ObjectContent[] ocs = getDatastorePropertiesOnHyperHost(new String[] {"name", "summary"}); if (ocs != null) { for (ObjectContent oc : ocs) { - DatastoreSummary dsSummary = (DatastoreSummary)VmwareHelper.getPropValue(oc, "summary"); + DatastoreSummary dsSummary = (DatastoreSummary) VmwareHelper.getPropValue(oc, "summary"); if (dsSummary.isMultipleHostAccess() == false && dsSummary.isAccessible() && dsSummary.getType().equalsIgnoreCase("vmfs")) { ManagedObjectReference morDs = oc.getObj(); String name = (String)VmwareHelper.getPropValue(oc, "name"); @@ -697,7 +697,7 @@ public void importVmFromOVF(String ovfFilePath, String vmName, DatastoreMO dsMo, @Override public boolean createBlankVm(String vmName, String vmInternalCSName, int cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int memoryMB, - int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, Pair controllerInfo, Boolean systemVm) throws Exception { + int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, Pair controllerInfo, Boolean systemVm) throws Exception { if (logger.isTraceEnabled()) logger.trace("vCenter API trace - createBlankVm(). target MOR: " + _mor.getValue() + ", vmName: " + vmName + ", cpuCount: " + cpuCount + ", cpuSpeedMhz: " + diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java index a079081b847e..0be1bdeb2466 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Objects; import java.util.UUID; @@ -119,7 +120,6 @@ import com.vmware.vim25.OvfCreateImportSpecResult; import com.vmware.vim25.OvfFile; import com.vmware.vim25.OvfFileItem; -import com.vmware.vim25.ParaVirtualSCSIController; import com.vmware.vim25.RuntimeFaultFaultMsg; import com.vmware.vim25.TaskInProgressFaultMsg; import com.vmware.vim25.VMwareDVSConfigSpec; @@ -127,20 +127,19 @@ import com.vmware.vim25.VMwareDVSPortgroupPolicy; import com.vmware.vim25.VMwareDVSPvlanConfigSpec; import com.vmware.vim25.VMwareDVSPvlanMapEntry; -import com.vmware.vim25.VirtualBusLogicController; -import com.vmware.vim25.VirtualController; +import com.vmware.vim25.VirtualAHCIController; import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDisk; import com.vmware.vim25.VirtualIDEController; import com.vmware.vim25.VirtualLsiLogicController; -import com.vmware.vim25.VirtualLsiLogicSASController; import com.vmware.vim25.VirtualMachineConfigSpec; import com.vmware.vim25.VirtualMachineFileInfo; import com.vmware.vim25.VirtualMachineGuestOsIdentifier; import com.vmware.vim25.VirtualMachineImportSpec; import com.vmware.vim25.VirtualMachineVideoCard; +import com.vmware.vim25.VirtualNVMEController; import com.vmware.vim25.VirtualSCSIController; import com.vmware.vim25.VirtualSCSISharing; import com.vmware.vim25.VmConfigFaultFaultMsg; @@ -1569,12 +1568,11 @@ public static ManagedObjectReference waitForNetworkReady(HostMO hostMo, String n public static boolean createBlankVm(VmwareHypervisorHost host, String vmName, String vmInternalCSName, int cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int memoryMB, int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, - Pair controllerInfo, Boolean systemVm) throws Exception { + Pair controllerInfo, Boolean systemVm) throws Exception { if (LOGGER.isInfoEnabled()) LOGGER.info("Create blank VM. cpuCount: " + cpuCount + ", cpuSpeed(MHz): " + cpuSpeedMHz + ", mem(Mb): " + memoryMB); - VirtualDeviceConfigSpec controllerSpec = null; // VM config basics VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); vmConfig.setName(vmName); @@ -1583,23 +1581,15 @@ public static boolean createBlankVm(VmwareHypervisorHost host, String vmName, St VmwareHelper.setBasicVmConfig(vmConfig, cpuCount, cpuSpeedMHz, cpuReservedMHz, memoryMB, memoryReserveMB, guestOsIdentifier, limitCpuUse, false); - Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(controllerInfo, null, host, guestOsIdentifier); - String scsiDiskController = HypervisorHostHelper.getScsiController(chosenDiskControllers); - // If there is requirement for a SCSI controller, ensure to create those. - if (scsiDiskController != null) { - int busNum = 0; - int maxControllerCount = VmwareHelper.MAX_SCSI_CONTROLLER_COUNT; - if (systemVm) { - maxControllerCount = 1; - } - while (busNum < maxControllerCount) { - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec = getControllerSpec(DiskControllerType.getType(scsiDiskController).toString(), busNum); + Pair chosenDiskControllers = VmwareHelper.chooseDiskControllers(controllerInfo, null, host, guestOsIdentifier); + Set requiredDiskControllers = VmwareHelper.getRequiredDiskControllers(chosenDiskControllers, systemVm); - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - } + ClusterMO clusterMo = new ClusterMO(host.getContext(), host.getHyperHostCluster()); + DatacenterMO dataCenterMo = new DatacenterMO(host.getContext(), host.getHyperHostDatacenter()); + setVMHardwareVersion(vmConfig, clusterMo, dataCenterMo); + validateDiskControllersHardwareVersion(vmConfig, clusterMo, requiredDiskControllers); + + VmwareHelper.addDiskControllersToVmConfigSpec(vmConfig, requiredDiskControllers, systemVm); if (guestOsIdentifier.startsWith("darwin")) { //Mac OS LOGGER.debug("Add USB Controller device for blank Mac OS VM " + vmName); @@ -1628,10 +1618,6 @@ public static boolean createBlankVm(VmwareHypervisorHost host, String vmName, St vmConfig.getDeviceChange().add(videoDeviceSpec); - ClusterMO clusterMo = new ClusterMO(host.getContext(), host.getHyperHostCluster()); - DatacenterMO dataCenterMo = new DatacenterMO(host.getContext(), host.getHyperHostDatacenter()); - setVMHardwareVersion(vmConfig, clusterMo, dataCenterMo); - LOGGER.debug(LogUtils.logGsonWithoutException("Creating blank VM with configuration [%s].", vmConfig)); if (host.createVm(vmConfig)) { // Here, when attempting to find the VM, we need to use the name @@ -1674,6 +1660,19 @@ public static void setVMHardwareVersion(VirtualMachineConfigSpec vmConfig, Clust } } + private static void validateDiskControllersHardwareVersion(VirtualMachineConfigSpec vmConfig, ClusterMO clusterMo, + Set requiredDiskControllers) throws Exception { + int hardwareVersion; + if (StringUtils.isNotBlank(vmConfig.getVersion())) { + hardwareVersion = Integer.parseInt(vmConfig.getVersion().replaceAll("[^0-9]", "")); + } else { + hardwareVersion = clusterMo.getDefaultHardwareVersion(); + } + if (hardwareVersion >= 0) { + VmwareHelper.validateDiskControllersHardwareVersion(requiredDiskControllers, hardwareVersion); + } + } + /** * Return the VM hardware version based on the information retrieved by the cluster and datacenter: * - If the cluster hardware version is set, then return this hardware version @@ -1698,34 +1697,6 @@ public static String getNewVMHardwareVersion(ClusterMO clusterMO, DatacenterMO d return version; } - private static VirtualDeviceConfigSpec getControllerSpec(String diskController, int busNum) { - VirtualDeviceConfigSpec controllerSpec = new VirtualDeviceConfigSpec(); - VirtualController controller = null; - - if (diskController.equalsIgnoreCase(DiskControllerType.ide.toString())) { - controller = new VirtualIDEController(); - } else if (DiskControllerType.pvscsi == DiskControllerType.getType(diskController)) { - controller = new ParaVirtualSCSIController(); - } else if (DiskControllerType.lsisas1068 == DiskControllerType.getType(diskController)) { - controller = new VirtualLsiLogicSASController(); - } else if (DiskControllerType.buslogic == DiskControllerType.getType(diskController)) { - controller = new VirtualBusLogicController(); - } else if (DiskControllerType.lsilogic == DiskControllerType.getType(diskController)) { - controller = new VirtualLsiLogicController(); - } - - if (!diskController.equalsIgnoreCase(DiskControllerType.ide.toString())) { - ((VirtualSCSIController)controller).setSharedBus(VirtualSCSISharing.NO_SHARING); - } - - controller.setBusNumber(busNum); - controller.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - - controllerSpec.setDevice(controller); - controllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - return controllerSpec; - } public static VirtualMachineMO createWorkerVM(VmwareHypervisorHost hyperHost, DatastoreMO dsMo, String vmName, String vmxFormattedHardwareVersion) throws Exception { // Allow worker VM to float within cluster so that we will have better chance to @@ -2173,54 +2144,50 @@ public static int getOsDiskFromOvfConf(VirtualMachineConfigSpec config, String d List deviceConfigList = config.getDeviceChange(); int controllerKey = 0; int deviceSeqNumber = 0; - int controllerNumber = 0; - int deviceNodeNumber = 0; int controllerCount = 0; String[] virtualNodeInfo = deviceLocation.split(":"); + Class controllerClass; + String controllerPrefix; if (deviceLocation.startsWith("scsi")) { - controllerNumber = Integer.parseInt(virtualNodeInfo[0].substring(4)); // get substring excluding prefix scsi - deviceNodeNumber = Integer.parseInt(virtualNodeInfo[1]); - - for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { - VirtualDevice device = deviceConfig.getDevice(); - if (device instanceof VirtualSCSIController) { - if (controllerNumber == controllerCount) { //((VirtualSCSIController)device).getBusNumber()) { - controllerKey = device.getKey(); - break; - } - controllerCount++; - } - } - } else { - controllerNumber = Integer.parseInt(virtualNodeInfo[0].substring(3)); // get substring excluding prefix ide - deviceNodeNumber = Integer.parseInt(virtualNodeInfo[1]); - controllerCount = 0; - - for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { - VirtualDevice device = deviceConfig.getDevice(); - if (device instanceof VirtualIDEController) { - if (controllerNumber == controllerCount) { //((VirtualIDEController)device).getBusNumber()) { - // Only 2 IDE controllers supported and they will have bus numbers 0 and 1 - controllerKey = device.getKey(); - break; - } - controllerCount++; - } - } - } - // Get devices on this controller at specific device node. - for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { - VirtualDevice device = deviceConfig.getDevice(); - if (device instanceof VirtualDisk) { - if (controllerKey == device.getControllerKey() && deviceNodeNumber == device.getUnitNumber()) { - break; - } - deviceSeqNumber++; - } - } - return deviceSeqNumber; - } + controllerClass = VirtualSCSIController.class; + controllerPrefix = "scsi"; + } else if (deviceLocation.startsWith("sata")) { + controllerClass = VirtualAHCIController.class; + controllerPrefix = "sata"; + } else if (deviceLocation.startsWith("nvme")) { + controllerClass = VirtualNVMEController.class; + controllerPrefix = "nvme"; + } else { + controllerClass = VirtualIDEController.class; + controllerPrefix = "ide"; + } + + int controllerNumber = Integer.parseInt(virtualNodeInfo[0].substring(controllerPrefix.length())); + int deviceNodeNumber = Integer.parseInt(virtualNodeInfo[1]); + + for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { + VirtualDevice device = deviceConfig.getDevice(); + if (controllerClass.isInstance(device)) { + if (controllerNumber == controllerCount) { + controllerKey = device.getKey(); + break; + } + controllerCount++; + } + } + // Get devices on this controller at specific device node. + for (VirtualDeviceConfigSpec deviceConfig : deviceConfigList) { + VirtualDevice device = deviceConfig.getDevice(); + if (device instanceof VirtualDisk) { + if (controllerKey == device.getControllerKey() && deviceNodeNumber == device.getUnitNumber()) { + break; + } + deviceSeqNumber++; + } + } + return deviceSeqNumber; + } public static String getOVFParamValue(VirtualMachineConfigSpec config) { String paramVal = ""; @@ -2258,35 +2225,6 @@ public static ManagedObjectReference getHypervisorHostMorFromGuid(String guid) { return morHyperHost; } - public static String getScsiController(Pair controllerInfo) { - String rootDiskController = controllerInfo.first(); - String dataDiskController = controllerInfo.second(); - - String scsiDiskController; //If any of the controller provided is SCSI then return it's sub-type. - if (isIdeController(rootDiskController) && isIdeController(dataDiskController)) { - //Default controllers would exist - return null; - } else if (isIdeController(rootDiskController) || isIdeController(dataDiskController)) { - // Only one of the controller types is IDE. Pick the other controller type to create controller. - if (isIdeController(rootDiskController)) { - scsiDiskController = dataDiskController; - } else { - scsiDiskController = rootDiskController; - } - } else if (DiskControllerType.getType(rootDiskController) != DiskControllerType.getType(dataDiskController)) { - // Both ROOT and DATA controllers are SCSI controllers but different sub-types, then prefer ROOT controller - scsiDiskController = rootDiskController; - } else { - // Both are SCSI controllers. - scsiDiskController = rootDiskController; - } - return scsiDiskController; - } - - public static boolean isIdeController(String controller) { - return DiskControllerType.getType(controller) == DiskControllerType.ide; - } - public static void createBaseFolder(DatastoreMO dsMo, VmwareHypervisorHost hyperHost, StoragePoolType poolType) throws Exception { if (poolType != null && poolType == StoragePoolType.DatastoreCluster) { StoragepodMO storagepodMO = new StoragepodMO(hyperHost.getContext(), dsMo.getMor()); diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ScsiDiskControllerType.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ScsiDiskControllerType.java deleted file mode 100644 index 4fbe3995924b..000000000000 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/ScsiDiskControllerType.java +++ /dev/null @@ -1,26 +0,0 @@ -//Licensed to the Apache Software Foundation (ASF) under one -//or more contributor license agreements. See the NOTICE file -//distributed with this work for additional information -//regarding copyright ownership. The ASF licenses this file -//to you under the Apache License, Version 2.0 (the -//"License"); you may not use this file except in compliance -//with the License. You may obtain a copy of the License at -// -//http://www.apache.org/licenses/LICENSE-2.0 -// -//Unless required by applicable law or agreed to in writing, -//software distributed under the License is distributed on an -//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -//KIND, either express or implied. See the License for the -//specific language governing permissions and limitations -//under the License. -package com.cloud.hypervisor.vmware.mo; - -public interface ScsiDiskControllerType { - String LSILOGIC_PARALLEL = "lsilogic"; - String LSILOGIC_SAS = "lsilogicsas"; - String LSILOGIC_SAS_1068 = "lsisas1068"; - String BUSLOGIC = "buslogic"; - String VMWARE_PARAVIRTUAL = "pvscsi"; - String VIRTUAL_SCSI = "VirtualSCSI"; -} diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index 288a108249e1..b06f8162e445 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -31,7 +31,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -78,11 +80,9 @@ import com.vmware.vim25.OvfCreateDescriptorParams; import com.vmware.vim25.OvfCreateDescriptorResult; import com.vmware.vim25.OvfFile; -import com.vmware.vim25.ParaVirtualSCSIController; import com.vmware.vim25.PropertyFilterSpec; import com.vmware.vim25.PropertySpec; import com.vmware.vim25.TraversalSpec; -import com.vmware.vim25.VirtualBusLogicController; import com.vmware.vim25.VirtualCdrom; import com.vmware.vim25.VirtualCdromIsoBackingInfo; import com.vmware.vim25.VirtualCdromRemotePassthroughBackingInfo; @@ -106,7 +106,6 @@ import com.vmware.vim25.VirtualHardwareOption; import com.vmware.vim25.VirtualIDEController; import com.vmware.vim25.VirtualLsiLogicController; -import com.vmware.vim25.VirtualLsiLogicSASController; import com.vmware.vim25.VirtualMachineCloneSpec; import com.vmware.vim25.VirtualMachineConfigInfo; import com.vmware.vim25.VirtualMachineConfigOption; @@ -146,6 +145,10 @@ public void setInternalCSName(String internalVMName) { this.internalCSName = internalVMName; } + protected VirtualMachineMO() { + super(); + } + public VirtualMachineMO(VmwareContext context, ManagedObjectReference morVm) { super(context, morVm); } @@ -1370,13 +1373,11 @@ public void createDisk(String vmdkDatastorePath, VirtualDiskType diskType, Virtu _context.waitForTaskProgressDone(morTask); } - public void updateVmdkAdapter(String vmdkFileName, String diskController) throws Exception { - - DiskControllerType diskControllerType = DiskControllerType.getType(diskController); - VmdkAdapterType vmdkAdapterType = VmdkAdapterType.getAdapterType(diskControllerType); - if (vmdkAdapterType == VmdkAdapterType.none) { + public void updateVmdkAdapter(String vmdkFileName, DiskControllerType diskController) throws Exception { + VmdkAdapterType vmdkAdapterType = diskController.getVmdkAdapterType(); + if (vmdkAdapterType == null || vmdkAdapterType == VmdkAdapterType.none) { String message = "Failed to attach disk due to invalid vmdk adapter type for vmdk file [" + - vmdkFileName + "] with controller : " + diskControllerType; + vmdkFileName + "] with controller : " + diskController.getName(); logger.debug(message); throw new Exception(message); } @@ -1426,49 +1427,52 @@ public void updateAdapterTypeIfRequired(String vmdkFileName) throws Exception { } } + /** + * Returns the controller key, the device number and whether the VMDK adapter must be updated for attaching a disk. + */ + protected Ternary getDiskControllerKeyAndUnitNumber(DiskControllerType diskController) throws Exception { + if (diskController == null) { + logger.debug("Provided disk controller is null; therefore, we will use the first available SCSI controller."); + return new Ternary<>(getScsiDeviceControllerKey(), -1, false); + } + + if (VirtualIDEController.class.getName().equals(diskController.getControllerReference()) + && getPowerState() == VirtualMachinePowerState.POWERED_ON) { + throw new Exception("Adding a virtual disk over IDE controller is not supported while instance is running in VMware hypervisor. Please re-try when instance is not running."); + } + + Pair controllerKeyAndUnitNumber = getNextAvailableControllerKeyAndDeviceNumberForType(diskController); + if (controllerKeyAndUnitNumber == null) { + throw new CloudRuntimeException(String.format("Unable to find an available disk controller of the required type: [%s]. " + + "If the disk controller being used has been changed, please restart your virtual machine so that CloudStack creates the required controllers. " + + "Otherwise, the maximum number of disks for the bus has been reached.", + diskController.getName())); + } + return new Ternary<>(controllerKeyAndUnitNumber.first(), controllerKeyAndUnitNumber.second(), true); + } + public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs) throws Exception { attachDisk(vmdkDatastorePathChain, morDs, null, null); } - public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId) throws Exception { + public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, DiskControllerType diskController, String vSphereStoragePolicyId) throws Exception { attachDisk(vmdkDatastorePathChain, morDs, diskController, vSphereStoragePolicyId, null); } - public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId, Long maxIops) throws Exception { - if(logger.isTraceEnabled()) - logger.trace("vCenter API trace - attachDisk(). target MOR: " + _mor.getValue() + ", vmdkDatastorePath: " - + GSON.toJson(vmdkDatastorePathChain) + ", datastore: " + morDs.getValue()); - int controllerKey = 0; - int unitNumber = 0; - - if (DiskControllerType.getType(diskController) == DiskControllerType.ide) { - // IDE virtual disk cannot be added if VM is running - if (getPowerState() == VirtualMachinePowerState.POWERED_ON) { - throw new Exception("Adding a virtual disk over IDE controller is not supported while instance is running in VMware hypervisor. Please re-try when instance is not running."); - } - // Get next available unit number and controller key - int ideDeviceCount = getNumberOfIDEDevices(); - if (ideDeviceCount >= VmwareHelper.MAX_IDE_CONTROLLER_COUNT * VmwareHelper.MAX_ALLOWED_DEVICES_IDE_CONTROLLER) { - throw new Exception("Maximum limit of devices supported on IDE controllers [" + VmwareHelper.MAX_IDE_CONTROLLER_COUNT - * VmwareHelper.MAX_ALLOWED_DEVICES_IDE_CONTROLLER + "] is reached."); - } - controllerKey = getIDEControllerKey(ideDeviceCount); - unitNumber = getFreeUnitNumberOnIDEController(controllerKey); - } else { - if (StringUtils.isNotBlank(diskController)) { - controllerKey = getScsiDiskControllerKey(diskController); - } else { - controllerKey = getScsiDeviceControllerKey(); - } - unitNumber = -1; - } + public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, DiskControllerType diskController, String vSphereStoragePolicyId, Long maxIops) throws Exception { + logger.trace("vCenter API trace - attachDisk(). target MOR: {}, vmdkDatastorePath: {}, datastore: {}", _mor.getValue(), + GSON.toJson(vmdkDatastorePathChain), morDs.getValue()); synchronized (_mor.getValue().intern()) { - VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, controllerKey, vmdkDatastorePathChain, morDs, unitNumber, 1, maxIops); - if (StringUtils.isNotBlank(diskController)) { - String vmdkFileName = vmdkDatastorePathChain[0]; - updateVmdkAdapter(vmdkFileName, diskController); + Ternary diskControllerKeyAndUnitNumber = getDiskControllerKeyAndUnitNumber(diskController); + + VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, diskControllerKeyAndUnitNumber.first(), + vmdkDatastorePathChain, morDs, diskControllerKeyAndUnitNumber.second(), 1, maxIops); + + if (diskControllerKeyAndUnitNumber.third()) { + updateVmdkAdapter(vmdkDatastorePathChain[0], diskController); } + VirtualMachineConfigSpec reConfigSpec = new VirtualMachineConfigSpec(); VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec(); @@ -2381,97 +2385,6 @@ private boolean moveDatastoreFile(final DatastoreMO dsMo, String srcFilePath, Ma return false; } - public int getPvScsiDeviceControllerKeyNoException() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && devices.size() > 0) { - for (VirtualDevice device : devices) { - if (device instanceof ParaVirtualSCSIController) { - return device.getKey(); - } - } - } - - return -1; - } - - public int getPvScsiDeviceControllerKey() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && devices.size() > 0) { - for (VirtualDevice device : devices) { - if (device instanceof ParaVirtualSCSIController) { - return device.getKey(); - } - } - } - - assert (false); - throw new Exception("VMware Paravirtual SCSI Controller Not Found"); - } - - protected VirtualSCSIController getScsiController(DiskControllerType type) { - switch (type) { - case pvscsi: - return new ParaVirtualSCSIController(); - case lsisas1068: - return new VirtualLsiLogicSASController(); - case buslogic: - return new VirtualBusLogicController(); - default: - return new VirtualLsiLogicController(); - } - } - - public void addScsiDeviceControllers(DiskControllerType type) throws Exception { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - int busNum = 0; - while (busNum < VmwareHelper.MAX_SCSI_CONTROLLER_COUNT) { - VirtualSCSIController scsiController = getScsiController(type); - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - - if (configureVm(vmConfig)) { - logger.info("Successfully added SCSI controllers."); - } else { - throw new Exception("Unable to add Scsi controllers to the instance " + getName()); - } - } - - public void ensurePvScsiDeviceController(int requiredNumScsiControllers, int availableBusNum) throws Exception { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - - int busNum = availableBusNum; - while (busNum < requiredNumScsiControllers) { - ParaVirtualSCSIController scsiController = new ParaVirtualSCSIController(); - - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - - if (configureVm(vmConfig)) { - throw new Exception("Unable to add Scsi controllers to the instance " + getName()); - } else { - logger.info("Successfully added " + requiredNumScsiControllers + " SCSI controllers."); - } - } - public String getRecommendedDiskController(String guestOsId) throws Exception { String recommendedController; GuestOsDescriptor guestOsDescriptor = getGuestOsDescriptor(guestOsId); @@ -2479,100 +2392,6 @@ public String getRecommendedDiskController(String guestOsId) throws Exception { return recommendedController; } - public boolean isPvScsiSupported() throws Exception { - int virtualHardwareVersion; - - virtualHardwareVersion = getVirtualHardwareVersion(); - - // Check if virtual machine is using hardware version 7 or later. - if (virtualHardwareVersion < 7) { - logger.error("The virtual hardware version of the Instance is " + virtualHardwareVersion - + ", which doesn't support PV SCSI controller type for virtual harddisks. Please upgrade this Instance's virtual hardware version to 7 or later."); - return false; - } - return true; - } - - // Would be useful if there exists multiple sub types of SCSI controllers per VM are supported in CloudStack f - public int getScsiDiskControllerKey(String diskController) throws Exception { - List devices = (List)_context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); - - if (CollectionUtils.isNotEmpty(devices)) { - DiskControllerType diskControllerType = DiskControllerType.getType(diskController); - for (VirtualDevice device : devices) { - if ((diskControllerType == DiskControllerType.lsilogic || diskControllerType == DiskControllerType.scsi) - && device instanceof VirtualLsiLogicController && isValidScsiDiskController((VirtualLsiLogicController)device)) { - return ((VirtualLsiLogicController)device).getKey(); - } else if ((diskControllerType == DiskControllerType.lsisas1068 || diskControllerType == DiskControllerType.scsi) - && device instanceof VirtualLsiLogicSASController && isValidScsiDiskController((VirtualLsiLogicSASController)device)) { - return ((VirtualLsiLogicSASController)device).getKey(); - } else if ((diskControllerType == DiskControllerType.pvscsi || diskControllerType == DiskControllerType.scsi) - && device instanceof ParaVirtualSCSIController && isValidScsiDiskController((ParaVirtualSCSIController)device)) { - return ((ParaVirtualSCSIController)device).getKey(); - } else if ((diskControllerType == DiskControllerType.buslogic || diskControllerType == DiskControllerType.scsi) - && device instanceof VirtualBusLogicController && isValidScsiDiskController((VirtualBusLogicController)device)) { - return ((VirtualBusLogicController)device).getKey(); - } - } - } - - assert (false); - throw new IllegalStateException("Scsi disk controller of type " + diskController + " not found among configured devices."); - } - - public int getScsiDiskControllerKeyNoException(String diskController, int scsiUnitNumber) throws Exception { - List devices = (List)_context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); - - if (CollectionUtils.isNotEmpty(devices) && scsiUnitNumber >= 0) { - int requiredScsiController = scsiUnitNumber / VmwareHelper.MAX_ALLOWED_DEVICES_SCSI_CONTROLLER; - int scsiControllerDeviceCount = 0; - DiskControllerType diskControllerType = DiskControllerType.getType(diskController); - for (VirtualDevice device : devices) { - if ((diskControllerType == DiskControllerType.lsilogic || diskControllerType == DiskControllerType.scsi) && device instanceof VirtualLsiLogicController) { - if (scsiControllerDeviceCount == requiredScsiController) { - if (isValidScsiDiskController((VirtualLsiLogicController)device)) { - return ((VirtualLsiLogicController)device).getKey(); - } - break; - } - scsiControllerDeviceCount++; - } else if ((diskControllerType == DiskControllerType.lsisas1068 || diskControllerType == DiskControllerType.scsi) && device instanceof VirtualLsiLogicSASController) { - if (scsiControllerDeviceCount == requiredScsiController) { - if (isValidScsiDiskController((VirtualLsiLogicSASController)device)) { - return ((VirtualLsiLogicSASController)device).getKey(); - } - break; - } - scsiControllerDeviceCount++; - } else if ((diskControllerType == DiskControllerType.pvscsi || diskControllerType == DiskControllerType.scsi) && device instanceof ParaVirtualSCSIController) { - if (scsiControllerDeviceCount == requiredScsiController) { - if (isValidScsiDiskController((ParaVirtualSCSIController)device)) { - return ((ParaVirtualSCSIController)device).getKey(); - } - break; - } - scsiControllerDeviceCount++; - } else if ((diskControllerType == DiskControllerType.buslogic || diskControllerType == DiskControllerType.scsi) && device instanceof VirtualBusLogicController) { - if (scsiControllerDeviceCount == requiredScsiController) { - if (isValidScsiDiskController((VirtualBusLogicController)device)) { - return ((VirtualBusLogicController)device).getKey(); - } - break; - } - scsiControllerDeviceCount++; - } - } - } - return -1; - } - - public int getNextScsiDiskDeviceNumber() throws Exception { - int scsiControllerKey = getScsiDeviceControllerKey(); - int deviceNumber = getNextDeviceNumber(scsiControllerKey); - - return deviceNumber; - } - public int getScsiDeviceControllerKey() throws Exception { List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); @@ -2602,47 +2421,6 @@ public int getScsiDeviceControllerKeyNoException() throws Exception { return -1; } - public void ensureLsiLogicDeviceControllers(int count, int availableBusNum) throws Exception { - int scsiControllerKey = getLsiLogicDeviceControllerKeyNoException(); - if (scsiControllerKey < 0) { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - - int busNum = availableBusNum; - while (busNum < count) { - VirtualLsiLogicController scsiController = new VirtualLsiLogicController(); - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - if (configureVm(vmConfig)) { - throw new Exception("Unable to add Lsi Logic controllers to the instance " + getName()); - } else { - logger.info("Successfully added " + count + " LsiLogic Parallel SCSI controllers."); - } - } - } - - private int getLsiLogicDeviceControllerKeyNoException() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && devices.size() > 0) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualLsiLogicController) { - return device.getKey(); - } - } - } - - return -1; - } - public void ensureScsiDeviceController() throws Exception { int scsiControllerKey = getScsiDeviceControllerKeyNoException(); if (scsiControllerKey < 0) { @@ -2664,32 +2442,6 @@ public void ensureScsiDeviceController() throws Exception { } } - public void ensureScsiDeviceControllers(int count, int availableBusNum) throws Exception { - int scsiControllerKey = getScsiDeviceControllerKeyNoException(); - if (scsiControllerKey < 0) { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - - int busNum = availableBusNum; - while (busNum < count) { - VirtualLsiLogicController scsiController = new VirtualLsiLogicController(); - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - if (configureVm(vmConfig)) { - throw new Exception("Unable to add Scsi controllers to the instance " + getName()); - } else { - logger.info("Successfully added " + count + " SCSI controllers."); - } - } - } - private boolean isValidScsiDiskController(VirtualSCSIController scsiDiskController) { if (scsiDiskController == null) { return false; @@ -3003,19 +2755,24 @@ public List> getDiskDatastorePathChain(Virt return pathList; } + /** + * Returns a virtual disk's numbering. For example, if the disk uses the second SCSI controller's third device node, + * then "scsi1:2" will be returned. + * @param allDevices a list containing all the instance's devices. + * @param theDevice the virtual disk. + * @throws Exception if the disk's controller is not found in allDevices or its type does not have an + * equivalent controller type. + */ public String getDeviceBusName(List allDevices, VirtualDevice theDevice) throws Exception { for (VirtualDevice device : allDevices) { - if (device.getKey() == theDevice.getControllerKey().intValue()) { - if (device instanceof VirtualIDEController) { - return String.format("ide%d:%d", ((VirtualIDEController)device).getBusNumber(), theDevice.getUnitNumber()); - } else if (device instanceof VirtualSCSIController) { - return String.format("scsi%d:%d", ((VirtualSCSIController)device).getBusNumber(), theDevice.getUnitNumber()); - } else { - throw new Exception("Device controller is not supported yet"); - } + if (device.getKey() != theDevice.getControllerKey()) { + continue; } + DiskControllerType controllerType = VmwareHelper.getDiskControllerType(null, device.getClass().getName()); + return String.format("%s%d:%d", controllerType.getBusName(), ((VirtualController) device).getBusNumber(), + theDevice.getUnitNumber()); } - throw new Exception("Unable to find device controller"); + throw new Exception("Unable to find device controller."); } public List getVirtualDisks() throws Exception { @@ -3106,19 +2863,6 @@ public VirtualDisk[] getAllDiskDevice() throws Exception { return deviceList.toArray(new VirtualDisk[0]); } - public VirtualDisk getDiskDeviceByBusName(List allDevices, String busName) throws Exception { - for (VirtualDevice device : allDevices) { - if (device instanceof VirtualDisk) { - VirtualDisk disk = (VirtualDisk)device; - String diskBusName = getDeviceBusName(allDevices, disk); - if (busName.equalsIgnoreCase(diskBusName)) - return disk; - } - } - - return null; - } - public VirtualDisk[] getAllIndependentDiskDevice() throws Exception { List independentDisks = new ArrayList<>(); VirtualDisk[] allDisks = getAllDiskDevice(); @@ -3175,70 +2919,6 @@ public int getIDEDeviceControllerKey() throws Exception { throw new Exception("IDE Controller Not Found"); } - public int getIDEControllerKey(int ideUnitNumber) throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - int requiredIdeController = ideUnitNumber / VmwareHelper.MAX_IDE_CONTROLLER_COUNT; - - int ideControllerCount = 0; - if(devices != null && !devices.isEmpty()) { - for(VirtualDevice device : devices) { - if(device instanceof VirtualIDEController) { - if (ideControllerCount == requiredIdeController) { - return ((VirtualIDEController)device).getKey(); - } - ideControllerCount++; - } - } - } - - assert(false); - throw new Exception("IDE Controller Not Found"); - } - - public int getNumberOfIDEDevices() throws Exception { - int ideDeviceCount = 0; - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && !devices.isEmpty()) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualIDEController) { - ideDeviceCount += ((VirtualIDEController)device).getDevice().size(); - } - } - } - return ideDeviceCount; - } - - public int getFreeUnitNumberOnIDEController(int controllerKey) throws Exception { - int freeUnitNumber = 0; - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - int deviceCount = 0; - int ideDeviceUnitNumber = -1; - if (devices != null) { - for (VirtualDevice device : devices) { - if (device.getControllerKey() == null || device.getControllerKey() != controllerKey) { - continue; - } - if (device instanceof VirtualDisk || device instanceof VirtualCdrom) { - deviceCount++; - ideDeviceUnitNumber = device.getUnitNumber(); - } - } - } - if (deviceCount == 1) { - if (ideDeviceUnitNumber == 0) { - freeUnitNumber = 1; - } // else freeUnitNumber is already initialized to 0 - } else if (deviceCount == 2) { - throw new Exception("IDE controller with key [" + controllerKey + "] already has 2 device attached. Cannot attach more than the limit of 2."); - } - return freeUnitNumber; - } public int getNextIDEDeviceNumber() throws Exception { int controllerKey = getIDEDeviceControllerKey(); return getNextDeviceNumber(controllerKey); @@ -3648,135 +3328,6 @@ public boolean isMemoryHotAddSupported(String guestOsId) throws Exception { } return guestOsSupportsMemoryHotAdd && virtualHardwareSupportsMemoryHotAdd; } - public void ensureLsiLogicSasDeviceControllers(int count, int availableBusNum) throws Exception { - int scsiControllerKey = getLsiLogicSasDeviceControllerKeyNoException(); - if (scsiControllerKey < 0) { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - - int busNum = availableBusNum; - while (busNum < count) { - VirtualLsiLogicSASController scsiController = new VirtualLsiLogicSASController(); - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - if (configureVm(vmConfig)) { - throw new Exception("Unable to add Scsi controller of type LsiLogic SAS."); - } - } - - } - - private int getLsiLogicSasDeviceControllerKeyNoException() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && !devices.isEmpty()) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualLsiLogicSASController) { - return device.getKey(); - } - } - } - - return -1; - } - - public void ensureBusLogicDeviceControllers(int count, int availableBusNum) throws Exception { - int scsiControllerKey = getBusLogicDeviceControllerKeyNoException(); - if (scsiControllerKey < 0) { - VirtualMachineConfigSpec vmConfig = new VirtualMachineConfigSpec(); - - int busNum = availableBusNum; - while (busNum < count) { - VirtualBusLogicController scsiController = new VirtualBusLogicController(); - - scsiController.setSharedBus(VirtualSCSISharing.NO_SHARING); - scsiController.setBusNumber(busNum); - scsiController.setKey(busNum - VmwareHelper.MAX_SCSI_CONTROLLER_COUNT); - VirtualDeviceConfigSpec scsiControllerSpec = new VirtualDeviceConfigSpec(); - scsiControllerSpec.setDevice(scsiController); - scsiControllerSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); - - vmConfig.getDeviceChange().add(scsiControllerSpec); - busNum++; - } - - if (configureVm(vmConfig)) { - throw new Exception("Unable to add Scsi BusLogic controllers to the instance " + getName()); - } else { - logger.info("Successfully added " + count + " SCSI BusLogic controllers."); - } - } - } - - private int getBusLogicDeviceControllerKeyNoException() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - if (devices != null && !devices.isEmpty()) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualBusLogicController) { - return device.getKey(); - } - } - } - - return -1; - } - - public Ternary getScsiControllerInfo() throws Exception { - List devices = (List)_context.getVimClient(). - getDynamicProperty(_mor, "config.hardware.device"); - - int scsiControllerCount = 0; - int busNum = -1; - DiskControllerType controllerType = DiskControllerType.lsilogic; - if (devices != null && !devices.isEmpty()) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualSCSIController) { - scsiControllerCount++; - int deviceBus = ((VirtualSCSIController)device).getBusNumber(); - if (busNum < deviceBus) { - busNum = deviceBus; - } - if (device instanceof VirtualLsiLogicController) { - controllerType = DiskControllerType.lsilogic; - } else if (device instanceof VirtualLsiLogicSASController) { - controllerType = DiskControllerType.lsisas1068; - } else if (device instanceof VirtualBusLogicController) { - controllerType = DiskControllerType.buslogic; - } else if (device instanceof ParaVirtualSCSIController) { - controllerType = DiskControllerType.pvscsi; - } - } - } - } - - return new Ternary<>(scsiControllerCount, busNum, controllerType); - } - - public int getNumberOfVirtualDisks() throws Exception { - List devices = (List)_context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); - - logger.info("Counting disk devices attached to Instance " + getVmName()); - int count = 0; - - if (devices != null && !devices.isEmpty()) { - for (VirtualDevice device : devices) { - if (device instanceof VirtualDisk) { - count++; - } - } - } - return count; - } public String getExternalDiskUUID(String datastoreVolumePath) throws Exception{ List devices = (List)_context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); @@ -3901,4 +3452,243 @@ public void removeChangeTrackPathFromVmdkForDisks() throws Exception { public String toString() { return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "internalCSName"); } + + public List getControllers() throws Exception { + List controllers = new ArrayList<>(); + + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + for (VirtualDevice device : devices) { + if (device instanceof VirtualController) { + controllers.add((VirtualController) device); + } + } + + return controllers; + } + + public Set getExistingDiskControllerTypes() throws Exception { + Set existingControllerTypes = new HashSet<>(); + + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + for (VirtualDevice device : devices) { + if (!(device instanceof VirtualController)) { + continue; + } + String classpath = device.getClass().getName(); + try { + DiskControllerType controllerType = VmwareHelper.getDiskControllerType(null, classpath); + existingControllerTypes.add(controllerType); + } catch (CloudRuntimeException e) { + logger.debug("No disk controller type found for existing controller [{}]; ignoring it.", classpath); + } + } + + return existingControllerTypes; + } + + /** + * Returns the type of the disk controller matching the provided bus name (e.g. "scsi1:0"), or null if + * no such controller exists. + */ + public DiskControllerType getDiskControllerTypeByBusName(String busName) throws Exception { + if (busName == null || !busName.contains(":")) { + return null; + } + + String controllerName = busName.substring(0, busName.indexOf(':')); + for (VirtualController controller : getControllers()) { + String classpath = controller.getClass().getName(); + try { + DiskControllerType controllerType = VmwareHelper.getDiskControllerType(null, classpath); + if (controllerName.equals(controllerType.getBusName() + controller.getBusNumber())) { + return controllerType; + } + } catch (CloudRuntimeException e) { + logger.debug("No disk controller type found for existing controller [{}]; ignoring it.", classpath); + } + } + + return null; + } + + /** + * Searches for a disk controller that has available device nodes, and returns its equivalent controller type.
+ * This function prioritizes choosing disk controllers different than VirtualIDEController. If no other + * disk controller type is available, but a VirtualIDEController is, then the IDE will be chosen. + * @throws CloudRuntimeException if no disk controller is available. + */ + public DiskControllerType getAnyExistingAvailableDiskController() throws Exception { + Set validDiskControllerClasspaths = VmwareHelper.getAllSupportedDiskControllerTypesExceptOsDefault().stream() + .map(DiskControllerType::getControllerReference) + .collect(Collectors.toSet()); + Set unavailableControllerClasspaths = new HashSet<>(); + + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + for (VirtualDevice device : devices) { + String deviceClasspath = device.getClass().getName(); + if (VirtualIDEController.class.getName().equals(deviceClasspath)) { + // Prioritize choosing controllers different from IDE + continue; + } + if (!validDiskControllerClasspaths.contains(deviceClasspath) || unavailableControllerClasspaths.contains(deviceClasspath)) { + continue; + } + DiskControllerType diskController = VmwareHelper.getDiskControllerType(null, deviceClasspath); + Pair nextAvailableControllerKeyAndUnitNumber = getNextAvailableControllerKeyAndDeviceNumberForType(diskController); + if (nextAvailableControllerKeyAndUnitNumber != null) { + return diskController; + } + unavailableControllerClasspaths.add(deviceClasspath); + } + + DiskControllerType ideControllerType = VmwareHelper.getDiskControllerType(null, VirtualIDEController.class.getName()); + Pair nextAvailableControllerKeyAndUnitNumber = getNextAvailableControllerKeyAndDeviceNumberForType(ideControllerType); + if (nextAvailableControllerKeyAndUnitNumber != null) { + return ideControllerType; + } + throw new CloudRuntimeException("Unable to find an available disk controller in the virtual machine."); + } + + /** + * Searches for disk controllers matching the provided controller type, and returns a pair containing, respectively, the first + * found available controller's key and its next available device node number. If no disk controller matching the controller type + * is found, returns null. + */ + public Pair getNextAvailableControllerKeyAndDeviceNumberForType(DiskControllerType controllerType) throws Exception { + if (controllerType.getControllerReference() == null) { + throw new CloudRuntimeException(String.format("Disk controller type [%s] does not have a controller reference and cannot be used to attach disks.", controllerType.getName())); + } + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + for (VirtualDevice device : devices) { + if (!controllerType.getControllerReference().equals(device.getClass().getName())) { + continue; + } + VirtualController controller = (VirtualController) device; + if (controller.getBusNumber() >= controllerType.getMaxControllerCount()) { + continue; + } + int nextAvailableDeviceNumber = getNextAvailableDeviceNumberForController(controller, controllerType); + if (nextAvailableDeviceNumber >= 0) { + return new Pair<>(controller.getKey(), nextAvailableDeviceNumber); + } + } + logger.debug("No available disk controller of class [{}] found.", controllerType.getControllerReference()); + return null; + } + + /** + * Returns the next available device number for the provided controller type, considering all controllers of that + * type. The returned number is global to the controller type, so it can be used to calculate both the controller + * index and the device number. If no device number is available, returns null. + */ + public Integer getNextAvailableDeviceNumberForType(DiskControllerType controllerType) throws Exception { + return getNextAvailableDeviceNumberForType(controllerType, 0); + } + + /** + * Returns the next available device number for the provided controller type, considering all controllers of that + * type and ignoring device numbers lower than startUnitNumber. The returned number is global to the + * controller type, so it can be used to calculate both the controller index and the device number. If no device + * number is available, returns null. + */ + public Integer getNextAvailableDeviceNumberForType(DiskControllerType controllerType, int startUnitNumber) throws Exception { + if (controllerType.getControllerReference() == null) { + throw new CloudRuntimeException(String.format("Disk controller type [%s] does not have a controller reference and cannot be used to attach disks.", controllerType.getName())); + } + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + int controllerIndex = 0; + for (VirtualDevice device : devices) { + if (!controllerType.getControllerReference().equals(device.getClass().getName())) { + continue; + } + VirtualController controller = (VirtualController) device; + if (controller.getBusNumber() < controllerType.getMaxControllerCount()) { + int controllerBaseUnitNumber = controllerIndex * controllerType.getMaxDeviceCount(); + int nextAvailableDeviceNumber = getNextAvailableDeviceNumberForController(controller, controllerType, Math.max(0, startUnitNumber - controllerBaseUnitNumber)); + if (nextAvailableDeviceNumber >= 0) { + return controllerBaseUnitNumber + nextAvailableDeviceNumber; + } + } + controllerIndex++; + } + logger.debug("No available device number for disk controller type [{}].", controllerType.getControllerReference()); + return null; + } + + /** + * Returns the next available device node number for the provided disk controller. If no node is available, returns -1. + * @param controllerType controller type that represents the provided disk controller's type. + */ + protected int getNextAvailableDeviceNumberForController(VirtualController diskController, DiskControllerType controllerType) throws Exception { + return getNextAvailableDeviceNumberForController(diskController, controllerType, 0); + } + + /** + * Returns the next available device node number for the provided disk controller, ignoring device numbers lower than + * startDeviceNumber. If no node is available, returns -1. + * @param controllerType controller type that represents the provided disk controller's type. + */ + protected int getNextAvailableDeviceNumberForController(VirtualController diskController, DiskControllerType controllerType, int startDeviceNumber) throws Exception { + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + + Set usedUnitNumbers = new HashSet<>(); + for (VirtualDevice device : devices) { + if (device.getControllerKey() == null || device.getControllerKey() != diskController.getKey() || device.getUnitNumber() == null) { + continue; + } + usedUnitNumbers.add(device.getUnitNumber()); + } + if (VmwareHelper.isControllerScsi(controllerType)) { + usedUnitNumbers.add(7); + } + + int unitNumber = startDeviceNumber; + while (unitNumber < controllerType.getMaxDeviceCount()) { + if (!usedUnitNumbers.contains(unitNumber)) { + return unitNumber; + } + unitNumber++; + } + logger.debug("Disk controller [{}] does not have an available device number.", diskController.getKey()); + return -1; + } + + /** + * Searches all the instance's devices for devices with a class represented by classpath, and returns the + * nth (specified by number) device found. + * @throws CloudRuntimeException if the device is not found. + */ + public VirtualDevice getNthDevice(String classpath, int number) throws Exception { + int currentNumber = 0; + + List devices = _context.getVimClient().getDynamicProperty(_mor, "config.hardware.device"); + for (VirtualDevice device : devices) { + if (classpath.equals(device.getClass().getName())) { + if (currentNumber == number) { + return device; + } + currentNumber++; + } + } + + logger.debug("Unable to find a device with classpath [{}] and number [{}] in the virtual machine.", classpath, number); + throw new CloudRuntimeException("Unable to find the required device in the virtual machine."); + } + + /** + * Validates (i) if the provided disk controller has a valid bus number and (ii) if it has an available device node number. + * @param controllerType controller type that represents the provided disk controller's type. + * @throws CloudRuntimeException if it does not. + */ + public void validateDiskControllerIsAvailable(VirtualController diskController, DiskControllerType controllerType) throws Exception { + if (diskController.getBusNumber() >= controllerType.getMaxControllerCount()) { + logger.debug("Disk controller [{}] has an invalid bus number [{}].", diskController.getKey(), diskController.getBusNumber()); + throw new CloudRuntimeException("Virtual machine does not have an available disk controller device."); + } + + int nextUnconfiguredDeviceNumber = getNextAvailableDeviceNumberForController(diskController, controllerType); + if (nextUnconfiguredDeviceNumber == -1) { + throw new CloudRuntimeException("Virtual machine does not have an available disk controller device."); + } + } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkAdapterType.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkAdapterType.java index ff0a7d30672d..7ac499765567 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkAdapterType.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmdkAdapterType.java @@ -22,18 +22,6 @@ public enum VmdkAdapterType { buslogic, none; - public static VmdkAdapterType getAdapterType(DiskControllerType diskControllerType) { - if (diskControllerType == DiskControllerType.ide) { - return VmdkAdapterType.ide; - } else if (diskControllerType == DiskControllerType.buslogic) { - return VmdkAdapterType.buslogic; - } else if (diskControllerType == DiskControllerType.lsilogic || diskControllerType == DiskControllerType.pvscsi || diskControllerType == DiskControllerType.lsisas1068) { - return VmdkAdapterType.lsilogic; - } else { - return VmdkAdapterType.none; - } - } - public static VmdkAdapterType getType(String vmdkAdapterType) { if (vmdkAdapterType == null) return VmdkAdapterType.none; diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java index 2177b062b003..18ad6a7a7679 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VmwareHypervisorHost.java @@ -64,7 +64,7 @@ public interface VmwareHypervisorHost { boolean createBlankVm(String vmName, String vmInternalCSName, int cpuCount, int cpuSpeedMHz, int cpuReservedMHz, boolean limitCpuUse, int memoryMB, int memoryReserveMB, String guestOsIdentifier, ManagedObjectReference morDs, boolean snapshotDirToParent, - Pair controllerInfo, Boolean systemVm) throws Exception; + Pair controllerInfo, Boolean systemVm) throws Exception; void importVmFromOVF(String ovfFilePath, String vmName, DatastoreMO dsMo, String diskOption, String configurationId) throws Exception; diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java index 2a42d49898fd..bd354827eee0 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java @@ -31,9 +31,13 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Random; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.xml.datatype.DatatypeConfigurationException; @@ -61,19 +65,25 @@ import com.vmware.vim25.HostPortGroupSpec; import com.vmware.vim25.NasDatastoreInfo; import com.vmware.vim25.VMwareDVSPortSetting; +import com.vmware.vim25.VirtualController; +import com.vmware.vim25.VirtualDeviceConfigSpec; +import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDeviceFileBackingInfo; import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualLsiLogicController; import com.vmware.vim25.VirtualMachineBootOptions; import com.vmware.vim25.VirtualMachineConfigInfo; import com.vmware.vim25.VirtualMachineConfigSummary; import com.vmware.vim25.VirtualMachineGuestOsIdentifier; import com.vmware.vim25.VirtualMachineToolsStatus; import com.vmware.vim25.VirtualSCSIController; +import com.vmware.vim25.VirtualSCSISharing; import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec; import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec; import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -130,17 +140,14 @@ public class VmwareHelper { protected static Logger LOGGER = LogManager.getLogger(VmwareHelper.class); public static final int MAX_SCSI_CONTROLLER_COUNT = 4; - public static final int MAX_IDE_CONTROLLER_COUNT = 2; - public static final int MAX_ALLOWED_DEVICES_IDE_CONTROLLER = 2; public static final int MAX_ALLOWED_DEVICES_SCSI_CONTROLLER = 16; public static final int MAX_SUPPORTED_DEVICES_SCSI_CONTROLLER = MAX_ALLOWED_DEVICES_SCSI_CONTROLLER - 1; // One device node is unavailable for hard disks or SCSI devices - public static final int MAX_USABLE_SCSI_CONTROLLERS = 2; public static final String MIN_VERSION_UEFI_LEGACY = "5.5"; public static final String MIN_VERSION_VMFS6 = "6.5"; public static boolean isReservedScsiDeviceNumber(int deviceNumber) { // The SCSI controller is assigned to virtual device node (z:7), so that device node is unavailable for hard disks or SCSI devices. - return (deviceNumber % VmwareHelper.MAX_ALLOWED_DEVICES_SCSI_CONTROLLER) == 7; + return (deviceNumber % MAX_ALLOWED_DEVICES_SCSI_CONTROLLER) == 7; } @Nonnull @@ -414,7 +421,7 @@ private static void setParentBackingInfo(VirtualDiskFlatVer2BackingInfo backingI } public static Pair prepareIsoDevice(VirtualMachineMO vmMo, String isoDatastorePath, ManagedObjectReference morDs, boolean connect, - boolean connectAtBoot, int deviceNumber, int contextNumber) throws Exception { + boolean connectAtBoot, int ideUnitNumber, int contextNumber) throws Exception { boolean newCdRom = false; VirtualCdrom cdRom = (VirtualCdrom)vmMo.getIsoDevice(); @@ -422,12 +429,16 @@ public static Pair prepareIsoDevice(VirtualMachineMO vmM newCdRom = true; cdRom = new VirtualCdrom(); - assert (vmMo.getIDEDeviceControllerKey() >= 0); - cdRom.setControllerKey(vmMo.getIDEDeviceControllerKey()); - if (deviceNumber < 0) - deviceNumber = vmMo.getNextIDEDeviceNumber(); - - cdRom.setUnitNumber(deviceNumber); + int ideControllerNumber = 0; + if (ideUnitNumber < 0) { + ideUnitNumber = vmMo.getNextIDEDeviceNumber(); + } else { + ideControllerNumber = ideUnitNumber / DiskControllerType.ide.getMaxDeviceCount(); + ideUnitNumber %= DiskControllerType.ide.getMaxDeviceCount(); + } + VirtualController ideController = (VirtualController) vmMo.getNthDevice(DiskControllerType.ide.getControllerReference(), ideControllerNumber); + cdRom.setControllerKey(ideController.getKey()); + cdRom.setUnitNumber(ideUnitNumber); cdRom.setKey(-contextNumber); } @@ -752,13 +763,11 @@ public static String getVCenterSafeUuid(DatastoreMO dsMo) throws Exception{ } public static String getRecommendedDiskControllerFromDescriptor(GuestOsDescriptor guestOsDescriptor) throws Exception { - String recommendedController; - - recommendedController = guestOsDescriptor.getRecommendedDiskController(); + String recommendedController = guestOsDescriptor.getRecommendedDiskController(); // Bypass auto detected PVSCSI controller to use LsiLogic Parallel instead - if (DiskControllerType.getType(recommendedController) == DiskControllerType.pvscsi) { - recommendedController = DiskControllerType.lsilogic.toString(); + if (matchesDiskControllerName(DiskControllerType.pvscsi, normalizeDiskControllerName(recommendedController))) { + return VirtualLsiLogicController.class.getSimpleName(); } return recommendedController; @@ -775,8 +784,18 @@ public static String trimSnapshotDeltaPostfix(String name) { return name; } - public static boolean isControllerOsRecommended(String dataDiskController) { - return DiskControllerType.getType(dataDiskController) == DiskControllerType.osdefault; + /** + * Returns true if the provided controller type's name is "osdefault". + */ + public static boolean isControllerOsRecommended(DiskControllerType controllerType) { + return DiskControllerType.osdefault.toString().equals(controllerType.getName()); + } + + /** + * Returns true if the provided controller type's bus name is "scsi". + */ + public static boolean isControllerScsi(DiskControllerType controllerType) { + return DiskControllerType.scsi.toString().equals(controllerType.getBusName()); } public static XMLGregorianCalendar getXMLGregorianCalendar(final Date date, final int offsetSeconds) throws DatatypeConfigurationException { @@ -910,18 +929,18 @@ protected static List getUnmanageInstanceDisks(Virtual instanceDisk.setChainInfo(GsonHelper.getGsonLogger().toJson(diskInfo)); } for (VirtualDevice device : vmMo.getAllDeviceList()) { - if (diskDevice.getControllerKey() == device.getKey()) { - if (device instanceof VirtualIDEController) { - instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString()); - instanceDisk.setControllerUnit(((VirtualIDEController) device).getBusNumber()); - } else if (device instanceof VirtualSCSIController) { - instanceDisk.setController(DiskControllerType.getType(device.getClass().getSimpleName()).toString()); - instanceDisk.setControllerUnit(((VirtualSCSIController) device).getBusNumber()); - } else { - instanceDisk.setController(DiskControllerType.none.toString()); - } - break; + if (diskDevice.getControllerKey() != device.getKey()) { + continue; + } + try { + DiskControllerType diskController = VmwareHelper.getDiskControllerType(null, device.getClass().getName()); + instanceDisk.setController(diskController.getName()); + instanceDisk.setControllerUnit(((VirtualController) device).getBusNumber()); + } catch (CloudRuntimeException e) { + LOGGER.debug("Unable to find a disk controller type for class [{}]; defaulting disk controller to 'none'.", device.getClass().getName()); + instanceDisk.setController(DiskControllerType.none.toString()); } + break; } if (disk.getBacking() instanceof VirtualDeviceFileBackingInfo) { VirtualDeviceFileBackingInfo diskBacking = (VirtualDeviceFileBackingInfo) disk.getBacking(); @@ -1156,45 +1175,199 @@ public static String getAbsoluteVmdkFile(VirtualDisk disk) { } /** - * Validates an instance's rootDiskController and dataDiskController details. Throws a - * CloudRuntimeException if they are invalid. + * Returns a disk controller type that has the provided name or the provided controller reference. + * @throws CloudRuntimeException if there is no configured disk controller matching the provided params. */ - public static void validateDiskControllerDetails(String rootDiskControllerDetail, String dataDiskControllerDetail) { - rootDiskControllerDetail = DiskControllerType.getType(rootDiskControllerDetail).toString(); - if (DiskControllerType.getType(rootDiskControllerDetail) == DiskControllerType.none) { - throw new CloudRuntimeException(String.format("[%s] is not a valid root disk controller", rootDiskControllerDetail)); + public static DiskControllerType getDiskControllerType(String name, String controllerReference) { + String normalizedName = normalizeDiskControllerName(name); + for (DiskControllerType controllerType : getAllSupportedDiskControllerTypes()) { + if (matchesDiskControllerName(controllerType, normalizedName) || matchesDiskControllerReference(controllerType, controllerReference)) { + return controllerType; + } + } + LOGGER.debug("No corresponding disk controller type found for name [{}] and controller reference [{}].", name, controllerReference); + throw new CloudRuntimeException("Specified disk controller is invalid."); + } + + private static String normalizeDiskControllerName(String name) { + if (name == null) { + return null; + } + String lowerCaseName = name.toLowerCase(Locale.ROOT); + if (lowerCaseName.equals("lsilogicsas")) { + return DiskControllerType.lsisas1068.getName(); + } + if (lowerCaseName.equals("virtualscsi")) { + return DiskControllerType.pvscsi.getName(); } - dataDiskControllerDetail = DiskControllerType.getType(dataDiskControllerDetail).toString(); - if (DiskControllerType.getType(dataDiskControllerDetail) == DiskControllerType.none) { - throw new CloudRuntimeException(String.format("[%s] is not a valid data disk controller", dataDiskControllerDetail)); + return name; + } + + private static boolean matchesDiskControllerName(DiskControllerType controllerType, String name) { + if (name == null) { + return false; + } + if (controllerType.getName().equalsIgnoreCase(name)) { + return true; + } + String controllerClassName = getSimpleClassName(controllerType.getControllerReference()); + return controllerClassName != null && controllerClassName.equalsIgnoreCase(getSimpleClassName(name)); + } + + private static boolean matchesDiskControllerReference(DiskControllerType controllerType, String controllerReference) { + if (controllerReference == null || controllerType.getControllerReference() == null) { + return false; + } + String typeReference = controllerType.getControllerReference(); + return typeReference.equalsIgnoreCase(controllerReference) + || getSimpleClassName(typeReference).equalsIgnoreCase(getSimpleClassName(controllerReference)); + } + + private static String getSimpleClassName(String className) { + if (className == null) { + return null; + } + int lastDotIndex = className.lastIndexOf('.'); + return lastDotIndex < 0 ? className : className.substring(lastDotIndex + 1); + } + + public static List getAllSupportedDiskControllerTypes() { + return DiskControllerType.getSupportedDiskControllerTypes(); + } + + public static List getAllSupportedDiskControllerTypesExceptOsDefault() { + return getAllSupportedDiskControllerTypes().stream() + .filter(c -> !isControllerOsRecommended(c)) + .collect(Collectors.toList()); + } + + /** + * Returns a set containing the disk controllers an instance should have based on the provided params. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. + * @param isSystemVm if true, only the root disk controller is required; otherwise, both controllers are required. + */ + public static Set getRequiredDiskControllers(Pair controllerInfo, + boolean isSystemVm) { + Set requiredDiskControllers = new HashSet<>(); + requiredDiskControllers.add(controllerInfo.first()); + if (!isSystemVm) { + requiredDiskControllers.add(controllerInfo.second()); + } + return requiredDiskControllers; + } + + /** + * Validates if the provided disk controllers are supported by the provided virtual hardware version. + * @throws CloudRuntimeException if one of the disk controllers requires a greater virtual hardware version. + */ + public static void validateDiskControllersHardwareVersion(Set requiredDiskControllers, int hardwareVersion) { + List unsupportedDiskControllers = new ArrayList<>(); + for (DiskControllerType diskController : requiredDiskControllers) { + if (diskController.getMinHardwareVersion() == null) { + continue; + } + int requiredVersion = Integer.parseInt(diskController.getMinHardwareVersion()); + if (hardwareVersion < requiredVersion) { + unsupportedDiskControllers.add(diskController); + } + } + + if (!unsupportedDiskControllers.isEmpty()) { + String names = unsupportedDiskControllers.stream().map(DiskControllerType::getName).collect(Collectors.joining(", ")); + LOGGER.debug("Virtual hardware version [{}] does not support disk controllers [{}].", hardwareVersion, names); + throw new CloudRuntimeException(String.format("Disk controllers [%s] are not supported by virtual hardware version [%s].", names, hardwareVersion)); + } + } + + /** + * Instructs the provided VirtualMachineConfigSpec to create the disk controllers contained in requiredDiskControllers. + * @param isSystemVm if true, only one disk controller of each type will be created; otherwise, the maximum amount of each controller will be created. + * @throws Exception if a disk controller type has an invalid controller reference. + */ + public static void addDiskControllersToVmConfigSpec(VirtualMachineConfigSpec vmConfigSpec, Set requiredDiskControllers, + boolean isSystemVm) throws Exception { + int currentKey = -1; + + for (DiskControllerType diskController : requiredDiskControllers) { + if (diskController.getControllerReference() == null) { + throw new CloudRuntimeException(String.format("Disk controller type [%s] cannot be used to create controllers.", diskController.getName())); + } + Class controllerClass = Class.forName(diskController.getControllerReference()); + if (controllerClass == VirtualIDEController.class) { + continue; + } + for (int bus = 0; bus < diskController.getMaxControllerCount(); bus++) { + VirtualController controller = (VirtualController) controllerClass.getDeclaredConstructor().newInstance(); + controller.setBusNumber(bus); + controller.setKey(currentKey); + currentKey--; + + if (controller instanceof VirtualSCSIController) { + ((VirtualSCSIController) controller).setSharedBus(VirtualSCSISharing.NO_SHARING); + } + + VirtualDeviceConfigSpec controllerConfigSpec = new VirtualDeviceConfigSpec(); + controllerConfigSpec.setDevice(controller); + controllerConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD); + vmConfigSpec.getDeviceChange().add(controllerConfigSpec); + + if (isSystemVm) { + break; + } + } } } + /** + * Based on an instance's rootDiskController and dataDiskController details, returns a pair + * containing the disk controller types that should be used for root disk and the data disks, respectively. + * @param isSystemVm if true, the root disk controller detail will be ignored, and the chosen root disk controller will be lsilogic. + */ + public static Pair getDiskControllersFromVmSettings(String rootDiskControllerDetail, + String dataDiskControllerDetail, + boolean isSystemVm) { + String updatedRootDiskControllerDetail = isSystemVm ? DiskControllerType.lsilogic.toString() : ObjectUtils.defaultIfNull(rootDiskControllerDetail, DiskControllerType.osdefault.toString()); + String updatedDataDiskControllerDetail = ObjectUtils.defaultIfNull(dataDiskControllerDetail, isSystemVm ? updatedRootDiskControllerDetail : DiskControllerType.osdefault.toString()); + + DiskControllerType specifiedRootDiskController = getDiskControllerType(updatedRootDiskControllerDetail, null); + DiskControllerType specifiedDataDiskController = getDiskControllerType(updatedDataDiskControllerDetail, null); + + return new Pair<>(specifiedRootDiskController, specifiedDataDiskController); + } + /** * Based on an instance's rootDiskController and dataDiskController details, returns a pair * containing the disk controllers that should be used for root disk and the data disks, respectively. - * - * @param controllerInfo pair containing the root disk and data disk controllers, respectively. - * @param vmMo virtual machine to derive the recommended disk controllers from. If not null, host and guestOsIdentifier will be ignored. - * @param host host to derive the recommended disk controllers from. Must be provided with guestOsIdentifier. + * @param controllerInfo pair containing the root disk and data disk controllers, respectively. + * @param vmMo virtual machine to derive the recommended disk controllers from. If not null, host and guestOsIdentifier will be ignored. + * @param host host to derive the recommended disk controllers from. Must be provided with guestOsIdentifier. * @param guestOsIdentifier used to derive the recommended disk controllers from the host. + * @throws Exception if no disk controller type matching the recommended disk controller exists. */ - public static Pair chooseRequiredDiskControllers(Pair controllerInfo, VirtualMachineMO vmMo, - VmwareHypervisorHost host, String guestOsIdentifier) throws Exception { - String recommendedDiskControllerClassName = vmMo != null ? vmMo.getRecommendedDiskController(null) : host.getRecommendedDiskController(guestOsIdentifier); - String recommendedDiskController = DiskControllerType.getType(recommendedDiskControllerClassName).toString(); - - String convertedRootDiskController = controllerInfo.first(); - if (isControllerOsRecommended(convertedRootDiskController)) { - convertedRootDiskController = recommendedDiskController; - } + public static Pair chooseDiskControllers(Pair controllerInfo, + VirtualMachineMO vmMo, + VmwareHypervisorHost host, + String guestOsIdentifier) throws Exception { + DiskControllerType convertedRootDiskController = controllerInfo.first(); + DiskControllerType convertedDataDiskController = controllerInfo.second(); + if (isControllerOsRecommended(convertedRootDiskController) || isControllerOsRecommended(convertedDataDiskController)) { + String recommendedDiskControllerClassName = vmMo != null ? vmMo.getRecommendedDiskController(null) : host.getRecommendedDiskController(guestOsIdentifier); + String normalizedRecommendedDiskControllerName = normalizeDiskControllerName(recommendedDiskControllerClassName); + DiskControllerType recommendedDiskController = getAllSupportedDiskControllerTypesExceptOsDefault().stream() + .filter(c -> matchesDiskControllerName(c, normalizedRecommendedDiskControllerName)) + .findFirst() + .orElseThrow(() -> new CloudRuntimeException("Recommended disk controller is not supported.")); + + if (isControllerOsRecommended(convertedRootDiskController)) { + convertedRootDiskController = recommendedDiskController; + } - String convertedDataDiskController = controllerInfo.second(); - if (isControllerOsRecommended(convertedDataDiskController)) { - convertedDataDiskController = recommendedDiskController; + if (isControllerOsRecommended(convertedDataDiskController)) { + convertedDataDiskController = recommendedDiskController; + } } - if (diskControllersShareTheSameBusType(convertedRootDiskController, convertedDataDiskController)) { + if (convertedRootDiskController.getBusName().equals(convertedDataDiskController.getBusName())) { LOGGER.debug("Root and data disk controllers share the same bus type; therefore, we will only use the controllers specified for the root disk."); return new Pair<>(convertedRootDiskController, convertedRootDiskController); } @@ -1202,28 +1375,18 @@ public static Pair chooseRequiredDiskControllers(Pair(convertedRootDiskController, convertedDataDiskController); } - protected static boolean diskControllersShareTheSameBusType(String rootDiskController, String dataDiskController) { - DiskControllerType rootDiskControllerType = DiskControllerType.getType(rootDiskController); - DiskControllerType dataDiskControllerType = DiskControllerType.getType(dataDiskController); - if (rootDiskControllerType.equals(dataDiskControllerType)) { - return true; - } - List scsiDiskControllers = List.of(DiskControllerType.scsi, DiskControllerType.lsilogic, DiskControllerType.lsisas1068, - DiskControllerType.buslogic ,DiskControllerType.pvscsi); - return scsiDiskControllers.contains(rootDiskControllerType) && scsiDiskControllers.contains(dataDiskControllerType); - } - /** * Identifies whether the disk is a root or data disk, and returns the controller from the provided pair that should * be used for the disk. * @param controllerInfo pair containing the root disk and data disk controllers, respectively. */ - public static String getControllerBasedOnDiskType(Pair controllerInfo, DiskTO disk) { + public static DiskControllerType getControllerBasedOnDiskType(Pair controllerInfo, + DiskTO disk) { if (disk.getType() == Volume.Type.ROOT || disk.getDiskSeq() == 0) { - LOGGER.debug(String.format("Choosing disk controller [%s] for the root disk.", controllerInfo.first())); + LOGGER.debug("Choosing disk controller [{}] for the root disk.", controllerInfo.first()); return controllerInfo.first(); } - LOGGER.debug(String.format("Choosing disk controller [%s] for the data disks.", controllerInfo.second())); + LOGGER.debug("Choosing disk controller [{}] for the data disks.", controllerInfo.second()); return controllerInfo.second(); } } diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java index 570f2a7b4a16..e0814d760c3c 100644 --- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java +++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMOTest.java @@ -19,10 +19,20 @@ import com.cloud.hypervisor.vmware.util.VmwareClient; import com.cloud.hypervisor.vmware.util.VmwareContext; +import com.cloud.hypervisor.vmware.util.VmwareHelper; +import com.vmware.vim25.VirtualCdrom; +import com.vmware.vim25.VirtualDisk; +import com.vmware.vim25.VirtualE1000; +import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualLsiLogicController; +import com.vmware.vim25.VirtualNVMEController; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VirtualController; import com.vmware.vim25.VirtualDevice; -import com.vmware.vim25.VirtualLsiLogicController; +import com.vmware.vim25.VirtualMachinePowerState; import com.vmware.vim25.VirtualLsiLogicSASController; import com.vmware.vim25.VirtualSCSIController; import com.vmware.vim25.VirtualSCSISharing; @@ -34,11 +44,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; +import java.util.Set; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -53,6 +66,10 @@ public class VirtualMachineMOTest { VmwareClient client; @Mock ManagedObjectReference mor; + @Mock + VirtualController virtualControllerMock; + @Spy + VirtualMachineMO vmMoSpy; VirtualMachineMO vmMo; @@ -81,6 +98,9 @@ public void setup() { closeable = MockitoAnnotations.openMocks(this); vmMo = new VirtualMachineMO(context, mor); when(context.getVimClient()).thenReturn(client); + + vmMoSpy._context = context; + vmMoSpy._mor = mor; } @BeforeClass @@ -107,18 +127,6 @@ public void testEnsureScsiDeviceController() { } } - @Test - public void TestEnsureLsiLogicDeviceControllers() { - try { - when(client.getDynamicProperty(any(ManagedObjectReference.class), any(String.class))).thenReturn(getVirtualScSiDeviceList(VirtualLsiLogicController.class)); - vmMo.ensureLsiLogicDeviceControllers(1, 0); - } - catch (Exception e) { - fail("Received exception when success expected: " + e.getMessage()); - } - - } - @Test public void testGetVmxFormattedVirtualHardwareVersionOneDigit() { String vmxHwVersion = VirtualMachineMO.getVmxFormattedVirtualHardwareVersion(8); @@ -135,4 +143,302 @@ public void testGetVmxFormattedVirtualHardwareVersionTwoDigits() { public void testGetVmxFormattedVirtualHardwareVersionInvalid() { VirtualMachineMO.getVmxFormattedVirtualHardwareVersion(-1); } + + @Test + public void getDeviceBusNameTestReturnsValue() throws Exception { + List virtualDevices = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + VirtualLsiLogicController controller = new VirtualLsiLogicController(); + controller.setKey(100 + i); + controller.setBusNumber(i); + virtualDevices.add(controller); + } + VirtualDisk disk = new VirtualDisk(); + disk.setControllerKey(101); + disk.setUnitNumber(3); + + String result = vmMoSpy.getDeviceBusName(virtualDevices, disk); + + Assert.assertEquals("scsi1:3", result); + } + + private List configureDevicesForTests() throws Exception { + List devices = new ArrayList<>(); + devices.add(new VirtualE1000()); + devices.add(new VirtualCdrom()); + devices.add(new VirtualDisk()); + devices.add(new VirtualIDEController()); + devices.add(new VirtualIDEController()); + VirtualNVMEController nvmeController = new VirtualNVMEController(); + nvmeController.setKey(111); + nvmeController.setBusNumber(3); + devices.add(nvmeController); + Mockito.doReturn(devices).when(client).getDynamicProperty(mor, "config.hardware.device"); + return devices; + } + + @Test + public void getControllersTestReturnsValue() throws Exception { + List devices = configureDevicesForTests(); + + List result = vmMoSpy.getControllers(); + + for (VirtualDevice device : devices) { + if (!(device instanceof VirtualController)) { + continue; + } + Assert.assertTrue(result.contains(device)); + } + } + + @Test + public void getExistingDiskControllerTypesTestReturnsValue() throws Exception { + configureDevicesForTests(); + + Set result = vmMoSpy.getExistingDiskControllerTypes(); + + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Assert.assertTrue(result.contains(ideController)); + Assert.assertTrue(result.contains(nvmeController)); + } + + @Test + public void getDiskControllerTypeByBusNameTestReturnsTypeWhenControllerExists() throws Exception { + configureDevicesForTests(); + + DiskControllerType result = vmMoSpy.getDiskControllerTypeByBusName("nvme3:0"); + + Assert.assertEquals(VmwareHelper.getDiskControllerType("nvme", null), result); + } + + @Test + public void getDiskControllerTypeByBusNameTestReturnsNullWhenControllerDoesNotExist() throws Exception { + configureDevicesForTests(); + + Assert.assertNull(vmMoSpy.getDiskControllerTypeByBusName("scsi0:0")); + } + + @Test + public void getAnyExistingAvailableDiskControllerTestReturnsNonIdeControllerWhenNonIdeControllerIsAvailable() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Mockito.doReturn(new Pair<>(1, 2)).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + Mockito.lenient().doReturn(new Pair<>(3, 4)).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(ideController); + configureDevicesForTests(); + + DiskControllerType result = vmMoSpy.getAnyExistingAvailableDiskController(); + + + Assert.assertEquals(nvmeController, result); + } + + @Test + public void getAnyExistingAvailableDiskControllerTestReturnsIdeControllerWhenNonIdeControllerIsNotAvailable() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Mockito.doReturn(null).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + Mockito.doReturn(new Pair<>(1, 2)).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(ideController); + configureDevicesForTests(); + + DiskControllerType result = vmMoSpy.getAnyExistingAvailableDiskController(); + + Assert.assertEquals(ideController, result); + } + + @Test(expected = CloudRuntimeException.class) + public void getAnyExistingAvailableDiskControllerTestThrowsExceptionWhenNoDiskControllerIsAvailable() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Mockito.doReturn(null).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + Mockito.doReturn(null).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(ideController); + configureDevicesForTests(); + + vmMoSpy.getAnyExistingAvailableDiskController(); + } + + @Test + public void getNextAvailableControllerKeyAndDeviceNumberForTypeTestReturnsValueWhenControllerIsAvailable() throws Exception { + Mockito.doReturn(0).when(vmMoSpy).getNextAvailableDeviceNumberForController(Mockito.any(VirtualNVMEController.class), Mockito.any()); + configureDevicesForTests(); + + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair result = vmMoSpy.getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + + Assert.assertEquals(111, (int) result.first()); + Assert.assertEquals(0, (int) result.second()); + } + + @Test + public void getNextAvailableControllerKeyAndDeviceNumberForTypeTestReturnsNullWhenNoControllerIsAvailable() throws Exception { + Mockito.doReturn(-1).when(vmMoSpy).getNextAvailableDeviceNumberForController(Mockito.any(), Mockito.any()); + configureDevicesForTests(); + + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Pair result = vmMoSpy.getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + + Assert.assertNull(result); + } + + private Pair configureScsiControllersAndDisksForGetNextAvailableDeviceNumberForControllerTests(int numberOfDevices) throws Exception { + List devices = new ArrayList<>(); + + devices.add(new VirtualIDEController()); + devices.add(new VirtualIDEController()); + + int firstScsiKey = 1; + VirtualLsiLogicController firstScsiController = new VirtualLsiLogicController(); + firstScsiController.setKey(firstScsiKey); + firstScsiController.setBusNumber(0); + VirtualLsiLogicController secondScsiController = new VirtualLsiLogicController(); + secondScsiController.setKey(firstScsiKey + 1); + secondScsiController.setBusNumber(1); + devices.add(firstScsiController); + devices.add(secondScsiController); + + int currentDeviceNumber = 0; + for (int i = 0; i < numberOfDevices; i++) { + VirtualDevice device = i == 0 ? new VirtualCdrom() : new VirtualDisk(); + device.setControllerKey(firstScsiKey); + device.setUnitNumber(currentDeviceNumber); + devices.add(device); + + currentDeviceNumber++; + if (VmwareHelper.isReservedScsiDeviceNumber(currentDeviceNumber)) { + currentDeviceNumber++; + } + } + + Mockito.doReturn(devices).when(client).getDynamicProperty(mor, "config.hardware.device"); + + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + return new Pair<>(firstScsiController, lsilogicController); + } + + @Test + public void getNextAvailableDeviceNumberForControllerTestValueWhenDeviceNumberIsAvailable() throws Exception { + int numberOfDevices = 14; + Pair config = configureScsiControllersAndDisksForGetNextAvailableDeviceNumberForControllerTests(numberOfDevices); + + int result = vmMoSpy.getNextAvailableDeviceNumberForController(config.first(), config.second()); + + Assert.assertEquals(numberOfDevices + 1, result); + } + + @Test + public void getNextAvailableDeviceNumberForControllerTestValueWhenNoDeviceNumberIsAvailable() throws Exception { + Pair config = configureScsiControllersAndDisksForGetNextAvailableDeviceNumberForControllerTests(15); + + int result = vmMoSpy.getNextAvailableDeviceNumberForController(config.first(), config.second()); + + Assert.assertEquals(-1, result); + } + + @Test + public void getNextAvailableDeviceNumberForTypeTestReturnsGlobalDeviceNumberWhenFirstControllerIsFull() throws Exception { + Pair config = configureScsiControllersAndDisksForGetNextAvailableDeviceNumberForControllerTests(15); + + Integer result = vmMoSpy.getNextAvailableDeviceNumberForType(config.second()); + + Assert.assertEquals(config.second().getMaxDeviceCount(), result); + } + + @Test + public void getNextAvailableDeviceNumberForTypeTestSkipsUsedDeviceNumbersFromProvidedValue() throws Exception { + List devices = new ArrayList<>(); + VirtualLsiLogicController controller = new VirtualLsiLogicController(); + controller.setKey(1); + controller.setBusNumber(0); + devices.add(controller); + for (int unitNumber : new int[]{0, 1, 3}) { + VirtualDisk disk = new VirtualDisk(); + disk.setControllerKey(1); + disk.setUnitNumber(unitNumber); + devices.add(disk); + } + Mockito.doReturn(devices).when(client).getDynamicProperty(mor, "config.hardware.device"); + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + + Integer result = vmMoSpy.getNextAvailableDeviceNumberForType(lsilogicController, 3); + + Assert.assertEquals(4, (int) result); + } + + @Test + public void getDiskControllerKeyAndUnitNumberTestUsesScsiControllerWhenDiskControllerIsNull() throws Exception { + Mockito.doReturn(42).when(vmMoSpy).getScsiDeviceControllerKey(); + + Ternary result = vmMoSpy.getDiskControllerKeyAndUnitNumber(null); + + Assert.assertEquals(42, (int) result.first()); + Assert.assertEquals(-1, (int) result.second()); + Assert.assertFalse(result.third()); + } + + @Test + public void getDiskControllerKeyAndUnitNumberTestReturnsNextAvailableController() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.doReturn(new Pair<>(200, 3)).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + + Ternary result = vmMoSpy.getDiskControllerKeyAndUnitNumber(nvmeController); + + Assert.assertEquals(200, (int) result.first()); + Assert.assertEquals(3, (int) result.second()); + Assert.assertTrue(result.third()); + } + + @Test(expected = Exception.class) + public void getDiskControllerKeyAndUnitNumberTestThrowsExceptionWhenAddingIdeDiskToPoweredOnVm() throws Exception { + Mockito.doReturn(VirtualMachinePowerState.POWERED_ON).when(vmMoSpy).getPowerState(); + + vmMoSpy.getDiskControllerKeyAndUnitNumber(DiskControllerType.ide); + } + + @Test(expected = CloudRuntimeException.class) + public void getDiskControllerKeyAndUnitNumberTestThrowsExceptionWhenNoControllerIsAvailable() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.doReturn(null).when(vmMoSpy).getNextAvailableControllerKeyAndDeviceNumberForType(nvmeController); + + vmMoSpy.getDiskControllerKeyAndUnitNumber(nvmeController); + } + + @Test + public void getNthDeviceTestReturnsDeviceWhenDeviceExists() throws Exception { + configureDevicesForTests(); + + vmMoSpy.getNthDevice(VirtualIDEController.class.getName(), 1); + } + + @Test(expected = CloudRuntimeException.class) + public void getNthDeviceTestThrowsExceptionWhenDeviceDoesNotExist() throws Exception { + configureDevicesForTests(); + + vmMoSpy.getNthDevice(VirtualIDEController.class.getName(), 2); + } + + @Test(expected = CloudRuntimeException.class) + public void validateDiskControllerIsAvailableTestThrowsExceptionWhenBusNumberIsInvalid() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.when(virtualControllerMock.getBusNumber()).thenReturn(4); + + vmMo.validateDiskControllerIsAvailable(virtualControllerMock, nvmeController); + } + + @Test(expected = CloudRuntimeException.class) + public void validateDiskControllerIsAvailableTestThrowsExceptionWhenNoAvailableDeviceNumberExists() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.when(virtualControllerMock.getBusNumber()).thenReturn(3); + Mockito.doReturn(-1).when(vmMoSpy).getNextAvailableDeviceNumberForController(virtualControllerMock, nvmeController); + + vmMoSpy.validateDiskControllerIsAvailable(virtualControllerMock, nvmeController); + } + + @Test + public void validateDiskControllerIsAvailableTestDoesNothingWhenBusNumberIsValidAndAvailableDeviceNumberExists() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + Mockito.when(virtualControllerMock.getBusNumber()).thenReturn(3); + Mockito.doReturn(14).when(vmMoSpy).getNextAvailableDeviceNumberForController(virtualControllerMock, nvmeController); + + vmMoSpy.validateDiskControllerIsAvailable(virtualControllerMock, nvmeController); + } } diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java index 54a312909b3d..b6657173b958 100644 --- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java +++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java @@ -20,12 +20,28 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import com.cloud.agent.api.to.DiskTO; import com.cloud.hypervisor.vmware.mo.ClusterMO; import com.cloud.hypervisor.vmware.mo.HostMO; +import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost; +import com.cloud.storage.Volume; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; import com.vmware.vim25.DatastoreInfo; import com.vmware.vim25.Description; import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VirtualCdrom; +import com.vmware.vim25.VirtualController; +import com.vmware.vim25.VirtualDevice; +import com.vmware.vim25.VirtualDeviceConfigSpec; +import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; +import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualLsiLogicController; +import com.vmware.vim25.VirtualMachineConfigSpec; +import com.vmware.vim25.VirtualSCSIController; +import com.vmware.vim25.VirtualSCSISharing; +import com.cloud.hypervisor.vmware.mo.DiskControllerType; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.junit.Assert; import org.junit.Before; @@ -38,12 +54,22 @@ import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; import com.vmware.vim25.VirtualDisk; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; @RunWith(MockitoJUnitRunner.class) public class VmwareHelperTest { @Mock private VirtualMachineMO virtualMachineMO; + @Mock + private VmwareHypervisorHost vmwareHypervisorHostMock; + @Mock + private VirtualMachineConfigSpec virtualMachineConfigSpecMock; private static final String diskLabel = "disk1"; private static final String diskFileBaseName = "xyz.vmdk"; @@ -75,6 +101,8 @@ public void setUp() throws Exception { Mockito.when(context.getVimClient()).thenReturn(client); Mockito.when(virtualMachineMO.getContext()).thenReturn(context); Mockito.when(virtualMachineMO.getName()).thenReturn(vmName); + + Mockito.when(virtualMachineConfigSpecMock.getDeviceChange()).thenReturn(new ArrayList<>()); } @Test @@ -108,6 +136,289 @@ public void testGetUnmanageInstanceDisks() { Assert.assertEquals(dataStoreName, disk.getDatastoreName()); } + @Test + public void isControllerOsRecommendedTestControllerIsOsRecommendedReturnsTrue() { + DiskControllerType controllerType = DiskControllerType.osdefault; + + boolean result = VmwareHelper.isControllerOsRecommended(controllerType); + + Assert.assertTrue(result); + } + + @Test + public void isControllerOsRecommendedTestControllerIsNotOsRecommendedReturnsFalse() { + DiskControllerType controllerType = DiskControllerType.lsilogic; + + boolean result = VmwareHelper.isControllerOsRecommended(controllerType); + + Assert.assertFalse(result); + } + + @Test + public void isControllerScsiTestControllerIsScsiReturnsTrue() { + DiskControllerType controllerType = DiskControllerType.lsilogic; + + boolean result = VmwareHelper.isControllerScsi(controllerType); + + Assert.assertTrue(result); + } + + @Test + public void isControllerScsiTestControllerIsNotScsiReturnsFalse() { + DiskControllerType controllerType = DiskControllerType.nvme; + + boolean result = VmwareHelper.isControllerScsi(controllerType); + + Assert.assertFalse(result); + } + + @Test + public void getDiskControllerTypeTestSearchByExistingNameReturnsObject() { + String name = "lsilogic"; + + DiskControllerType controllerType = VmwareHelper.getDiskControllerType(name, null); + + Assert.assertEquals(name, controllerType.getName()); + } + + @Test + public void getDiskControllerTypeTestSearchByExistingControllerReferenceReturnsObject() { + String classpath = VirtualLsiLogicController.class.getName(); + + DiskControllerType controllerType = VmwareHelper.getDiskControllerType(null, classpath); + + Assert.assertEquals(classpath, controllerType.getControllerReference()); + } + + @Test + public void getDiskControllerTypeTestSearchIsCaseInsensitiveAndAcceptsAliases() { + Assert.assertEquals(DiskControllerType.lsisas1068, VmwareHelper.getDiskControllerType("LSILogicSAS", null)); + Assert.assertEquals(DiskControllerType.pvscsi, VmwareHelper.getDiskControllerType("VirtualSCSI", null)); + Assert.assertEquals(DiskControllerType.lsilogic, VmwareHelper.getDiskControllerType("VirtualLsiLogicController", null)); + Assert.assertEquals(DiskControllerType.ide, VmwareHelper.getDiskControllerType("vim.vm.device.VirtualIDEController", null)); + } + + @Test(expected = CloudRuntimeException.class) + public void getDiskControllerTypeTestThrowsExceptionWhenNoMatches() { + VmwareHelper.getDiskControllerType("invalid", "invalid"); + } + + @Test + public void getAllSupportedDiskControllerTypesExceptOsDefaultTestDoesNotContainOsDefault() { + List result = VmwareHelper.getAllSupportedDiskControllerTypesExceptOsDefault(); + + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Assert.assertFalse(result.contains(osdefaultController)); + Assert.assertFalse(result.isEmpty()); + } + + @Test + public void getRequiredDiskControllersTestRequiresAllControllersWhenInstanceIsNotSystemVm() { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + + Set result = VmwareHelper.getRequiredDiskControllers(controllerInfo, false); + + Assert.assertTrue(result.contains(controllerInfo.first())); + Assert.assertTrue(result.contains(controllerInfo.second())); + } + + @Test + public void getRequiredDiskControllersTestRequiresOnlyRootDiskControllerWhenInstanceIsSystemVm() { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + + Set result = VmwareHelper.getRequiredDiskControllers(controllerInfo, true); + + Assert.assertTrue(result.contains(controllerInfo.first())); + Assert.assertFalse(result.contains(controllerInfo.second())); + } + + @Test + public void chooseDiskControllersDiskControllersTestControllersAreNotOsRecommendedReturnsProvidedControllers() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType sataController = VmwareHelper.getDiskControllerType("sata", null); + Pair controllerInfo = new Pair<>(nvmeController, sataController); + + Pair result = VmwareHelper.chooseDiskControllers(controllerInfo, virtualMachineMO, null, null); + + Assert.assertEquals(nvmeController, result.first()); + Assert.assertEquals(sataController, result.second()); + Mockito.verify(virtualMachineMO, Mockito.never()).getRecommendedDiskController(null); + } + + @Test + public void chooseDiskControllersTestControllersAreOsRecommendedAndVmMoIsProvidedReturnsConvertedControllersBasedOnVmMo() throws Exception { + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Pair controllerInfo = new Pair<>(osdefaultController, osdefaultController); + Mockito.doReturn("VirtualLsiLogicController").when(virtualMachineMO).getRecommendedDiskController(null); + + Pair result = VmwareHelper.chooseDiskControllers(controllerInfo, virtualMachineMO, null, null); + + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Assert.assertEquals(lsilogicController, result.first()); + Assert.assertEquals(lsilogicController, result.second()); + } + + @Test + public void chooseDiskControllersTestResolvesAliasedRecommendedController() throws Exception { + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Pair controllerInfo = new Pair<>(osdefaultController, osdefaultController); + Mockito.doReturn("lsilogicsas").when(virtualMachineMO).getRecommendedDiskController(null); + + Pair result = VmwareHelper.chooseDiskControllers(controllerInfo, virtualMachineMO, null, null); + + Assert.assertEquals(DiskControllerType.lsisas1068, result.first()); + Assert.assertEquals(DiskControllerType.lsisas1068, result.second()); + } + + @Test + public void chooseDiskControllersTestControllersAreOsRecommendedAndHostIsProvidedReturnsConvertedControllersBasedOnHost() throws Exception { + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + Pair controllerInfo = new Pair<>(osdefaultController, osdefaultController); + String guestOsId = "guestOsId"; + Mockito.doReturn("VirtualLsiLogicController").when(vmwareHypervisorHostMock).getRecommendedDiskController(guestOsId); + + Pair result = VmwareHelper.chooseDiskControllers(controllerInfo, null, vmwareHypervisorHostMock, guestOsId); + + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Assert.assertEquals(lsilogicController, result.first()); + Assert.assertEquals(lsilogicController, result.second()); + } + + @Test + public void chooseDiskControllersTestControllersShareTheSameBusTypeReturnsRootDiskController() throws Exception { + DiskControllerType osdefaultController = VmwareHelper.getDiskControllerType("osdefault", null); + DiskControllerType pvscsiController = VmwareHelper.getDiskControllerType("pvscsi", null); + Pair controllerInfo = new Pair<>(osdefaultController, pvscsiController); + Mockito.doReturn("VirtualLsiLogicController").when(virtualMachineMO).getRecommendedDiskController(null); + + Pair result = VmwareHelper.chooseDiskControllers(controllerInfo, virtualMachineMO, null, null); + + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Assert.assertEquals(lsilogicController, result.first()); + Assert.assertEquals(lsilogicController, result.second()); + } + + @Test + public void addDiskControllersToVmConfigSpecTestDoesNotAddIdeControllers() throws Exception { + DiskControllerType ideController = VmwareHelper.getDiskControllerType("ide", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(ideController); + + VmwareHelper.addDiskControllersToVmConfigSpec(virtualMachineConfigSpecMock, requiredControllers, false); + + Assert.assertEquals(0, virtualMachineConfigSpecMock.getDeviceChange().size()); + } + + @Test + public void addDiskControllersToVmConfigSpecTestMaximumAmmountOfControllersIsAdded() throws Exception { + DiskControllerType nvmeController = VmwareHelper.getDiskControllerType("nvme", null); + DiskControllerType sataController = VmwareHelper.getDiskControllerType("sata", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(nvmeController); + requiredControllers.add(sataController); + + VmwareHelper.addDiskControllersToVmConfigSpec(virtualMachineConfigSpecMock, requiredControllers, false); + + int expectedControllerAmount = nvmeController.getMaxControllerCount() + sataController.getMaxControllerCount(); + Assert.assertEquals(expectedControllerAmount, virtualMachineConfigSpecMock.getDeviceChange().size()); + + Set usedKeys = new HashSet<>(); + Map> usedBusNumbers = new HashMap<>(); + usedBusNumbers.put(nvmeController.getControllerReference(), new HashSet<>()); + usedBusNumbers.put(sataController.getControllerReference(), new HashSet<>()); + for (VirtualDeviceConfigSpec virtualDeviceConfigSpec : virtualMachineConfigSpecMock.getDeviceChange()) { + Assert.assertEquals(VirtualDeviceConfigSpecOperation.ADD, virtualDeviceConfigSpec.getOperation()); + VirtualController controller = (VirtualController) virtualDeviceConfigSpec.getDevice(); + usedKeys.add(controller.getKey()); + usedBusNumbers.get(controller.getClass().getName()).add(controller.getBusNumber()); + } + Assert.assertEquals(expectedControllerAmount, usedKeys.size()); + Assert.assertEquals((int) nvmeController.getMaxControllerCount(), usedBusNumbers.get(nvmeController.getControllerReference()).size()); + Assert.assertEquals((int) sataController.getMaxControllerCount(), usedBusNumbers.get(sataController.getControllerReference()).size()); + } + + @Test + public void addDiskControllersToVmConfigSpecTestAddedScsiControllersDoNotShareBus() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(lsilogicController); + + VmwareHelper.addDiskControllersToVmConfigSpec(virtualMachineConfigSpecMock, requiredControllers, false); + + for (VirtualDeviceConfigSpec virtualDeviceConfigSpec : virtualMachineConfigSpecMock.getDeviceChange()) { + Assert.assertEquals(VirtualSCSISharing.NO_SHARING, ((VirtualSCSIController) virtualDeviceConfigSpec.getDevice()).getSharedBus()); + } + } + + @Test + public void addDiskControllersToVmConfigSpecTestInstanceIsSystemVmAddsOneController() throws Exception { + DiskControllerType lsilogicController = VmwareHelper.getDiskControllerType("lsilogic", null); + Set requiredControllers = new HashSet<>(); + requiredControllers.add(lsilogicController); + + VmwareHelper.addDiskControllersToVmConfigSpec(virtualMachineConfigSpecMock, requiredControllers, true); + + Assert.assertEquals(1, virtualMachineConfigSpecMock.getDeviceChange().size()); + } + + @Test + public void getDiskControllersFromVmSettingsTestReturnsSpecifiedControllersWhenInstanceIsNotSystemVm() { + Pair controllerInfo = VmwareHelper.getDiskControllersFromVmSettings("nvme", "sata", false); + + Assert.assertEquals("nvme", controllerInfo.first().getName()); + Assert.assertEquals("sata", controllerInfo.second().getName()); + } + + @Test + public void getDiskControllersFromVmSettingsTestReturnsLsiLogicForRootDiskWhenInstanceIsSystemVm() { + Pair controllerInfo = VmwareHelper.getDiskControllersFromVmSettings("nvme", "sata", true); + + Assert.assertEquals("lsilogic", controllerInfo.first().getName()); + Assert.assertEquals("sata", controllerInfo.second().getName()); + } + + @Test + public void getDiskControllersFromVmSettingsTestReturnsOsDefaultForMissingDataDiskControllerWhenInstanceIsNotSystemVm() { + Pair controllerInfo = VmwareHelper.getDiskControllersFromVmSettings("ide", null, false); + + Assert.assertEquals(DiskControllerType.ide, controllerInfo.first()); + Assert.assertEquals(DiskControllerType.osdefault, controllerInfo.second()); + } + + @Test + public void getControllerBasedOnDiskTypeTestReturnsRootDiskControllerWhenVolumeTypeIsRoot() { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + DiskTO disk = new DiskTO(); + disk.setType(Volume.Type.ROOT); + + DiskControllerType result = VmwareHelper.getControllerBasedOnDiskType(controllerInfo, disk); + + Assert.assertEquals(DiskControllerType.lsilogic, result); + } + + @Test + public void getControllerBasedOnDiskTypeTestReturnsRootDiskControllerWhenVolumeDiskSeqIsZero() { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + DiskTO disk = new DiskTO(); + disk.setDiskSeq(0L); + + DiskControllerType result = VmwareHelper.getControllerBasedOnDiskType(controllerInfo, disk); + + Assert.assertEquals(DiskControllerType.lsilogic, result); + } + + @Test + public void getControllerBasedOnDiskTypeTestReturnsDataDiskControllerWhenVolumeTypeIsNotRootAndDiskSeqIsNotZero() { + Pair controllerInfo = new Pair<>(DiskControllerType.lsilogic, DiskControllerType.nvme); + DiskTO disk = new DiskTO(); + disk.setType(Volume.Type.DATADISK); + disk.setDiskSeq(1L); + + DiskControllerType result = VmwareHelper.getControllerBasedOnDiskType(controllerInfo, disk); + + Assert.assertEquals(DiskControllerType.nvme, result); + } + @Test public void testGetVmwareHostVersionException() throws Exception { HostMO hostMO = Mockito.mock(HostMO.class); @@ -128,4 +439,55 @@ public void testGetVmwareHostVersionInvalidParameter() { ClusterMO clusterMO = Mockito.mock(ClusterMO.class); Assert.assertNull(VmwareHelper.getVmwareHostVersion(clusterMO)); } + + @Test + public void prepareIsoDeviceTestUsesIdeControllerAndDeviceNumberFromGlobalUnitNumber() throws Exception { + VirtualController secondIdeController = new VirtualIDEController(); + secondIdeController.setKey(42); + Mockito.when(virtualMachineMO.getIsoDevice()).thenReturn(null); + Mockito.when(virtualMachineMO.getNthDevice(DiskControllerType.ide.getControllerReference(), 1)).thenReturn(secondIdeController); + + Pair result = VmwareHelper.prepareIsoDevice(virtualMachineMO, null, null, true, true, 2, 1); + + VirtualCdrom cdRom = (VirtualCdrom) result.first(); + Assert.assertTrue(result.second()); + Assert.assertEquals(42, (int) cdRom.getControllerKey()); + Assert.assertEquals(0, (int) cdRom.getUnitNumber()); + } + + @Test + public void validateDiskControllersHardwareVersionTestDoesNothingWhenNoControllersAreProvided() { + VmwareHelper.validateDiskControllersHardwareVersion(Collections.emptySet(), 0); + } + + @Test + public void validateDiskControllersHardwareVersionTestDoesNothingWhenControllersDoNotRequireMinimumHardwareVersion() { + VmwareHelper.validateDiskControllersHardwareVersion(Set.of(DiskControllerType.ide, DiskControllerType.lsilogic), 0); + } + + @Test + public void validateDiskControllersHardwareVersionTestDoesNothingWhenHardwareVersionEqualsMinimum() { + VmwareHelper.validateDiskControllersHardwareVersion(Set.of(DiskControllerType.ahci), 10); + } + + @Test + public void validateDiskControllersHardwareVersionTestDoesNothingWhenHardwareVersionIsHigherThanMinimum() { + VmwareHelper.validateDiskControllersHardwareVersion(Set.of(DiskControllerType.nvme), 14); + } + + @Test(expected = CloudRuntimeException.class) + public void validateDiskControllersHardwareVersionTestThrowsExceptionWhenHardwareVersionIsTooOld() { + VmwareHelper.validateDiskControllersHardwareVersion(Set.of(DiskControllerType.nvme), 12); + } + + @Test + public void validateDiskControllersHardwareVersionTestThrowsExceptionListingAllUnsupportedControllers() { + try { + VmwareHelper.validateDiskControllersHardwareVersion(Set.of(DiskControllerType.pvscsi, DiskControllerType.nvme), 6); + Assert.fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + Assert.assertTrue(e.getMessage().contains(DiskControllerType.pvscsi.getName())); + Assert.assertTrue(e.getMessage().contains(DiskControllerType.nvme.getName())); + } + } }