diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index d6099ac47178..b9a4938b5b29 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -225,6 +225,7 @@ public class ApiConstants { public static final String ICMP_TYPE = "icmptype"; public static final String ID = "id"; public static final String IDS = "ids"; + public static final String IMPORT_INSTANCE_HOST_ID = "importinstancehostid"; public static final String INDEX = "index"; public static final String INSTANCES_DISKS_STATS_RETENTION_ENABLED = "instancesdisksstatsretentionenabled"; public static final String INSTANCES_DISKS_STATS_RETENTION_TIME = "instancesdisksstatsretentiontime"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java index 945f861cd3e2..d18efedbbe98 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -146,9 +146,13 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { private String clusterName; @Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, - description = "(only for importing VMs from VMware to KVM) optional - the host to perform the virt-v2v migration from VMware to KVM.") + description = "(only for importing VMs from VMware to KVM) optional - the host to perform the virt-v2v conversion from VMware to KVM.") private Long convertInstanceHostId; + @Parameter(name = ApiConstants.IMPORT_INSTANCE_HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, since = "4.19.2", + description = "(only for importing VMs from VMware to KVM) optional - the host to use to import the converted instance for migration from VMware to KVM.") + private Long importInstanceHostId; + @Parameter(name = ApiConstants.CONVERT_INSTANCE_STORAGE_POOL_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "(only for importing VMs from VMware to KVM) optional - the temporary storage pool to perform the virt-v2v migration from VMware to KVM.") private Long convertStoragePoolId; @@ -201,6 +205,10 @@ public Long getConvertInstanceHostId() { return convertInstanceHostId; } + public Long getImportInstanceHostId() { + return importInstanceHostId; + } + public Long getConvertStoragePoolId() { return convertStoragePoolId; } 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 3e8a0571e12d..2ed11a680e2f 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1570,6 +1570,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster String clusterName = cmd.getClusterName(); String sourceHostName = cmd.getHostIp(); Long convertInstanceHostId = cmd.getConvertInstanceHostId(); + Long importInstanceHostId = cmd.getImportInstanceHostId(); Long convertStoragePoolId = cmd.getConvertStoragePoolId(); if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) { @@ -1599,8 +1600,8 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster DataStoreTO temporaryConvertLocation = null; String ovfTemplateOnConvertLocation = null; try { - HostVO convertHost = selectInstanceConversionKVMHostInCluster(destinationCluster, convertInstanceHostId); - HostVO importHost = convertInstanceHostId == null ? convertHost : selectInstanceConversionKVMHostInCluster(destinationCluster, null); + HostVO convertHost = selectKVMHostForConversionInCluster(destinationCluster, convertInstanceHostId); + HostVO importHost = selectKVMHostForImportingInCluster(destinationCluster, importInstanceHostId); CheckConvertInstanceAnswer conversionSupportAnswer = checkConversionSupportOnHost(convertHost, sourceVMName, false); LOGGER.debug(String.format("The host %s (%s) is selected to execute the conversion of the instance %s" + " from VMware to KVM ", convertHost.getId(), convertHost.getName(), sourceVMName)); @@ -1787,7 +1788,41 @@ private Map createParamsForRemoveClonedInstance(String vcenter, return params; } - private HostVO selectInstanceConversionKVMHostInCluster(Cluster destinationCluster, Long convertInstanceHostId) { + private HostVO selectKVMHostForImportingInCluster(Cluster destinationCluster, Long importInstanceHostId) { + if (importInstanceHostId != null) { + HostVO selectedHost = hostDao.findById(importInstanceHostId); + if (selectedHost == null) { + String msg = String.format("Cannot find host with ID %s", importInstanceHostId); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + if (selectedHost.getResourceState() != ResourceState.Enabled || + selectedHost.getStatus() != Status.Up || selectedHost.getType() != Host.Type.Routing || + destinationCluster.getDataCenterId() != selectedHost.getDataCenterId() || + selectedHost.getClusterId() != destinationCluster.getId() + ) { + String msg = String.format( + "Cannot import the converted instance on the host %s as it is not a running and Enabled host", + selectedHost.getName()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + return selectedHost; + } + + List hosts = hostDao.listByClusterAndHypervisorType(destinationCluster.getId(), destinationCluster.getHypervisorType()); + if (CollectionUtils.isNotEmpty(hosts)) { + return hosts.get(new Random().nextInt(hosts.size())); + } + + String err = String.format( + "Could not find any suitable %s host in cluster %s to import the converted instance", + destinationCluster.getHypervisorType(), destinationCluster.getName()); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + private HostVO selectKVMHostForConversionInCluster(Cluster destinationCluster, Long convertInstanceHostId) { if (convertInstanceHostId != null) { HostVO selectedHost = hostDao.findById(convertInstanceHostId); if (selectedHost == null) { @@ -1795,9 +1830,12 @@ private HostVO selectInstanceConversionKVMHostInCluster(Cluster destinationClust LOGGER.error(msg); throw new CloudRuntimeException(msg); } - if (selectedHost.getResourceState() != ResourceState.Enabled || - selectedHost.getStatus() != Status.Up || selectedHost.getType() != Host.Type.Routing) { - String msg = String.format("Cannot perform the conversion on the host %s as it is not a running and Enabled host", selectedHost.getName()); + + if (!List.of(ResourceState.Enabled, ResourceState.Disabled).contains(selectedHost.getResourceState()) || + selectedHost.getStatus() != Status.Up || selectedHost.getType() != Host.Type.Routing || + destinationCluster.getDataCenterId() != selectedHost.getDataCenterId() + ) { + String msg = String.format("Cannot perform the conversion on the host %s as it is not running", selectedHost.getName()); LOGGER.error(msg); throw new CloudRuntimeException(msg); } 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 76754bcc78a7..b5aa88f3c02f 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -632,11 +632,13 @@ private void baseTestImportVmFromVmwareToKvm(VcenterParameter vcenterParameter, when(convertHost.getId()).thenReturn(convertHostId); when(convertHost.getName()).thenReturn("KVM-Convert-Host"); when(convertHost.getType()).thenReturn(Host.Type.Routing); + when(convertHost.getDataCenterId()).thenReturn(zoneId); + when(convertHost.getClusterId()).thenReturn(clusterId); if (selectConvertHost) { when(importVmCmd.getConvertInstanceHostId()).thenReturn(convertHostId); + when(importVmCmd.getImportInstanceHostId()).thenReturn(convertHostId); when(hostDao.findById(convertHostId)).thenReturn(convertHost); } - when(hostDao.listByClusterHypervisorTypeAndHostCapability(clusterId, Hypervisor.HypervisorType.KVM, Host.HOST_INSTANCE_CONVERSION)).thenReturn(List.of(convertHost)); DataStoreTO dataStoreTO = mock(DataStoreTO.class); DataStore dataStore = mock(DataStore.class); diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index 23b1e6ab5f57..bb0e04b8807c 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -164,6 +164,18 @@ @handle-checkselectpair-change="updateSelectedKvmHostForConversion" /> + + + 1) { this.updateSelectedRootDisk() } @@ -930,6 +945,17 @@ export default { }) }) }, + fetchKvmHostsForImporting () { + api('listHosts', { + clusterid: this.cluster.id, + hypervisor: this.cluster.hypervisortype, + type: 'Routing', + state: 'Up', + resourcestate: 'Enabled' + }).then(json => { + this.kvmHostsForImporting = json.listhostsresponse.host || [] + }) + }, fetchStoragePoolsForConversion () { if (this.selectedStorageOptionForConversion === 'primary') { const params = { @@ -956,6 +982,14 @@ export default { }) } }, + updateSelectedKvmHostForImporting (clusterid, checked, value) { + if (checked) { + this.selectedKvmHostForImporting = value + } else { + this.selectedKvmHostForImporting = null + this.resetStorageOptionsForConversion() + } + }, updateSelectedKvmHostForConversion (clusterid, checked, value) { if (checked) { this.selectedKvmHostForConversion = value