This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit fe3df6b660d3757797235cf4cbd3f96716feffbe
Merge: 38b674f4426 17e5947a6d2
Author: Wei Zhou <[email protected]>
AuthorDate: Mon Jul 13 11:03:44 2026 +0200

    Merge branch '4.22' into 'main'

 .../kvm/storage/KVMStorageProcessor.java           |    9 +-
 .../org/apache/cloudstack/utils/qemu/QemuImg.java  |    5 +-
 .../apache/cloudstack/utils/qemu/QemuImgTest.java  |   15 +
 .../adapter/flasharray/FlashArrayAdapter.java      |  184 ++-
 .../com/cloud/server/ManagementServerImpl.java     |   69 +-
 .../com/cloud/server/ManagementServerImplTest.java | 1414 +++++++++++++++++++-
 6 files changed, 1609 insertions(+), 87 deletions(-)

diff --cc server/src/test/java/com/cloud/server/ManagementServerImplTest.java
index e69f927b41e,451e30005cf..1ac03258bee
--- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
+++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
@@@ -185,24 -245,12 +249,27 @@@ public class ManagementServerImplTest 
      @Mock
      HostAllocator hostAllocator;
  
+     @Mock
+     VgpuProfileDao vgpuProfileDao;
+ 
+     @Mock
+     VgpuProfileVO vgpuProfileVO;
+ 
 +    @Mock
 +    VirtualMachine virtualMachineMock;
 +
 +    @Mock
 +    VirtualMachineProfile virtualMachineProfileMock;
 +
 +    @Mock
 +    DataCenterDeployment dataCenterDeploymentMock;
 +
 +    @Mock
 +    DeploymentPlanner.ExcludeList excludeListMock;
 +
 +    @Mock
 +    Host hostMock;
 +
-     @Mock
-     DeploymentPlanningManager deploymentPlanningManagerMock;
- 
      private AutoCloseable closeable;
      private MockedStatic<ApiDBUtils> apiDBUtilsMock;
  
@@@ -1076,19 -1126,1344 +1146,1353 @@@
  
      @Test
      public void testGetExternalVmConsole() {
 -        VirtualMachine virtualMachine = Mockito.mock(VirtualMachine.class);
          Host host = Mockito.mock(Host.class);
 -        Mockito.when(extensionManager.getInstanceConsole(virtualMachine, 
host)).thenReturn(Mockito.mock(com.cloud.agent.api.Answer.class));
 -        Assert.assertNotNull(spy.getExternalVmConsole(virtualMachine, host));
 -        Mockito.verify(extensionManager).getInstanceConsole(virtualMachine, 
host);
 +        Mockito.when(extensionManager.getInstanceConsole(virtualMachineMock, 
host)).thenReturn(Mockito.mock(com.cloud.agent.api.Answer.class));
 +        Assert.assertNotNull(spy.getExternalVmConsole(virtualMachineMock, 
host));
 +        
Mockito.verify(extensionManager).getInstanceConsole(virtualMachineMock, host);
 +    }
 +
 +    @Test
 +    public void 
getCapableSuitableHostsTestHostArchIsNotFilteredWhenNoSuitableHostsAreFound() {
 +        List<Host> compatibleHosts = List.of(hostMock);
 +        Mockito.doReturn(null).when(hostAllocator).allocateTo(Mockito.any(), 
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyList(), 
Mockito.anyInt(), Mockito.anyBoolean());
 +
 +        spy.getCapableSuitableHosts(virtualMachineMock, 
virtualMachineProfileMock, dataCenterDeploymentMock, compatibleHosts, 
excludeListMock, hostMock);
 +
 +        apiDBUtilsMock.verify(() -> 
ApiDBUtils.listZoneClustersArchs(Mockito.anyLong()), Mockito.never());
      }
