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 bd3a5655e91..13919b04f61 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -538,7 +538,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return nicIpAddresses; } - private StoragePool getStoragePool(final UnmanagedInstanceTO.Disk disk, final DataCenter zone, final Cluster cluster) { + private StoragePool getStoragePool(final UnmanagedInstanceTO.Disk disk, final DataCenter zone, final Cluster cluster, String diskOfferingTags) { StoragePool storagePool = null; final String dsHost = disk.getDatastoreHost(); final String dsPath = disk.getDatastorePath(); @@ -548,7 +548,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { List pools = primaryDataStoreDao.listPoolByHostPath(dsHost, dsPath); for (StoragePool pool : pools) { if (pool.getDataCenterId() == zone.getId() && - (pool.getClusterId() == null || pool.getClusterId().equals(cluster.getId()))) { + (pool.getClusterId() == null || pool.getClusterId().equals(cluster.getId())) && + volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOfferingTags)) { storagePool = pool; break; } @@ -560,7 +561,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { pools.addAll(primaryDataStoreDao.listByDataCenterId(zone.getId())); for (StoragePool pool : pools) { String searchPoolParam = StringUtils.isNotBlank(dsPath) ? dsPath : dsName; - if (StringUtils.contains(pool.getPath(), searchPoolParam)) { + if (StringUtils.contains(pool.getPath(), searchPoolParam) && + volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOfferingTags)) { storagePool = pool; break; } @@ -623,7 +625,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (diskOffering != null && !diskOffering.isCustomized() && diskOffering.getDiskSize() < disk.getCapacity()) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Size of disk offering(ID: %s) %dGB is found less than the size of disk(ID: %s) %dGB during VM import", diskOffering.getUuid(), (diskOffering.getDiskSize() / Resource.ResourceType.bytesToGiB), disk.getDiskId(), (disk.getCapacity() / (Resource.ResourceType.bytesToGiB)))); } - StoragePool storagePool = getStoragePool(disk, zone, cluster); + diskOffering = diskOffering != null ? diskOffering : diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + StoragePool storagePool = getStoragePool(disk, zone, cluster, diskOffering != null ? diskOffering.getTags() : null); if (diskOffering != null && !migrateAllowed && !storagePoolSupportsDiskOffering(storagePool, diskOffering)) { throw new InvalidParameterValueException(String.format("Disk offering: %s is not compatible with storage pool: %s of unmanaged disk: %s", diskOffering.getUuid(), storagePool.getUuid(), disk.getDiskId())); } @@ -860,7 +863,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { diskInfo.setDiskChain(new String[]{disk.getImagePath()}); chainInfo = gson.toJson(diskInfo); } - StoragePool storagePool = getStoragePool(disk, zone, cluster); + StoragePool storagePool = getStoragePool(disk, zone, cluster, diskOffering != null ? diskOffering.getTags() : null); DiskProfile profile = volumeManager.importVolume(type, name, diskOffering, diskSize, minIops, maxIops, vm.getDataCenterId(), vm.getHypervisorType(), vm, template, owner, deviceId, storagePool.getId(), path, chainInfo); @@ -1645,7 +1648,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { temporaryConvertLocation = selectInstanceConversionTemporaryLocation( destinationCluster, convertHost, convertStoragePoolId); - List convertStoragePools = findInstanceConversionStoragePoolsInCluster(destinationCluster); + List convertStoragePools = findInstanceConversionStoragePoolsInCluster(destinationCluster, serviceOffering, dataDiskOfferingMap); long importStartTime = System.currentTimeMillis(); Pair sourceInstanceDetails = getSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, clusterName, sourceHostName, sourceVMName); sourceVMwareInstance = sourceInstanceDetails.first(); @@ -1661,15 +1664,18 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { if (cmd.getForceMsToImportVmFiles() || !conversionSupportAnswer.isOvfExportSupported()) { // Uses MS for OVF export to temporary conversion location int noOfThreads = UnmanagedVMsManager.ThreadsOnMSToImportVMwareVMFiles.value(); - ovfTemplateOnConvertLocation = createOvfTemplateOfSourceVmwareUnmanagedInstance(vcenter, datacenterName, username, password, - clusterName, sourceHostName, sourceVMwareInstance.getName(), temporaryConvertLocation, noOfThreads); + ovfTemplateOnConvertLocation = createOvfTemplateOfSourceVmwareUnmanagedInstance( + vcenter, datacenterName, username, password, clusterName, sourceHostName, + sourceVMwareInstance.getName(), temporaryConvertLocation, noOfThreads); convertedInstance = convertVmwareInstanceToKVMWithOVFOnConvertLocation(sourceVMName, - sourceVMwareInstance, convertHost, importHost, convertStoragePools, - temporaryConvertLocation, ovfTemplateOnConvertLocation); + sourceVMwareInstance, convertHost, importHost, convertStoragePools, + serviceOffering, dataDiskOfferingMap, temporaryConvertLocation, + ovfTemplateOnConvertLocation); } else { // Uses KVM Host for OVF export to temporary conversion location, through ovftool convertedInstance = convertVmwareInstanceToKVMAfterExportingOVFToConvertLocation( - sourceVMName, sourceVMwareInstance, convertHost, importHost, convertStoragePools, + sourceVMName, sourceVMwareInstance, convertHost, importHost, + convertStoragePools, serviceOffering, dataDiskOfferingMap, temporaryConvertLocation, vcenter, username, password, datacenterName); } @@ -1759,9 +1765,9 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { convertedInstance.setPowerState(UnmanagedInstanceTO.PowerState.PowerOff); List convertedInstanceDisks = convertedInstance.getDisks(); List sourceVMwareInstanceDisks = sourceVMwareInstance.getDisks(); - for (int i = 0; i < convertedInstanceDisks.size(); i++) { - UnmanagedInstanceTO.Disk disk = convertedInstanceDisks.get(i); - disk.setDiskId(sourceVMwareInstanceDisks.get(i).getDiskId()); + for (UnmanagedInstanceTO.Disk sourceVMwareInstanceDisk : sourceVMwareInstanceDisks) { + UnmanagedInstanceTO.Disk convertedDisk = convertedInstanceDisks.get(sourceVMwareInstanceDisk.getPosition()); + convertedDisk.setDiskId(sourceVMwareInstanceDisk.getDiskId()); } List convertedInstanceNics = convertedInstance.getNics(); List sourceVMwareInstanceNics = sourceVMwareInstance.getNics(); @@ -1945,16 +1951,16 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } private UnmanagedInstanceTO convertVmwareInstanceToKVMWithOVFOnConvertLocation( - String sourceVM, UnmanagedInstanceTO sourceVMwareInstance, - HostVO convertHost, HostVO importHost, - List convertStoragePools, DataStoreTO temporaryConvertLocation, - String ovfTemplateDirConvertLocation + String sourceVM, UnmanagedInstanceTO sourceVMwareInstance, HostVO convertHost, + HostVO importHost, List convertStoragePools, + ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap, + DataStoreTO temporaryConvertLocation, String ovfTemplateDirConvertLocation ) { logger.debug(String.format("Delegating the conversion of instance %s from VMware to KVM to the host %s (%s) using OVF %s on conversion datastore", sourceVM, convertHost.getId(), convertHost.getName(), ovfTemplateDirConvertLocation)); RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(sourceVM); - List destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks()); + List destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks(), serviceOffering, dataDiskOfferingMap); ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM, destinationStoragePools, temporaryConvertLocation, ovfTemplateDirConvertLocation, false, false); int timeoutSeconds = UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60; @@ -1980,16 +1986,17 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } private UnmanagedInstanceTO convertVmwareInstanceToKVMAfterExportingOVFToConvertLocation( - String sourceVM, UnmanagedInstanceTO sourceVMwareInstance, - HostVO convertHost, HostVO importHost, List convertStoragePools, - DataStoreTO temporaryConvertLocation, String vcenterHost, - String vcenterUsername, String vcenterPassword, String datacenterName + String sourceVM, UnmanagedInstanceTO sourceVMwareInstance, HostVO convertHost, + HostVO importHost, List convertStoragePools, + ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap, + DataStoreTO temporaryConvertLocation, String vcenterHost, String vcenterUsername, + String vcenterPassword, String datacenterName ) { logger.debug(String.format("Delegating the conversion of instance %s from VMware to KVM to the host %s (%s) after OVF export through ovftool", sourceVM, convertHost.getId(), convertHost.getName())); RemoteInstanceTO remoteInstanceTO = new RemoteInstanceTO(sourceVMwareInstance.getName(), vcenterHost, vcenterUsername, vcenterPassword, datacenterName); - List destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks()); + List destinationStoragePools = selectInstanceConversionStoragePools(convertStoragePools, sourceVMwareInstance.getDisks(), serviceOffering, dataDiskOfferingMap); ConvertInstanceCommand cmd = new ConvertInstanceCommand(remoteInstanceTO, Hypervisor.HypervisorType.KVM, destinationStoragePools, temporaryConvertLocation, null, false, true); int timeoutSeconds = UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.value() * 60 * 60; @@ -2052,12 +2059,31 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return ((ImportConvertedInstanceAnswer) importAnswer).getConvertedInstance(); } - private List findInstanceConversionStoragePoolsInCluster(Cluster destinationCluster) { + private List findInstanceConversionStoragePoolsInCluster( + Cluster destinationCluster, ServiceOfferingVO serviceOffering, + Map dataDiskOfferingMap + ) { List pools = new ArrayList<>(); - List clusterPools = primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem); - pools.addAll(clusterPools); - List zonePools = primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getDataCenterId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem); - pools.addAll(zonePools); + pools.addAll(primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem)); + pools.addAll(primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getDataCenterId(), Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem)); + List diskOfferingTags = new ArrayList<>(); + for (Long diskOfferingId : dataDiskOfferingMap.values()) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + if (diskOffering == null) { + String msg = String.format("Cannot find disk offering with ID %s", diskOfferingId); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + diskOfferingTags.add(diskOffering.getTags()); + } + if (serviceOffering.getDiskOfferingId() != null) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + if (diskOffering != null) { + diskOfferingTags.add(diskOffering.getTags()); + } + } + + pools = getPoolsWithMatchingTags(pools, diskOfferingTags); if (pools.isEmpty()) { String msg = String.format("Cannot find suitable storage pools in cluster %s for the conversion", destinationCluster.getName()); logger.error(msg); @@ -2066,12 +2092,54 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return pools; } - private List selectInstanceConversionStoragePools(List pools, List disks) { + private List getPoolsWithMatchingTags(List pools, List diskOfferingTags) { + if (diskOfferingTags.isEmpty()) { + return pools; + } + List poolsSupportingTags = new ArrayList<>(pools); + for (String tags : diskOfferingTags) { + boolean tagsMatched = false; + for (StoragePoolVO pool : pools) { + if (volumeApiService.doesTargetStorageSupportDiskOffering(pool, tags)) { + poolsSupportingTags.add(pool); + tagsMatched = true; + } + } + if (!tagsMatched) { + String msg = String.format("Cannot find suitable storage pools for the conversion with disk offering tags %s", tags); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + } + return poolsSupportingTags; + } + + private List selectInstanceConversionStoragePools( + List pools, List disks, + ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap + ) { List storagePools = new ArrayList<>(disks.size()); - //TODO: Choose pools by capacity + for (int i = 0; i < disks.size(); i++) { + storagePools.add(null); + } + Set dataDiskIds = dataDiskOfferingMap.keySet(); for (UnmanagedInstanceTO.Disk disk : disks) { - Long capacity = disk.getCapacity(); - storagePools.add(pools.get(0).getUuid()); + Long diskOfferingId = dataDiskOfferingMap.get(disk.getDiskId()); + if (diskOfferingId == null && !dataDiskIds.contains(disk.getDiskId())) { + diskOfferingId = serviceOffering.getDiskOfferingId(); + } + //TODO: Choose pools by capacity + if (diskOfferingId == null) { + storagePools.set(disk.getPosition(), pools.get(0).getUuid()); + } else { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + for (StoragePoolVO pool : pools) { + if (volumeApiService.doesTargetStorageSupportDiskOffering(pool, diskOffering.getTags())) { + storagePools.set(disk.getPosition(), pool.getUuid()); + break; + } + } + } } return storagePools; } 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 d80bbffaaf4..9205e8cb2c7 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -279,6 +279,7 @@ public class UnmanagedVMsManagerImplTest { List instanceDisks = new ArrayList<>(); UnmanagedInstanceTO.Disk instanceDisk = new UnmanagedInstanceTO.Disk(); instanceDisk.setDiskId("1000-1"); + instanceDisk.setPosition(0); instanceDisk.setLabel("DiskLabel"); instanceDisk.setController("scsi"); instanceDisk.setImagePath("[b6ccf44a1fa13e29b3667b4954fa10ee] TestInstance/ROOT-1.vmdk"); @@ -424,6 +425,7 @@ public class UnmanagedVMsManagerImplTest { ImportUnmanagedInstanceCmd importUnmanageInstanceCmd = Mockito.mock(ImportUnmanagedInstanceCmd.class); when(importUnmanageInstanceCmd.getName()).thenReturn("TestInstance"); when(importUnmanageInstanceCmd.getDomainId()).thenReturn(null); + when(volumeApiService.doesTargetStorageSupportDiskOffering(any(StoragePool.class), any())).thenReturn(true); try (MockedStatic ignored = Mockito.mockStatic(UsageEventUtils.class)) { unmanagedVMsManager.importUnmanagedInstance(importUnmanageInstanceCmd); } @@ -703,6 +705,8 @@ public class UnmanagedVMsManagerImplTest { when(agentManager.send(Mockito.eq(convertHostId), Mockito.any(CheckConvertInstanceCommand.class))).thenReturn(checkConvertInstanceAnswer); } + when(volumeApiService.doesTargetStorageSupportDiskOffering(any(StoragePool.class), any())).thenReturn(true); + ConvertInstanceAnswer convertInstanceAnswer = mock(ConvertInstanceAnswer.class); ImportConvertedInstanceAnswer convertImportedInstanceAnswer = mock(ImportConvertedInstanceAnswer.class); when(convertInstanceAnswer.getConvertedInstance()).thenReturn(instance); diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue b/ui/src/views/infra/zone/ZoneWizardAddResources.vue index ef2cba1c3d9..00811ed3a10 100644 --- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue +++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue @@ -192,6 +192,13 @@ export default { placeHolder: 'message.error.cluster.name', required: true }, + { + title: 'label.arch', + key: 'arch', + required: false, + select: true, + options: this.architectureTypes + }, { title: 'label.vcenter.host', key: 'vCenterHost', @@ -846,6 +853,13 @@ export default { primaryStorageScopes: [], primaryStorageProtocols: [], primaryStorageProviders: [], + architectureTypes: [{ + id: 'x86_64', + description: 'AMD 64 bits (x86_64)' + }, { + id: 'aarch64', + description: 'ARM 64 bits (aarch64)' + }], storageProviders: [], currentStep: null, options: ['primaryStorageScope', 'primaryStorageProtocol', 'provider', 'primaryStorageProvider'] diff --git a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue index e562fb33d72..01006cd0c72 100644 --- a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue +++ b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue @@ -1283,6 +1283,7 @@ export default { if (this.isEdgeZone) { clusterName = 'Cluster-' + this.stepData.zoneReturned.name } + params.arch = this.prefillContent?.arch || null if (hypervisor === 'VMware') { params.username = this.prefillContent?.vCenterUsername || null