diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index 88df637b0dd1..14991e483dfb 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -61,6 +61,7 @@ import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.agent.api.BackupSnapshotCommand; @@ -1393,6 +1394,9 @@ public Pair getHypervisorVMOutOfBandAndCloneIfRequ String datacenter = params.get(VmDetailConstants.VMWARE_DATACENTER_NAME); String username = params.get(VmDetailConstants.VMWARE_VCENTER_USERNAME); String password = params.get(VmDetailConstants.VMWARE_VCENTER_PASSWORD); + Integer requestedCpuNumber = params.containsKey(VmDetailConstants.CPU_NUMBER) ? Integer.parseInt(params.get(VmDetailConstants.CPU_NUMBER)) : null; + Integer requestedCpuSpeed = params.containsKey(VmDetailConstants.CPU_SPEED) ? Integer.parseInt(params.get(VmDetailConstants.CPU_SPEED)) : null; + Integer requestedMemory = params.containsKey(VmDetailConstants.MEMORY) ? Integer.parseInt(params.get(VmDetailConstants.MEMORY)) : null; try { VmwareContext context = connectToVcenter(vcenter, username, password); @@ -1428,6 +1432,8 @@ public Pair getHypervisorVMOutOfBandAndCloneIfRequ } } + checkSourceVmResourcesAgainstSelectedOfferingResources(vmMo, requestedCpuNumber, requestedCpuSpeed, requestedMemory); + logger.debug(String.format("Cloning VM %s at VMware host %s on vCenter %s", vmName, hostIp, vcenter)); VirtualMachineMO clonedVM = createCloneFromSourceVM(vmName, vmMo, dataCenterMO); logger.debug(String.format("VM %s cloned successfully, to VM %s", vmName, clonedVM.getName())); @@ -1444,6 +1450,29 @@ public Pair getHypervisorVMOutOfBandAndCloneIfRequ } } + protected void checkSourceVmResourcesAgainstSelectedOfferingResources(VirtualMachineMO vmMo, Integer requestedCpuNumber, Integer requestedCpuSpeed, Integer requestedMemory) throws Exception { + if (ObjectUtils.allNull(requestedCpuNumber, requestedCpuSpeed, requestedMemory)) { + return; + } + VirtualMachineConfigSummary configSummary = vmMo.getConfigSummary(); + if (configSummary != null) { + compareSourceVmResourceAgainstRequested(configSummary.getNumCpu(), requestedCpuNumber, "CPU number"); + compareSourceVmResourceAgainstRequested(configSummary.getCpuReservation(), requestedCpuSpeed, "CPU speed"); + compareSourceVmResourceAgainstRequested(configSummary.getMemorySizeMB(), requestedMemory, "Memory"); + } + } + + protected void compareSourceVmResourceAgainstRequested(Integer actualResource, Integer requestedResource, String resourceName) throws Exception { + if (ObjectUtils.anyNull(actualResource, requestedResource)) { + return; + } + if (requestedResource < actualResource) { + String err = String.format("The requested %s (%d) is less than the source VM %s (%d)", resourceName, requestedResource, resourceName, actualResource); + logger.error(err); + throw new CloudRuntimeException(err); + } + } + private boolean isWindowsVm(VirtualMachineMO vmMo) throws Exception { UnmanagedInstanceTO sourceInstance = VmwareHelper.getUnmanagedInstance(vmMo.getRunningHost(), vmMo); return sourceInstance.getOperatingSystem().toLowerCase().contains("windows"); diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java index 6e96330ac584..270f61fcbbee 100644 --- a/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java +++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/guru/VMwareGuruTest.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; +import com.vmware.vim25.VirtualMachineConfigSummary; import org.apache.cloudstack.storage.NfsMountManager; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -626,4 +627,24 @@ public void testRemoveVMTemplateFileOutOfBand() throws Exception { boolean result = vMwareGuru.removeVMTemplateOutOfBand(dataStore, templateDir); assertTrue(result); } + + @Test(expected = CloudRuntimeException.class) + public void testCheckSourceVmResourcesAgainstSelectedOfferingResourcesInsufficientMemory() throws Exception { + VirtualMachineMO virtualMachineMO = Mockito.mock(VirtualMachineMO.class); + VirtualMachineConfigSummary configSummary = Mockito.mock(VirtualMachineConfigSummary.class); + Mockito.when(virtualMachineMO.getConfigSummary()).thenReturn(configSummary); + Mockito.when(configSummary.getNumCpu()).thenReturn(1); + Mockito.when(configSummary.getMemorySizeMB()).thenReturn(2048); + vMwareGuru.checkSourceVmResourcesAgainstSelectedOfferingResources(virtualMachineMO, 1, 500, 1024); + } + + @Test + public void testCheckSourceVmResourcesAgainstSelectedOfferingResourcesGreaterOffering() throws Exception { + VirtualMachineMO virtualMachineMO = Mockito.mock(VirtualMachineMO.class); + VirtualMachineConfigSummary configSummary = Mockito.mock(VirtualMachineConfigSummary.class); + Mockito.when(virtualMachineMO.getConfigSummary()).thenReturn(configSummary); + Mockito.when(configSummary.getNumCpu()).thenReturn(1); + Mockito.when(configSummary.getMemorySizeMB()).thenReturn(1024); + vMwareGuru.checkSourceVmResourcesAgainstSelectedOfferingResources(virtualMachineMO, 2, 1500, 2048); + } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 14e038487cd5..13fa2608016c 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1644,16 +1644,45 @@ private UserVm importUnmanagedInstanceFromHypervisor(DataCenter zone, Cluster cl } private Pair getSourceVmwareUnmanagedInstance(String vcenter, String datacenterName, String username, - String password, String clusterName, String sourceHostName, - String sourceVM) { + String password, String clusterName, String sourceHostName, + String sourceVM, ServiceOfferingVO serviceOffering) { HypervisorGuru vmwareGuru = hypervisorGuruManager.getGuru(Hypervisor.HypervisorType.VMware); Map params = createParamsForTemplateFromVmwareVmMigration(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVM); + addServiceOfferingDetailsToParams(params, serviceOffering); return vmwareGuru.getHypervisorVMOutOfBandAndCloneIfRequired(sourceHostName, sourceVM, params); } + /** + * Add the minimum resources to check on the hypervisor source VM before converting the instance against the selected offering resources + * @param params sets the minimum CPU number, CPU speed and memory to be checked against the source VM + * @param serviceOffering service offering for the converted VM + */ + protected void addServiceOfferingDetailsToParams(Map params, ServiceOfferingVO serviceOffering) { + if (serviceOffering != null) { + serviceOfferingDao.loadDetails(serviceOffering); + Map serviceOfferingDetails = serviceOffering.getDetails(); + + if (serviceOffering.getCpu() != null) { + params.put(VmDetailConstants.CPU_NUMBER, String.valueOf(serviceOffering.getCpu())); + } else if (MapUtils.isNotEmpty(serviceOfferingDetails) && serviceOfferingDetails.containsKey(ApiConstants.MIN_CPU_NUMBER)) { + params.put(VmDetailConstants.CPU_NUMBER, serviceOfferingDetails.get(ApiConstants.MIN_CPU_NUMBER)); + } + + if (serviceOffering.getSpeed() != null) { + params.put(VmDetailConstants.CPU_SPEED, String.valueOf(serviceOffering.getSpeed())); + } + + if (serviceOffering.getRamSize() != null) { + params.put(VmDetailConstants.MEMORY, String.valueOf(serviceOffering.getRamSize())); + } else if (MapUtils.isNotEmpty(serviceOfferingDetails) && serviceOfferingDetails.containsKey(ApiConstants.MIN_MEMORY)) { + params.put(VmDetailConstants.MEMORY, serviceOfferingDetails.get(ApiConstants.MIN_MEMORY)); + } + } + } + private String createOvfTemplateOfSourceVmwareUnmanagedInstance(String vcenter, String datacenterName, String username, String password, String clusterName, String sourceHostName, String sourceVMwareInstanceName, DataStoreTO convertLocation, int threadsCountToExportOvf) { @@ -1734,7 +1763,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster // sourceVMwareInstance could be a cloned instance from sourceVMName, of the sourceVMName itself if its powered off. // isClonedInstance indicates if the VM is a clone of sourceVMName - Pair sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName); + Pair sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName, serviceOffering); sourceVMwareInstance = sourceInstanceDetails.first(); isClonedInstance = sourceInstanceDetails.second(); diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 60ce662b4f37..a24ba7f068b2 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -40,6 +40,7 @@ import com.cloud.offering.DiskOffering; import com.cloud.vm.ImportVMTaskVO; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; @@ -1309,4 +1310,45 @@ public void testCheckExtraParamsAllowedEnabledParamNotInTheAllowedList() { Mockito.when(configKeyMockParamsAllowedList.value()).thenReturn("network,x"); unmanagedVMsManager.checkExtraParamsAllowed("--mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254 -x"); } + + @Test + public void testAddServiceOfferingDetailsToParamsFixedOffering() { + Map params = new HashMap<>(); + ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class); + Mockito.when(serviceOfferingVO.getCpu()).thenReturn(2); + Mockito.when(serviceOfferingVO.getRamSize()).thenReturn(2048); + unmanagedVMsManager.addServiceOfferingDetailsToParams(params, serviceOfferingVO); + Assert.assertEquals("2", params.get(VmDetailConstants.CPU_NUMBER)); + Assert.assertEquals("2048", params.get(VmDetailConstants.MEMORY)); + } + + @Test + public void testAddServiceOfferingDetailsToParamsCustomConstrainedOffering() { + Map params = new HashMap<>(); + ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class); + Map details = new HashMap<>(); + details.put(ApiConstants.MIN_CPU_NUMBER, "1"); + details.put(ApiConstants.MIN_MEMORY, "1024"); + Mockito.when(serviceOfferingVO.getDetails()).thenReturn(details); + Mockito.when(serviceOfferingVO.getCpu()).thenReturn(null); + Mockito.when(serviceOfferingVO.getSpeed()).thenReturn(1500); + Mockito.when(serviceOfferingVO.getRamSize()).thenReturn(null); + unmanagedVMsManager.addServiceOfferingDetailsToParams(params, serviceOfferingVO); + Assert.assertEquals("1", params.get(VmDetailConstants.CPU_NUMBER)); + Assert.assertEquals("1500", params.get(VmDetailConstants.CPU_SPEED)); + Assert.assertEquals("1024", params.get(VmDetailConstants.MEMORY)); + } + + @Test + public void testAddServiceOfferingDetailsToParamsCustomUnconstrainedOffering() { + Map params = new HashMap<>(); + ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class); + Mockito.when(serviceOfferingVO.getCpu()).thenReturn(null); + Mockito.when(serviceOfferingVO.getSpeed()).thenReturn(null); + Mockito.when(serviceOfferingVO.getRamSize()).thenReturn(null); + unmanagedVMsManager.addServiceOfferingDetailsToParams(params, serviceOfferingVO); + Assert.assertFalse(params.containsKey(VmDetailConstants.CPU_NUMBER)); + Assert.assertFalse(params.containsKey(VmDetailConstants.CPU_SPEED)); + Assert.assertFalse(params.containsKey(VmDetailConstants.MEMORY)); + } }