+ 
+     // ============= Tests for listHostsForMigrationOfVM =============
+ 
+     @Test(expected = PermissionDeniedException.class)
+     public void testListHostsForMigrationOfVMNonRootAdmin() {
+         mockRunningVM(1L, HypervisorType.KVM);
+         Account caller = Mockito.mock(Account.class);
+         Mockito.doReturn(caller).when(spy).getCaller();
+         
Mockito.when(accountManager.isRootAdmin(caller.getId())).thenReturn(false);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMNullVM() {
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(null);
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMNotRunning() {
+         VMInstanceVO vm = mockVM(1L, HypervisorType.KVM, State.Stopped);
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMUnsupportedHypervisor() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.BareMetal);
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMLxcUserVM() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.LXC);
+         Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @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()))
+             
.thenReturn(Mockito.mock(com.cloud.service.ServiceOfferingDetailsVO.class));
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         Assert.assertNotNull(result);
+         Assert.assertEquals(0, result.first().first().size());
+         Assert.assertEquals(Integer.valueOf(0), result.first().second());
+         Assert.assertEquals(0, result.second().size());
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithSystemVM() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.VMware);
+         
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.VMware);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // System VMs can use storage motion
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.VMware,
 null))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.VMware);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.VMware);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         // Verify this doesn't throw exception - system VMs should be 
migratable
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify storage motion capability was checked
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
+ 
+         // Verify result structure and data
+         Assert.assertNotNull(result);
+         Assert.assertNotNull("All hosts list should not be null", 
result.first());
+         Assert.assertNotNull("Suitable hosts list should not be null", 
result.second());
+         Assert.assertNotNull("Storage motion map should not be null", 
result.third());
+ 
+         // Verify all hosts returned (from searchForServers)
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("All hosts list should contain 2 hosts", 2, 
result.first().first().size());
+ 
+         // Verify suitable hosts (from host allocator)
+         Assert.assertEquals("Should return 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Suitable hosts should contain host1",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Suitable hosts should contain host2",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithDomainRouter() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+         // Verify domain router can be migrated
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify hypervisor capabilities were checked
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
+ 
+         // Verify result contains expected hosts
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should return 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Result should contain host 101",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Result should contain host 102",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithMultipleVolumes() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         // Multiple volumes - root and data disk
+         VolumeVO rootVolume = mockVolume(1L, 1L);
+         VolumeVO dataVolume = mockVolume(2L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(rootVolume,
 dataVolume));
+ 
+         DiskOfferingVO sharedOffering = mockSharedDiskOffering(1L);
+         Mockito.when(diskOfferingDao.findById(1L)).thenReturn(sharedOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, rootVolume);
+ 
+         // Verify multiple volumes are handled
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify all volumes were checked
+         Mockito.verify(volumeDao).findCreatedByInstance(vm.getId());
+         Mockito.verify(diskOfferingDao, Mockito.times(2)).findById(1L);
+ 
+         // Verify result
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should return 2 suitable hosts for migration", 
2, result.second().size());
+         Assert.assertTrue("Suitable hosts should include host 101",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Suitable hosts should include host 102",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMWithMixedStorage() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.XenServer);
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.XenServer);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // No storage motion support
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.XenServer,
 null))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         // Mixed storage - one shared, one local
+         VolumeVO sharedVolume = mockVolume(1L, 1L);
+         VolumeVO localVolume = mockVolume(2L, 2L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(sharedVolume,
 localVolume));
