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 846eab599fd1..11ec2467a35c 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1679,6 +1679,7 @@ protected UserVm importUnmanagedInstanceFromVmwareToKvm(DataCenter zone, Cluster ApiConstants.FORCE_MS_TO_IMPORT_VM_FILES, ApiConstants.USE_VDDK)); } + convertStoragePoolId = resolveImplicitConversionStoragePoolId(destinationCluster, convertStoragePoolId, forceConvertToPool); checkConversionStoragePool(convertStoragePoolId, forceConvertToPool); validateSelectedConversionStoragePoolForVddk(useVddk, convertStoragePoolId, serviceOffering, dataDiskOfferingMap); @@ -1816,6 +1817,45 @@ protected void checkConversionStoragePool(Long convertStoragePoolId, boolean for } } + protected Long resolveImplicitConversionStoragePoolId(Cluster destinationCluster, Long convertStoragePoolId, + boolean forceConvertToPool) { + if (!forceConvertToPool || convertStoragePoolId != null) { + return convertStoragePoolId; + } + + List candidatePools = getImplicitForceConvertToPoolStoragePools(destinationCluster); + if (candidatePools.isEmpty()) { + logFailureAndThrowException(String.format("The parameter forceconverttopool is set to true, but no suitable primary storage pool was found in cluster %s", + destinationCluster.getName())); + } + if (candidatePools.size() > 1) { + logFailureAndThrowException(String.format("The parameter forceconverttopool is set to true, but multiple suitable primary storage pools were found in cluster %s. Please provide convertinstancepoolid.", + destinationCluster.getName())); + } + + StoragePoolVO implicitPool = candidatePools.get(0); + logger.debug("The parameter forceconverttopool is set to true and no conversion storage pool was provided; using the only suitable primary storage pool [{}].", + implicitPool.getName()); + return implicitPool.getId(); + } + + private List getImplicitForceConvertToPoolStoragePools(Cluster destinationCluster) { + Set pools = new HashSet<>(); + for (Storage.StoragePoolType poolType : forceConvertToPoolAllowedTypes) { + List clusterPools = primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getId(), + Hypervisor.HypervisorType.KVM, poolType); + if (clusterPools != null) { + pools.addAll(clusterPools); + } + List zonePools = primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(destinationCluster.getDataCenterId(), + Hypervisor.HypervisorType.KVM, poolType); + if (zonePools != null) { + pools.addAll(zonePools); + } + } + return new ArrayList<>(pools); + } + protected void validateSelectedConversionStoragePoolForVddk(boolean useVddk, Long convertStoragePoolId, ServiceOfferingVO serviceOffering, Map dataDiskOfferingMap) { if (!useVddk || convertStoragePoolId == null) { 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 bee6c4ad257f..904a785e1794 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -1405,6 +1405,46 @@ public void testCheckConversionStoragePoolTemporarySecondaryStorageForceConvertT unmanagedVMsManager.checkConversionStoragePool(null, true); } + @Test + public void testResolveImplicitConversionStoragePoolIdUsesSingleSuitablePoolWhenForceConvertToPool() { + ClusterVO cluster = getClusterForTests(); + StoragePoolVO storagePool = mock(StoragePoolVO.class); + when(storagePool.getId()).thenReturn(42L); + when(primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(cluster.getId(), + Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem)) + .thenReturn(List.of(storagePool)); + + Long storagePoolId = unmanagedVMsManager.resolveImplicitConversionStoragePoolId(cluster, null, true); + + Assert.assertEquals(Long.valueOf(42L), storagePoolId); + } + + @Test + public void testResolveImplicitConversionStoragePoolIdKeepsExplicitPoolWhenForceConvertToPool() { + ClusterVO cluster = getClusterForTests(); + + Long storagePoolId = unmanagedVMsManager.resolveImplicitConversionStoragePoolId(cluster, 42L, true); + + Assert.assertEquals(Long.valueOf(42L), storagePoolId); + Mockito.verifyNoInteractions(primaryDataStoreDao); + } + + @Test(expected = CloudRuntimeException.class) + public void testResolveImplicitConversionStoragePoolIdFailsWhenMultiplePoolsFound() { + ClusterVO cluster = getClusterForTests(); + when(cluster.getName()).thenReturn("cluster-1"); + StoragePoolVO firstStoragePool = mock(StoragePoolVO.class); + StoragePoolVO secondStoragePool = mock(StoragePoolVO.class); + when(primaryDataStoreDao.findClusterWideStoragePoolsByHypervisorAndPoolType(cluster.getId(), + Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem)) + .thenReturn(List.of(firstStoragePool)); + when(primaryDataStoreDao.findZoneWideStoragePoolsByHypervisorAndPoolType(cluster.getDataCenterId(), + Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem)) + .thenReturn(List.of(secondStoragePool)); + + unmanagedVMsManager.resolveImplicitConversionStoragePoolId(cluster, null, true); + } + @Test public void testCheckConversionStoragePoolPrimaryStagingPool() { StoragePoolVO destPool = mock(StoragePoolVO.class); diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue index ffa0d9344335..4e4a54e73e1f 100644 --- a/ui/src/views/tools/ImportUnmanagedInstance.vue +++ b/ui/src/views/tools/ImportUnmanagedInstance.vue @@ -1085,11 +1085,7 @@ export default { } getAPI('listStoragePools', params).then(json => { this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || [] - // Keep selected pool state aligned when the value is auto-populated by v-model. - if (this.form.convertstoragepoolid) { - const poolExists = this.storagePoolsForConversion.some(pool => pool.id === this.form.convertstoragepoolid) - this.selectedStoragePoolForConversion = poolExists ? this.form.convertstoragepoolid : null - } + this.syncSelectedStoragePoolForConversion() }) } else if (this.selectedStorageOptionForConversion === 'local') { const kvmHost = this.kvmHostsForConversion.filter(x => x.id === this.selectedKvmHostForConversion)[0] @@ -1099,13 +1095,25 @@ export default { status: 'Up' }).then(json => { this.storagePoolsForConversion = json.liststoragepoolsresponse.storagepool || [] - if (this.form.convertstoragepoolid) { - const poolExists = this.storagePoolsForConversion.some(pool => pool.id === this.form.convertstoragepoolid) - this.selectedStoragePoolForConversion = poolExists ? this.form.convertstoragepoolid : null - } + this.syncSelectedStoragePoolForConversion() }) } }, + syncSelectedStoragePoolForConversion () { + if (this.form.convertstoragepoolid) { + const poolExists = this.storagePoolsForConversion.some(pool => pool.id === this.form.convertstoragepoolid) + if (poolExists) { + this.selectedStoragePoolForConversion = this.form.convertstoragepoolid + return + } + this.form.convertstoragepoolid = null + this.selectedStoragePoolForConversion = null + } + if (this.storagePoolsForConversion.length === 1) { + this.form.convertstoragepoolid = this.storagePoolsForConversion[0].id + this.selectedStoragePoolForConversion = this.form.convertstoragepoolid + } + }, updateSelectedKvmHostForImporting (clusterid, checked, value) { if (checked) { this.selectedKvmHostForImporting = value @@ -1162,6 +1170,9 @@ export default { this.selectedStoragePoolForConversion = null this.showStoragePoolsForConversion = false this.resetStorageOptionsForConversion() + if (val) { + this.selectPrimaryStorageForForcedConversion() + } }, onUseVddkChange (val, isUserChange = true) { if (isUserChange) { @@ -1186,6 +1197,14 @@ export default { this.fetchKvmHostsForConversion() } this.resetStorageOptionsForConversion() + if (val) { + this.selectPrimaryStorageForForcedConversion() + } + }, + selectPrimaryStorageForForcedConversion () { + this.selectedStorageOptionForConversion = 'primary' + this.showStoragePoolsForConversion = true + this.fetchStoragePoolsForConversion() }, updateSelectedRootDisk () { var rootDisk = this.resource.disk[this.selectedRootDiskIndex]