[VMware to KVM migration] Check source VM against the selected offering (#11908)

* [VMware to KVM migration] Check source VM against the selected offering

* Fix build
This commit is contained in:
Nicolas Vazquez 2025-10-26 08:15:26 -03:00 committed by GitHub
parent c2c1e11580
commit bfc4f60e1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 124 additions and 3 deletions

View File

@ -61,6 +61,7 @@ import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
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 class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
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 class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
}
}
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 class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co
}
}
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");

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
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 class VMwareGuruTest {
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);
}
}

View File

@ -1644,16 +1644,45 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
}
private Pair<UnmanagedInstanceTO, Boolean> 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<String, String> 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<String, String> params, ServiceOfferingVO serviceOffering) {
if (serviceOffering != null) {
serviceOfferingDao.loadDetails(serviceOffering);
Map<String, String> 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 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager {
// 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<UnmanagedInstanceTO, Boolean> sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName);
Pair<UnmanagedInstanceTO, Boolean> sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName, serviceOffering);
sourceVMwareInstance = sourceInstanceDetails.first();
isClonedInstance = sourceInstanceDetails.second();

View File

@ -40,6 +40,7 @@ import java.util.UUID;
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 class UnmanagedVMsManagerImplTest {
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<String, String> 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<String, String> params = new HashMap<>();
ServiceOfferingVO serviceOfferingVO = mock(ServiceOfferingVO.class);
Map<String, String> 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<String, String> 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));
}
}