+ 
+         DiskOfferingVO sharedOffering = mockSharedDiskOffering(1L);
+         DiskOfferingVO localOffering = mockLocalDiskOffering(2L);
+         
Mockito.when(diskOfferingDao.findById(sharedVolume.getDiskOfferingId())).thenReturn(sharedOffering);
+         
Mockito.when(diskOfferingDao.findById(localVolume.getDiskOfferingId())).thenReturn(localOffering);
+ 
+         // Should throw exception because we have local storage without 
storage motion support
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMKVMWithNullHypervisorVersion() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         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);
+ 
+         // KVM host with null hypervisor version
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // KVM null version should be treated as empty string
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify KVM null version was converted to empty string
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
+ 
+         // Verify result data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Total hosts should be 2", Integer.valueOf(2), 
result.first().second());
+         Assert.assertEquals("Suitable hosts should be 2", 2, 
result.second().size());
+         Assert.assertTrue("Should contain host 101",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Should contain host 102",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMNonUefiVm() {
+         // Test VM migration for non-UEFI VM (regular VM migration flow)
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify hosts are returned for migration (non-UEFI VMs don't need 
UEFI-enabled hosts)
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should have 2 total hosts", Integer.valueOf(2), 
result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Should contain host 101",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Should contain host 102",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithUefiVmClusterScope() {
+         // Test UEFI VM migration with cluster-scoped search (no storage 
motion)
+         // This exercises the code path where filteredHosts is NULL and 
allocateTo without list is called
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // No storage motion support - cluster-scoped search
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         // Mock filterUefiHostsForMigration to return success with filtered 
hosts (only host1 is UEFI-compatible)
+         List<HostVO> uefiCompatibleHosts = List.of(host1);
+         Pair<Boolean, List<HostVO>> uefiFilterResult = new Pair<>(true, 
uefiCompatibleHosts);
+         
Mockito.doReturn(uefiFilterResult).when(spy).filterUefiHostsForMigration(
+             Mockito.anyList(), Mockito.anyList(), Mockito.any());
+ 
+         // Setup other mocks
+         
Mockito.when(dpdkHelper.isVMDpdkEnabled(vm.getId())).thenReturn(false);
+         
Mockito.when(affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId())).thenReturn(0L);
+         DataCenterVO dc = Mockito.mock(DataCenterVO.class);
+         
Mockito.when(dcDao.findById(srcHost.getDataCenterId())).thenReturn(dc);
+         
Mockito.doNothing().when(dpMgr).checkForNonDedicatedResources(Mockito.any(), 
Mockito.any(), Mockito.any());
+         Mockito.doNothing().when(dpMgr).reorderHostsByPriority(Mockito.any(), 
Mockito.anyList());
+ 
+         // After UEFI filtering, filteredHosts is set to uefiCompatibleHosts 
(line 1582)
+         // So allocateTo WITH list parameter is called (line 1608) even in 
cluster scope
+         Mockito.when(hostAllocator.allocateTo(Mockito.any(), Mockito.any(), 
Mockito.any(),
+             Mockito.any(), Mockito.anyList(), Mockito.anyInt(), 
Mockito.anyBoolean()))
+             .thenReturn(new ArrayList<>(uefiCompatibleHosts));
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify result structure
+         Assert.assertNotNull("Result should not be null", result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("All hosts list should contain 2 hosts", 2, 
result.first().first().size());
+ 
+         // Verify only UEFI-compatible hosts are in suitable list
+         Assert.assertEquals("Should have 1 UEFI-compatible suitable host", 1, 
result.second().size());
+         Assert.assertTrue("Host 101 should be the only UEFI-compatible host",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertFalse("Host 102 should not be in suitable hosts (not 
UEFI-compatible)",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithUefiVmZoneWideScope() {
+         // Test UEFI VM migration with zone-wide search (storage motion 
enabled)
+         // This exercises the code path where filteredHosts IS populated and 
allocateTo WITH list is called
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.VMware);
+         Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.VMware);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // Storage motion supported - zone-wide search with filteredHosts
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.VMware,
 null))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for zone-wide search (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.VMware);
