This is an automated email from the ASF dual-hosted git repository.
winterhazel pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new ec2d3ea1e6d Fix `findHostsForMigration` never returning hosts from
other clusters (#13452)
ec2d3ea1e6d is described below
commit ec2d3ea1e6d186964760c8d2ab33d3164715e1b5
Author: Fabricio Duarte <[email protected]>
AuthorDate: Fri Jul 10 13:22:58 2026 -0300
Fix `findHostsForMigration` never returning hosts from other clusters
(#13452)
---
.../com/cloud/server/ManagementServerImpl.java | 69 +++++++++++++---------
.../com/cloud/server/ManagementServerImplTest.java | 61 ++++++++++++++-----
2 files changed, 86 insertions(+), 44 deletions(-)
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index bd4c311e3cd..1fc92ad0e8e 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -1469,6 +1469,7 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
*/
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>,
Map<Host, Boolean>> getTechnicallyCompatibleHosts(
final VirtualMachine vm,
+ final Host srcHost,
final Long startIndex,
final Long pageSize,
final String keyword) {
@@ -1479,31 +1480,6 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
return new Ternary<>(new Pair<>(new ArrayList<>(), 0), new
ArrayList<>(), new HashMap<>());
}
- final long srcHostId = vm.getHostId();
- final Host srcHost = _hostDao.findById(srcHostId);
- if (srcHost == null) {
- if (logger.isDebugEnabled()) {
- logger.debug("Unable to find the host with ID: " + srcHostId +
" of this Instance: " + vm);
- }
- final InvalidParameterValueException ex = new
InvalidParameterValueException("Unable to find the host (with specified ID) of
instance with specified ID");
- ex.addProxyObject(String.valueOf(srcHostId), "hostId");
- ex.addProxyObject(vm.getUuid(), "vmId");
- throw ex;
- }
-
- String srcHostVersion = srcHost.getHypervisorVersion();
- if (HypervisorType.KVM.equals(srcHost.getHypervisorType()) &&
srcHostVersion == null) {
- srcHostVersion = "";
- }
-
- // Check if the vm can be migrated with storage.
- boolean canMigrateWithStorage = false;
-
- List<HypervisorType> hypervisorTypes = Arrays.asList(new
HypervisorType[]{HypervisorType.VMware, HypervisorType.KVM});
- if (VirtualMachine.Type.User.equals(vm.getType()) ||
hypervisorTypes.contains(vm.getHypervisorType())) {
- canMigrateWithStorage =
_hypervisorCapabilitiesDao.isStorageMotionSupported(srcHost.getHypervisorType(),
srcHostVersion);
- }
-
// Check if the vm is using any disks on local storage.
final VirtualMachineProfile vmProfile = new
VirtualMachineProfileImpl(vm, null, _offeringDao.findById(vm.getId(),
vm.getServiceOfferingId()), null, null);
final List<VolumeVO> volumes =
_volumeDao.findCreatedByInstance(vmProfile.getId());
@@ -1517,10 +1493,12 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
}
}
+ boolean canMigrateWithStorage = isStorageMigrationSupported(vm,
srcHost);
if (!canMigrateWithStorage && usesLocal) {
throw new InvalidParameterValueException("Unsupported operation,
instance uses Local storage, cannot migrate");
}
+ final String srcHostVersion = getHypervisorVersionOfHost(srcHost);
final Type hostType = srcHost.getType();
Pair<List<HostVO>, Integer> allHostsPair = null;
List<HostVO> allHosts = null;
@@ -1596,6 +1574,23 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
return new Ternary<>(allHostsPairResult, filteredHosts,
requiresStorageMotion);
}
+ protected boolean isStorageMigrationSupported(final VirtualMachine vm,
final Host srcHost) {
+ final List<HypervisorType> hypervisorTypes =
Arrays.asList(HypervisorType.VMware, HypervisorType.KVM);
+ if (VirtualMachine.Type.User.equals(vm.getType()) ||
hypervisorTypes.contains(vm.getHypervisorType())) {
+ final String srcHostVersion = getHypervisorVersionOfHost(srcHost);
+ return
_hypervisorCapabilitiesDao.isStorageMotionSupported(srcHost.getHypervisorType(),
srcHostVersion);
+ }
+ return false;
+ }
+
+ protected String getHypervisorVersionOfHost(final Host host) {
+ final String version = host.getHypervisorVersion();
+ if (version == null &&
HypervisorType.KVM.equals(host.getHypervisorType())) {
+ return "";
+ }
+ return version;
+ }
+
/**
* Apply affinity group constraints and other exclusion rules for VM
migration.
* This builds an ExcludeList based on affinity groups, DPDK requirements,
and dedicated resources.
@@ -1692,9 +1687,19 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
validateVmForHostMigration(vm);
+ final long srcHostId = vm.getHostId();
+ final Host srcHost = _hostDao.findById(srcHostId);
+ if (srcHost == null) {
+ logger.debug("Unable to find the host with ID: {} of this
Instance: {}", srcHostId, vm);
+ final InvalidParameterValueException ex = new
InvalidParameterValueException("Unable to find the host (with specified ID) of
instance with specified ID");
+ ex.addProxyObject(String.valueOf(srcHostId), "hostId");
+ ex.addProxyObject(vm.getUuid(), "vmId");
+ throw ex;
+ }
+
// Get technically compatible hosts (storage, hypervisor, UEFI)
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>,
Map<Host, Boolean>> compatibilityResult =
- getTechnicallyCompatibleHosts(vm, startIndex, pageSize, keyword);
+ getTechnicallyCompatibleHosts(vm, srcHost, startIndex, pageSize,
keyword);
Pair<List<? extends Host>, Integer> allHostsPair =
compatibilityResult.first();
List<? extends Host> filteredHosts = compatibilityResult.second();
@@ -1707,9 +1712,7 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
}
// Create deployment plan and VM profile
- final Host srcHost = _hostDao.findById(vm.getHostId());
- final DataCenterDeployment plan = new DataCenterDeployment(
- srcHost.getDataCenterId(), srcHost.getPodId(),
srcHost.getClusterId(), null, null, null);
+ final DataCenterDeployment plan =
createDeploymentPlanForMigrationListing(vm, srcHost);
final VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(
vm, null, _offeringDao.findById(vm.getId(),
vm.getServiceOfferingId()), null, null);
@@ -1723,6 +1726,14 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
return new Ternary<>(otherHosts, suitableHosts, requiresStorageMotion);
}
+ protected DataCenterDeployment
createDeploymentPlanForMigrationListing(final VirtualMachine vm, final Host
srcHost) {
+ final boolean canMigrateWithStorage = isStorageMigrationSupported(vm,
srcHost);
+ if (canMigrateWithStorage) {
+ return new DataCenterDeployment(srcHost.getDataCenterId(),
srcHost.getPodId(), null, null, null, null);
+ }
+ return new DataCenterDeployment(srcHost.getDataCenterId(),
srcHost.getPodId(), srcHost.getClusterId(), null, null, null);
+ }
+
/**
* Add non DPDK enabled hosts to the avoid list
*/
diff --git
a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
index da2005f6136..2f3e97716a0 100644
--- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
+++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
@@ -20,6 +20,7 @@ import com.cloud.api.ApiDBUtils;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.Vlan.VlanType;
import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.deploy.DataCenterDeployment;
import com.cloud.deploy.DeploymentPlanningManager;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
@@ -227,7 +228,7 @@ public class ManagementServerImplTest {
apiDBUtilsMock = Mockito.mockStatic(ApiDBUtils.class);
// Return empty list to avoid architecture filtering in most tests
apiDBUtilsMock.when(() ->
ApiDBUtils.listZoneClustersArchs(Mockito.anyLong()))
- .thenReturn(new ArrayList<>());
+ .thenReturn(new ArrayList<>());
}
@After
@@ -246,7 +247,7 @@ public class ManagementServerImplTest {
}
@Test(expected = InvalidParameterValueException.class)
- public void testDuplicateRegistraitons(){
+ public void testDuplicateRegistrations() {
String accountName = "account";
String publicKeyString = "ssh-rsa very public";
String publicKeyMaterial =
spy.getPublicKeyFromKeyKeyMaterial(publicKeyString);
@@ -826,9 +827,13 @@ public class ManagementServerImplTest {
@Test
public void testListHostsForMigrationOfVMGpuEnabled() {
VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+ long hostId = vm.getHostId();
+ HostVO srcHost = mockHost(hostId, 4L, 5L, 6L, HypervisorType.KVM);
Account caller = mockRootAdminAccount();
+
Mockito.doReturn(caller).when(spy).getCaller();
Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
+ Mockito.doReturn(srcHost).when(hostDao).findById(hostId);
// Mock GPU detail
Mockito.when(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(),
GPU.Keys.pciDevice.toString()))
@@ -888,7 +893,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify storage motion capability was checked
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.VMware,
null);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
// Verify result structure and data
Assert.assertNotNull(result);
@@ -952,7 +957,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify hypervisor capabilities were checked
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.KVM,
"");
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
// Verify result contains expected hosts
Assert.assertNotNull(result);
@@ -1097,7 +1102,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify KVM null version was converted to empty string
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.KVM,
"");
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
// Verify result data
Assert.assertNotNull(result);
@@ -1416,7 +1421,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify storage motion capability was checked for User VM
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.VMware,
null);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
// Verify response data
Assert.assertNotNull(result);
@@ -1481,7 +1486,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify hypervisor is in supported hypervisors list
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(hypervisorType,
version);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(hypervisorType, version);
// Verify validation passed for this hypervisor
Assert.assertNotNull("Result should not be null for " +
hypervisorType, result);
@@ -1508,8 +1513,6 @@ public class ManagementServerImplTest {
Account caller = mockRootAdminAccount();
Mockito.doReturn(caller).when(spy).getCaller();
Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
-
Mockito.when(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(),
GPU.Keys.pciDevice.toString()))
- .thenReturn(null);
Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(null);
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
@@ -1589,7 +1592,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify that storage motion capability was checked for system VM
(VMware is in hypervisorTypes list)
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.VMware,
null);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
// Verify response structure
Assert.assertNotNull(result);
@@ -1642,7 +1645,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify User VM can migrate with storage (User VM type always checks)
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.KVM,
"");
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
// Verify response data
Assert.assertNotNull(result);
@@ -1695,7 +1698,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify XenServer without storage motion was checked
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.XenServer,
null);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.XenServer, null);
// Verify cluster-scoped search was used (not zone-wide)
Mockito.verify(spy).searchForServers(
Mockito.eq(0L), Mockito.eq(20L), Mockito.isNull(),
Mockito.any(Type.class),
@@ -1845,14 +1848,14 @@ public class ManagementServerImplTest {
Mockito.doReturn(caller).when(spy).getCaller();
Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
Mockito.when(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(),
GPU.Keys.pciDevice.toString()))
- .thenReturn(null);
+ .thenReturn(null);
HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.VMware);
Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
// VMware with DomainRouter should still check storage motion
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.VMware,
null))
- .thenReturn(true);
+ .thenReturn(true);
ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
Mockito.when(offeringDao.findById(vm.getId(),
vm.getServiceOfferingId())).thenReturn(offering);
@@ -1880,7 +1883,7 @@ public class ManagementServerImplTest {
spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
// Verify VMware always checks storage motion (hypervisorTypes list
includes VMware)
-
Mockito.verify(hypervisorCapabilitiesDao).isStorageMotionSupported(HypervisorType.VMware,
null);
+ Mockito.verify(hypervisorCapabilitiesDao,
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
// Verify response
Assert.assertNotNull(result);
@@ -2074,4 +2077,32 @@ public class ManagementServerImplTest {
Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false);
return diskOffering;
}
+
+ @Test
+ public void
createDeploymentPlanForMigrationListingTestAllocatesInAnyClusterWhenStorageMigrationIsSupported()
{
+ VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+ HostVO srcHost = mockHost(vm.getHostId(), 1L, 2L, 3L,
HypervisorType.KVM);
+
+ Mockito.doReturn(true).when(spy).isStorageMigrationSupported(vm,
srcHost);
+
+ DataCenterDeployment deploymentPlan =
spy.createDeploymentPlanForMigrationListing(vm, srcHost);
+
+ Assert.assertEquals(3L, deploymentPlan.getDataCenterId());
+ Assert.assertEquals(2L, (long) deploymentPlan.getPodId());
+ Assert.assertNull(deploymentPlan.getClusterId());
+ }
+
+ @Test
+ public void
createDeploymentPlanForMigrationListingTestAllocatesInSourceClusterWhenStorageMigrationIsNotSupported()
{
+ VMInstanceVO vm = mockRunningVM(1L, HypervisorType.XenServer);
+ HostVO srcHost = mockHost(vm.getHostId(), 4L, 5L, 6L,
HypervisorType.XenServer);
+
+ Mockito.doReturn(false).when(spy).isStorageMigrationSupported(vm,
srcHost);
+
+ DataCenterDeployment deploymentPlan =
spy.createDeploymentPlanForMigrationListing(vm, srcHost);
+
+ Assert.assertEquals(6L, deploymentPlan.getDataCenterId());
+ Assert.assertEquals(5L, (long) deploymentPlan.getPodId());
+ Assert.assertEquals(4L, (long) deploymentPlan.getClusterId());
+ }
}