+         HostVO host2 = mockHost(102L, 2L, 1L, 1L, HypervisorType.VMware); // 
Different cluster
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         // Mock filterUefiHostsForMigration to return success with filtered 
hosts (only host1 is UEFI-compatible)
+         List<HostVO> uefiCompatibleHosts = List.of(host1);
+         Pair<Boolean, List<HostVO>> uefiFilterResult = new Pair<>(true, 
uefiCompatibleHosts);
+         
Mockito.doReturn(uefiFilterResult).when(spy).filterUefiHostsForMigration(
+             Mockito.anyList(), Mockito.anyList(), Mockito.any());
+ 
+         // Override hostAllocator to return only UEFI-compatible hosts
+         // Uses allocateTo WITH list parameter (filteredHosts is populated in 
zone-wide search)
+         Mockito.when(hostAllocator.allocateTo(Mockito.any(), Mockito.any(), 
Mockito.any(),
+             Mockito.any(), Mockito.anyList(), Mockito.anyInt(), 
Mockito.anyBoolean()))
+             .thenReturn(new ArrayList<>(uefiCompatibleHosts));
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify result structure
+         Assert.assertNotNull("Result should not be null", result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("All hosts list should contain 2 hosts", 2, 
result.first().first().size());
+ 
+         // Verify only UEFI-compatible hosts are in suitable list
+         Assert.assertEquals("Should have 1 UEFI-compatible suitable host", 1, 
result.second().size());
+         Assert.assertTrue("Host 101 should be the only UEFI-compatible host",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertFalse("Host 102 should not be in suitable hosts (not 
UEFI-compatible)",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+ 
+         // Verify storage motion map is populated
+         Assert.assertNotNull("Storage motion map should not be null", 
result.third());
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMUefiFilteringReturnsEmpty() {
+         // Test case where UEFI filtering results in no suitable hosts
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         // Mock filterUefiHostsForMigration FIRST to return false (no 
UEFI-enabled hosts found)
+         // This simulates the scenario where UEFI VM has no compatible hosts
+         Pair<Boolean, List<HostVO>> uefiFilterResult = new Pair<>(false, 
null);
+         
Mockito.doReturn(uefiFilterResult).when(spy).filterUefiHostsForMigration(
+             Mockito.anyList(), Mockito.anyList(), Mockito.any());
+ 
+         // Note: No other mocks needed because when 
filterUefiHostsForMigration returns false,
+         // the method returns early and doesn't proceed to host allocation or 
other processing
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify result structure
+         Assert.assertNotNull("Result should not be null", result);
+         Assert.assertNotNull("All hosts list should not be null", 
result.first());
+         Assert.assertNotNull("Suitable hosts list should not be null", 
result.second());
+         Assert.assertNotNull("Storage motion map should not be null", 
result.third());
+ 
+         // Verify all hosts are still returned (from searchForServers)
+         Assert.assertEquals("Should still return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("All hosts list should contain 2 hosts", 2, 
result.first().first().size());
+ 
+         // Verify suitable hosts list is empty due to UEFI filtering
+         Assert.assertEquals("Should have 0 suitable hosts after UEFI 
filtering", 0, result.second().size());
+ 
+         // Verify storage motion map is empty
+         Assert.assertTrue("Storage motion map should be empty when no 
suitable hosts", result.third().isEmpty());
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMStorageMotionCapabilityCheck() {
+         // Test User VM with VMware - should check storage motion for User VMs
+         VMInstanceVO userVm = mockRunningVM(1L, HypervisorType.VMware);
+         Mockito.when(userVm.getType()).thenReturn(VirtualMachine.Type.User);
+ 
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(userVm);
+         
Mockito.when(serviceOfferingDetailsDao.findDetail(userVm.getServiceOfferingId(),
 GPU.Keys.pciDevice.toString()))
+             .thenReturn(null);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.VMware);
+         
Mockito.when(hostDao.findById(userVm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.VMware,
 null))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(userVm.getId(), 
userVm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(userVm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.VMware);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.VMware);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(userVm, srcHost, hosts, volume, true);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify storage motion capability was checked for User VM
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
+ 
+         // Verify response data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Host 101 should be in suitable list",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Host 102 should be in suitable list",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithAllSupportedHypervisors() {
+         // Test each supported hypervisor type
+         HypervisorType[] supportedTypes = {
+             HypervisorType.XenServer,
+             HypervisorType.VMware,
+             HypervisorType.KVM,
+             HypervisorType.Ovm,
+             HypervisorType.Hyperv,
+             HypervisorType.Ovm3
+         };
+ 
+         for (HypervisorType hypervisorType : supportedTypes) {
+             VMInstanceVO vm = mockRunningVM(1L, hypervisorType);
+             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);
+ 
+             HostVO srcHost = mockHost(100L, 1L, 1L, 1L, hypervisorType);
+             
Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+             String version = hypervisorType == HypervisorType.KVM ? "" : null;
+             
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(hypervisorType, 
version))
+                 .thenReturn(false);
+ 
+             ServiceOfferingVO offering = 
Mockito.mock(ServiceOfferingVO.class);
+             Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+             VolumeVO volume = mockVolume(1L, 1L);
+             
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+             DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+             
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+             // Mock searchForServers for cluster-scoped search
+             HostVO host1 = mockHost(101L, 1L, 1L, 1L, hypervisorType);
+             HostVO host2 = mockHost(102L, 1L, 1L, 1L, hypervisorType);
+             List<HostVO> hosts = List.of(host1, host2);
+             Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+             Mockito.doReturn(hostsPair).when(spy).searchForServers(
+                 Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+                 Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+                 Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+                 Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+             setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+             Ternary<Pair<List<? extends Host>, Integer>, List<? extends 
Host>, Map<Host, Boolean>> result =
+                 spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+             // Verify hypervisor is in supported hypervisors list
+             Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(hypervisorType, version);
+ 
+             // Verify validation passed for this hypervisor
+             Assert.assertNotNull("Result should not be null for " + 
hypervisorType, result);
+             Assert.assertEquals("Should return 2 total hosts for " + 
hypervisorType,
+                 Integer.valueOf(2), result.first().second());
+             Assert.assertEquals("Should have 2 suitable hosts for " + 
hypervisorType,
+                 2, result.second().size());
+             Assert.assertTrue("Host 101 should be available for " + 
hypervisorType,
+                 result.second().stream().anyMatch(h -> h.getId() == 101L));
+             Assert.assertTrue("Host 102 should be available for " + 
hypervisorType,
+                 result.second().stream().anyMatch(h -> h.getId() == 102L));
+ 
+             // Reset mocks for next iteration
+             Mockito.reset(vmInstanceDao, hostDao, serviceOfferingDetailsDao, 
volumeDao,
+                 diskOfferingDao, hypervisorCapabilitiesDao, offeringDao, 
dpdkHelper,
+                 affinityGroupVMMapDao, dpMgr, dcDao, hostAllocator, 
dataStoreManager);
+             Mockito.reset(spy);
+         }
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMSourceHostNotFound() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         Account caller = mockRootAdminAccount();
+         Mockito.doReturn(caller).when(spy).getCaller();
+         Mockito.when(vmInstanceDao.findById(1L)).thenReturn(vm);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(null);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test(expected = InvalidParameterValueException.class)
+     public void testListHostsForMigrationOfVMLocalStorageNoStorageMotion() {
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.XenServer);
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.XenServer);
+         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(srcHost);
+ 
+         // Mock storage motion not supported
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.XenServer,
 null))
+             .thenReturn(false);
+ 
+         // Mock local storage usage
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockLocalDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMStorageMotionCheckForSystemVM() {
+         // Test that storage motion capability is checked for System VMs
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.VMware);
+         
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.VMware);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // Storage motion supported for VMware
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.VMware,
 null))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.VMware);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.VMware);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify that storage motion capability was checked for system VM 
(VMware is in hypervisorTypes list)
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
+ 
+         // Verify response structure
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should have 2 total hosts", Integer.valueOf(2), 
result.first().second());
+         Assert.assertTrue("Should have suitable hosts", 
result.second().size() > 0);
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMStorageMotionCheckForUserVM() {
+         // Test that storage motion capability is checked for User VMs
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+         Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // Storage motion supported for User VM with KVM
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(true);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify User VM can migrate with storage (User VM type always 
checks)
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.KVM, "");
+ 
+         // Verify response data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should have 2 total hosts", Integer.valueOf(2), 
result.first().second());
+         Assert.assertTrue("Should have suitable hosts", 
result.second().size() > 0);
+     }
+ 
+     @Test
+     public void 
testListHostsForMigrationOfVMWithoutStorageMotionClusterScope() {
+         // When storage motion not supported, should search only in same 
cluster
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.XenServer);
+         Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.User);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.XenServer);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         // No storage motion support
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.XenServer,
 null))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers - verify cluster scope is used
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.XenServer);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.XenServer);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.eq(0L), Mockito.eq(20L), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.eq(1L), // cluster=1L
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.eq(100L));
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify XenServer without storage motion was checked
+         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),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.eq(1L),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.eq(100L));
+ 
+         // Verify response data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Should contain host 101",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Should contain host 102",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithNoVolumes() {
+         // Edge case: VM with no volumes
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         // No volumes
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(new 
ArrayList<>());
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         // Set up mocks without volume since there are no volumes
+         Pair<Boolean, List<HostVO>> uefiResult = new Pair<>(true, hosts);
+         Mockito.doReturn(uefiResult).when(spy).filterUefiHostsForMigration(
+             Mockito.anyList(), Mockito.anyList(), Mockito.any());
+         
Mockito.when(dpdkHelper.isVMDpdkEnabled(vm.getId())).thenReturn(false);
+         
Mockito.when(affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId())).thenReturn(0L);
+         DataCenterVO dc = Mockito.mock(DataCenterVO.class);
+         Mockito.when(dcDao.findById(1L)).thenReturn(dc);
+         
Mockito.doNothing().when(dpMgr).checkForNonDedicatedResources(Mockito.any(), 
Mockito.any(), Mockito.any());
+         Mockito.doNothing().when(dpMgr).reorderHostsByPriority(Mockito.any(), 
Mockito.anyList());
+ 
+         Mockito.when(hostAllocator.allocateTo(Mockito.any(), Mockito.any(), 
Mockito.any(),
+             Mockito.any(), Mockito.anyList(), Mockito.anyInt(), 
Mockito.anyBoolean()))
+             .thenReturn(new ArrayList<>(hosts));
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Should still process without throwing exception for usesLocal check
+         Mockito.verify(volumeDao).findCreatedByInstance(vm.getId());
+ 
+         // Verify response
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should have 2 total hosts", Integer.valueOf(2), 
result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts even with no 
volumes", 2, result.second().size());
+         Assert.assertTrue("Storage motion map should be empty for VM with no 
volumes",
+             result.third().isEmpty());
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMOverloadedMethod() {
+         // Test the overloaded method that takes vmId instead of vm object
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search with keyword
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.eq("keyword-test"), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+         // Call overloaded method with vmId
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, "keyword-test");
+ 
+         // Verify VM was fetched by ID
+         Mockito.verify(vmInstanceDao).findById(1L);
+ 
+         // Verify keyword was passed to searchForServers
+         Mockito.verify(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.eq("keyword-test"), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         // Verify response data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should have 2 hosts", Integer.valueOf(2), 
result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+     }
+ 
+     @Test
+     public void testListHostsForMigrationOfVMVmwareStorageMotionCheck() {
+         // VMware should check storage motion even for non-User VMs
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.VMware);
+         
Mockito.when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter);
+ 
+         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);
+ 
+         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);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers with zone-wide scope (storage motion enabled)
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.VMware);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.VMware);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.anyLong(), Mockito.anyLong(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.any(HypervisorType.class), Mockito.isNull(), 
Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume, true);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify VMware always checks storage motion (hypervisorTypes list 
includes VMware)
+         Mockito.verify(hypervisorCapabilitiesDao, 
Mockito.atLeastOnce()).isStorageMotionSupported(HypervisorType.VMware, null);
+ 
+         // Verify response
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Host 101 should be in the list",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+     }
+ 
+ 
+     @Test
+     public void testListHostsForMigrationOfVMWithNullKeyword() {
+         // Test with null keyword parameter
+         VMInstanceVO vm = mockRunningVM(1L, HypervisorType.KVM);
+ 
+         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);
+ 
+         HostVO srcHost = mockHost(100L, 1L, 1L, 1L, HypervisorType.KVM);
+         Mockito.when(hostDao.findById(vm.getHostId())).thenReturn(srcHost);
+ 
+         
Mockito.when(hypervisorCapabilitiesDao.isStorageMotionSupported(HypervisorType.KVM,
 ""))
+             .thenReturn(false);
+ 
+         ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
+         Mockito.when(offeringDao.findById(vm.getId(), 
vm.getServiceOfferingId())).thenReturn(offering);
+ 
+         VolumeVO volume = mockVolume(1L, 1L);
+         
Mockito.when(volumeDao.findCreatedByInstance(vm.getId())).thenReturn(List.of(volume));
+ 
+         DiskOfferingVO diskOffering = mockSharedDiskOffering(1L);
+         
Mockito.when(diskOfferingDao.findById(volume.getDiskOfferingId())).thenReturn(diskOffering);
+ 
+         // Mock searchForServers for cluster-scoped search
+         HostVO host1 = mockHost(101L, 1L, 1L, 1L, HypervisorType.KVM);
+         HostVO host2 = mockHost(102L, 1L, 1L, 1L, HypervisorType.KVM);
+         List<HostVO> hosts = List.of(host1, host2);
+         Pair<List<HostVO>, Integer> hostsPair = new Pair<>(hosts, 2);
+         Mockito.doReturn(hostsPair).when(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         setupMigrationMocks(vm, srcHost, hosts, volume);
+ 
+         Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, 
Map<Host, Boolean>> result =
+             spy.listHostsForMigrationOfVM(1L, 0L, 20L, null);
+ 
+         // Verify null keyword is handled
+         Mockito.verify(vmInstanceDao).findById(1L);
+ 
+         // Verify searchForServers was called with null keyword
+         Mockito.verify(spy).searchForServers(
+             Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), 
Mockito.any(Type.class),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.anyLong(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.isNull(), 
Mockito.isNull(),
+             Mockito.isNull(), Mockito.isNull(), Mockito.anyLong());
+ 
+         // Verify response data
+         Assert.assertNotNull(result);
+         Assert.assertEquals("Should return 2 total hosts", 
Integer.valueOf(2), result.first().second());
+         Assert.assertEquals("Should have 2 suitable hosts", 2, 
result.second().size());
+         Assert.assertTrue("Host 101 should be available",
+             result.second().stream().anyMatch(h -> h.getId() == 101L));
+         Assert.assertTrue("Host 102 should be available",
+             result.second().stream().anyMatch(h -> h.getId() == 102L));
+     }
+ 
+     // Note: Tests for success scenarios with complex flows (managed storage, 
zone-wide volumes,
+     // DPDK exclusion, affinity groups, architecture filtering) require full 
setup with mocking
+     // private methods like hasSuitablePoolsForVolume(), 
excludeNonDPDKEnabledHosts(), and
+     // filterUefiHostsForMigration() which are better suited for integration 
tests.
+ 
+     // ============= Helper methods for tests =============
+ 
+     /**
+      * Sets up common mocks for successful migration tests
+      * For storage motion tests, set forceStorageMotion=true to configure 
volume in same cluster
+      * (which avoids complex filtering logic for cross-cluster storage motion)
+      */
+     private void setupMigrationMocks(VMInstanceVO vm, HostVO srcHost,
+                                      List<HostVO> targetHosts, VolumeVO 
volume) {
+         setupMigrationMocks(vm, srcHost, targetHosts, volume, false);
+     }
+ 
+     private void setupMigrationMocks(VMInstanceVO vm, HostVO srcHost,
+                                      List<HostVO> targetHosts, VolumeVO 
volume,
+                                      boolean forceStorageMotion) {
+         // Mock dataStoreManager for volume pool lookup (lenient as not used 
in all paths)
+         // For storage motion tests, put volume in same cluster to avoid 
complex filtering
+         PrimaryDataStore primaryDataStore = 
Mockito.mock(PrimaryDataStore.class);
+         
Mockito.when(dataStoreManager.getPrimaryDataStore(volume.getPoolId())).thenReturn(primaryDataStore);
+         // If not forceStorageMotion, volume is in same cluster (no storage 
motion needed)
+         // If forceStorageMotion, set volClusterId to null (zone-wide storage)
+         
Mockito.when(primaryDataStore.getClusterId()).thenReturn(forceStorageMotion ? 
null : srcHost.getClusterId());
+ 
+         // Mock zoneWideVolumeRequiresStorageMotion for zone-wide volumes
+         if (forceStorageMotion) {
+             
Mockito.doReturn(false).when(spy).zoneWideVolumeRequiresStorageMotion(
+                 Mockito.any(), Mockito.any(), Mockito.any());
+         }
+ 
+         // Mock filterUefiHostsForMigration - must return hosts properly
+         Pair<Boolean, List<HostVO>> uefiResult = new Pair<>(true, new 
ArrayList<>(targetHosts));
+         Mockito.doReturn(uefiResult).when(spy).filterUefiHostsForMigration(
+             Mockito.anyList(), Mockito.anyList(), Mockito.any());
+ 
+         // Mock DPDK check
+         
Mockito.when(dpdkHelper.isVMDpdkEnabled(vm.getId())).thenReturn(false);
+ 
+         // Mock affinity group count
+         
Mockito.when(affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId())).thenReturn(0L);
+ 
+         // Mock datacenter
+         DataCenterVO dc = Mockito.mock(DataCenterVO.class);
+         
Mockito.when(dcDao.findById(srcHost.getDataCenterId())).thenReturn(dc);
+ 
+         // Mock dedicated resources check
+         Mockito.doNothing().when(dpMgr).checkForNonDedicatedResources(
+             Mockito.any(), Mockito.any(), Mockito.any());
+ 
+         // Mock priority reordering
+         Mockito.doNothing().when(dpMgr).reorderHostsByPriority(Mockito.any(), 
Mockito.anyList());
+ 
+         // Mock host allocators - both signatures
+         // 1. Version with filteredHosts list (used when 
canMigrateWithStorage = true)
+         Mockito.when(hostAllocator.allocateTo(Mockito.any(), Mockito.any(), 
Mockito.any(),
+             Mockito.any(), Mockito.anyList(), Mockito.anyInt(), 
Mockito.anyBoolean()))
+             .thenReturn(new ArrayList<>(targetHosts));
+     }
+ 
+     private VMInstanceVO mockRunningVM(Long id, HypervisorType 
hypervisorType) {
+         return mockVM(id, hypervisorType, State.Running);
+     }
+ 
+     private VMInstanceVO mockVM(Long id, HypervisorType hypervisorType, State 
state) {
+         VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
+         when(vm.getId()).thenReturn(id);
+         when(vm.getState()).thenReturn(state);
+         when(vm.getHypervisorType()).thenReturn(hypervisorType);
+         when(vm.getHostId()).thenReturn(100L);
+         when(vm.getServiceOfferingId()).thenReturn(1L);
+         when(vm.getType()).thenReturn(VirtualMachine.Type.User);
+         when(vm.getUuid()).thenReturn("uuid-" + id);
+         when(vm.getDataCenterId()).thenReturn(1L);
+         return vm;
+     }
+ 
+     private Account mockRootAdminAccount() {
+         Account account = Mockito.mock(Account.class);
+         Mockito.when(account.getId()).thenReturn(1L);
+         Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(true);
+         return account;
+     }
+ 
+     private HostVO mockHost(Long id, Long clusterId, Long podId, Long dcId, 
HypervisorType hypervisorType) {
+         HostVO host = new HostVO("guid-" + id);
+         ReflectionTestUtils.setField(host, "id", id);
+         ReflectionTestUtils.setField(host, "clusterId", clusterId);
+         ReflectionTestUtils.setField(host, "podId", podId);
+         ReflectionTestUtils.setField(host, "dataCenterId", dcId);
+         ReflectionTestUtils.setField(host, "hypervisorType", hypervisorType);
+         ReflectionTestUtils.setField(host, "type", Host.Type.Routing);
+         ReflectionTestUtils.setField(host, "hypervisorVersion", null);
+         return host;
+     }
+ 
+     private VolumeVO mockVolume(Long id, Long poolId) {
+         VolumeVO volume = Mockito.mock(VolumeVO.class);
+         when(volume.getId()).thenReturn(id);
+         when(volume.getPoolId()).thenReturn(poolId);
+         when(volume.getDiskOfferingId()).thenReturn(1L);
+         when(volume.getVolumeType()).thenReturn(Volume.Type.ROOT);
+         return volume;
+     }
+ 
+     private DiskOfferingVO mockLocalDiskOffering(Long id) {
+         DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
+         Mockito.when(diskOffering.getId()).thenReturn(id);
+         Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(true);
+         return diskOffering;
+     }
+ 
+     private DiskOfferingVO mockSharedDiskOffering(Long id) {
+         DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
+         Mockito.when(diskOffering.getId()).thenReturn(id);
+         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());
+     }
  }

Reply via email to