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

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

commit 76a4bc8c9dea970f829772fdb8419013974797f9
Merge: 63c142be261 e8df87e89be
Author: Wei Zhou <[email protected]>
AuthorDate: Mon Jul 13 09:58:35 2026 +0200

    Merge remote-tracking branch 'apache/4.20' into 4.22

 .../adapter/flasharray/FlashArrayAdapter.java      |  184 ++-
 .../com/cloud/server/ManagementServerImpl.java     |   69 +-
 .../com/cloud/server/ManagementServerImplTest.java | 1338 +++++++++++++++++++-
 3 files changed, 1512 insertions(+), 79 deletions(-)

diff --cc server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 470c968d0de,1fc92ad0e8e..95e688b5d8c
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@@ -1552,11 -1498,10 +1529,12 @@@ public class ManagementServerImpl exten
              throw new InvalidParameterValueException("Unsupported operation, 
instance uses Local storage, cannot migrate");
          }
  
 +        validateVgpuProfileForVmMigration(vmProfile);
 +
+         final String srcHostVersion = getHypervisorVersionOfHost(srcHost);
          final Type hostType = srcHost.getType();
 -        Pair<List<HostVO>, Integer> allHostsPair = null;
 -        List<HostVO> allHosts = null;
 +        Pair<List<HostVO>, Integer> allHostsPair;
 +        List<HostVO> allHosts;
          List<HostVO> filteredHosts = null;
          final Map<Host, Boolean> requiresStorageMotion = new HashMap<>();
  
@@@ -1756,17 -1726,14 +1759,25 @@@
          return new Ternary<>(otherHosts, suitableHosts, 
requiresStorageMotion);
      }
  
 +    private void validateVgpuProfileForVmMigration(final 
VirtualMachineProfile vmProfile) {
 +        // Validate if the VM is using a vGPU profile that supports migration.
 +        ServiceOffering serviceOffering = vmProfile.getServiceOffering();
 +        if (serviceOffering.getVgpuProfileId() != null) {
 +            VgpuProfileVO vgpuProfile = 
vgpuProfileDao.findById(serviceOffering.getVgpuProfileId());
 +            if (vgpuProfile == null || 
"passthrough".equals(vgpuProfile.getName())) {
 +                throw new InvalidParameterValueException("Unsupported 
operation, VM uses host passthrough, cannot migrate");
 +            }
 +        }
 +    }
 +
+     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 --cc server/src/test/java/com/cloud/server/ManagementServerImplTest.java
index b569368f248,2f3e97716a0..b0f274e6fc8
--- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
+++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java
@@@ -746,317 -772,1337 +746,1651 @@@ public class ManagementServerImplTest 
          Assert.assertTrue(spy.zoneWideVolumeRequiresStorageMotion(dataStore, 
host1, host2));
      }
  
 +    @Test(expected = InvalidParameterValueException.class)
 +    public void testSearchForConfigurationsMultipleIds() {
 +        ListCfgsByCmd cmd = Mockito.mock(ListCfgsByCmd.class);
 +        
Mockito.when(cmd.getConfigName()).thenReturn("pool.storage.capacity.disablethreshold");
 +        Mockito.when(cmd.getZoneId()).thenReturn(1L);
 +        Mockito.when(cmd.getStoragepoolId()).thenReturn(2L);
 +        spy.searchForConfigurations(cmd);
 +    }
 +
 +    @Test
 +    public void testSearchForConfigurations() {
 +        Long poolId = 1L;
 +        ListCfgsByCmd cmd = Mockito.mock(ListCfgsByCmd.class);
 +        
Mockito.when(cmd.getConfigName()).thenReturn("pool.storage.capacity.disablethreshold");
 +        Mockito.when(cmd.getStoragepoolId()).thenReturn(poolId);
 +        Mockito.when(cmd.getZoneId()).thenReturn(null);
 +        Mockito.when(cmd.getClusterId()).thenReturn(null);
 +        Mockito.when(cmd.getAccountId()).thenReturn(null);
 +        Mockito.when(cmd.getDomainId()).thenReturn(null);
 +        Mockito.when(cmd.getImageStoreId()).thenReturn(null);
 +
 +        SearchCriteria<ConfigurationVO> sc = 
Mockito.mock(SearchCriteria.class);
 +        Mockito.when(configDao.createSearchCriteria()).thenReturn(sc);
 +        ConfigurationVO cfg = new ConfigurationVO("Advanced", "DEFAULT", 
"test", "pool.storage.capacity.disablethreshold", null, "description");
 +        Mockito.when(configDao.searchAndCount(any(), any())).thenReturn(new 
Pair<>(List.of(cfg), 1));
 +        
Mockito.when(configDao.findByName("pool.storage.capacity.disablethreshold")).thenReturn(cfg);
 +
 +        ConfigKey storageDisableThreshold = new 
ConfigKey<>(ConfigKey.CATEGORY_ALERT, Double.class, 
"pool.storage.capacity.disablethreshold", "0.85",
 +                "Percentage (as a value between 0 and 1) of storage 
utilization above which allocators will disable using the pool for low storage 
available.",
 +                true, List.of(ConfigKey.Scope.StoragePool, 
ConfigKey.Scope.Zone));
 +        
when(configDepot.get("pool.storage.capacity.disablethreshold")).thenReturn(storageDisableThreshold);
 +
 +        Pair<List<? extends Configuration>, Integer> result = 
spy.searchForConfigurations(cmd);
 +
 +        Assert.assertEquals("0.85", result.first().get(0).getValue());
 +    }
 +    @Test
 +    public void testAddGuestOsCategory() {
 +        AddGuestOsCategoryCmd addCmd = 
Mockito.mock(AddGuestOsCategoryCmd.class);
 +        String name = "Ubuntu";
 +        boolean featured = true;
 +        Mockito.when(addCmd.getName()).thenReturn(name);
 +        Mockito.when(addCmd.isFeatured()).thenReturn(featured);
 +        Mockito.doAnswer((Answer<GuestOSCategoryVO>) invocation -> 
(GuestOSCategoryVO)invocation.getArguments()[0]).when(guestOSCategoryDao).persist(Mockito.any(GuestOSCategoryVO.class));
 +        GuestOsCategory result = spy.addGuestOsCategory(addCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(name, result.getName());
 +        Assert.assertEquals(featured, result.isFeatured());
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).persist(any(GuestOSCategoryVO.class));
 +    }
 +
 +    @Test
 +    public void testUpdateGuestOsCategory() {
 +        UpdateGuestOsCategoryCmd updateCmd = 
Mockito.mock(UpdateGuestOsCategoryCmd.class);
 +        GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", 
false);
 +        long id = 1L;
 +        String name = "Updated Name";
 +        Boolean featured = true;
 +        Integer sortKey = 10;
 +        Mockito.when(updateCmd.getId()).thenReturn(id);
 +        Mockito.when(updateCmd.getName()).thenReturn(name);
 +        Mockito.when(updateCmd.isFeatured()).thenReturn(featured);
 +        Mockito.when(updateCmd.getSortKey()).thenReturn(sortKey);
 +        
Mockito.when(guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
 +        Mockito.when(guestOSCategoryDao.update(Mockito.eq(id), 
any(GuestOSCategoryVO.class))).thenReturn(true);
 +        GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(name, result.getName());
 +        Assert.assertEquals(featured, result.isFeatured());
 +        Mockito.verify(guestOSCategoryDao, Mockito.times(1)).findById(id);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
 +    }
 +
 +    @Test(expected = InvalidParameterValueException.class)
 +    public void 
testUpdateGuestOsCategory_ThrowsExceptionWhenCategoryNotFound() {
 +        UpdateGuestOsCategoryCmd updateCmd = 
Mockito.mock(UpdateGuestOsCategoryCmd.class);
 +        long id = 1L;
 +        when(updateCmd.getId()).thenReturn(id);
 +        when(guestOSCategoryDao.findById(id)).thenReturn(null);
 +        spy.updateGuestOsCategory(updateCmd);
 +    }
 +
 +    @Test
 +    public void testUpdateGuestOsCategory_NoChanges() {
 +        UpdateGuestOsCategoryCmd updateCmd = 
Mockito.mock(UpdateGuestOsCategoryCmd.class);
 +        GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", 
false);
 +        long id = 1L;
 +        when(updateCmd.getId()).thenReturn(id);
 +        when(updateCmd.getName()).thenReturn(null);
 +        when(updateCmd.isFeatured()).thenReturn(null);
 +        when(updateCmd.getSortKey()).thenReturn(null);
 +        when(guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
 +        GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertNotNull(result.getName());
 +        Assert.assertFalse(result.isFeatured());
 +        Mockito.verify(guestOSCategoryDao, Mockito.times(1)).findById(id);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.never()).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
 +    }
 +
 +    @Test
 +    public void testUpdateGuestOsCategory_UpdateNameOnly() {
 +        UpdateGuestOsCategoryCmd updateCmd = 
Mockito.mock(UpdateGuestOsCategoryCmd.class);
 +        GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", 
false);
 +        long id = 1L;
 +        String name = "Updated Name";
 +        Mockito.when(updateCmd.getId()).thenReturn(id);
 +        Mockito.when(updateCmd.getName()).thenReturn(name);
 +        Mockito.when(updateCmd.isFeatured()).thenReturn(null);
 +        Mockito.when(updateCmd.getSortKey()).thenReturn(null);
 +        
Mockito.when(guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
 +        Mockito.when(guestOSCategoryDao.update(Mockito.eq(id), 
any(GuestOSCategoryVO.class))).thenReturn(true);
 +        GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(name, result.getName());
 +        Assert.assertFalse(result.isFeatured());
 +        Mockito.verify(guestOSCategoryDao, Mockito.times(1)).findById(id);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
 +    }
 +
 +    @Test
 +    public void testDeleteGuestOsCategory_Successful() {
 +        DeleteGuestOsCategoryCmd deleteCmd = 
Mockito.mock(DeleteGuestOsCategoryCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        long id = 1L;
 +        Mockito.when(deleteCmd.getId()).thenReturn(id);
 +        
Mockito.when(guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
 +        
Mockito.when(guestOSDao.listIdsByCategoryId(id)).thenReturn(Arrays.asList());
 +        Mockito.when(guestOSCategoryDao.remove(id)).thenReturn(true);
 +        boolean result = spy.deleteGuestOsCategory(deleteCmd);
 +        Assert.assertTrue(result);
 +        Mockito.verify(guestOSCategoryDao, Mockito.times(1)).findById(id);
 +        Mockito.verify(guestOSDao, Mockito.times(1)).listIdsByCategoryId(id);
 +        Mockito.verify(guestOSCategoryDao, Mockito.times(1)).remove(id);
 +    }
 +
 +    @Test(expected = InvalidParameterValueException.class)
 +    public void 
testDeleteGuestOsCategory_ThrowsExceptionWhenCategoryNotFound() {
 +        DeleteGuestOsCategoryCmd deleteCmd = 
Mockito.mock(DeleteGuestOsCategoryCmd.class);
 +        long id = 1L;
 +        Mockito.when(deleteCmd.getId()).thenReturn(id);
 +        Mockito.when(guestOSCategoryDao.findById(id)).thenReturn(null);
 +        spy.deleteGuestOsCategory(deleteCmd);
 +    }
 +
 +    @Test(expected = InvalidParameterValueException.class)
 +    public void testDeleteGuestOsCategory_ThrowsExceptionWhenGuestOsExists() {
 +        DeleteGuestOsCategoryCmd deleteCmd = 
Mockito.mock(DeleteGuestOsCategoryCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        long id = 1L;
 +        Mockito.when(deleteCmd.getId()).thenReturn(id);
 +        
Mockito.when(guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
 +        
Mockito.when(guestOSDao.listIdsByCategoryId(id)).thenReturn(Arrays.asList(1L));
 +        spy.deleteGuestOsCategory(deleteCmd);
 +    }
 +
 +    private void mockGuestOsJoin() {
 +        GuestOSVO vo = mock(GuestOSVO.class);
 +        SearchBuilder<GuestOSVO> sb = mock(SearchBuilder.class);
 +        when(sb.entity()).thenReturn(vo);
 +        when(guestOSDao.createSearchBuilder()).thenReturn(sb);
 +    }
 +
 +    @Test
 +    public void testListGuestOSCategoriesByCriteria_Success() {
 +        ListGuestOsCategoriesCmd listCmd = 
Mockito.mock(ListGuestOsCategoriesCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        Filter filter = Mockito.mock(Filter.class);
 +        Long id = 1L;
 +        String name = "Ubuntu";
 +        String keyword = "Linux";
 +        Boolean featured = true;
 +        Long zoneId = 1L;
 +        CPU.CPUArch arch = CPU.CPUArch.getDefault();
 +        Boolean isIso = true;
 +        Boolean isVnf = false;
 +        Mockito.when(listCmd.getId()).thenReturn(id);
 +        Mockito.when(listCmd.getName()).thenReturn(name);
 +        Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
 +        Mockito.when(listCmd.isFeatured()).thenReturn(featured);
 +        Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
 +        Mockito.when(listCmd.getArch()).thenReturn(arch);
 +        Mockito.when(listCmd.isIso()).thenReturn(isIso);
 +        Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
 +        SearchBuilder<GuestOSCategoryVO> searchBuilder = 
Mockito.mock(SearchBuilder.class);
 +        Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
 +        SearchCriteria<GuestOSCategoryVO> searchCriteria = 
Mockito.mock(SearchCriteria.class);
 +        
Mockito.when(guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
 +        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
 +        Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, 
arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
 +        Pair<List<GuestOSCategoryVO>, Integer> mockResult = new 
Pair<>(Arrays.asList(guestOSCategory), 1);
 +        mockGuestOsJoin();
 +        
Mockito.when(guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), 
Mockito.any())).thenReturn(mockResult);
 +        Pair<List<? extends GuestOsCategory>, Integer> result = 
spy.listGuestOSCategoriesByCriteria(listCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(1, result.second().intValue());
 +        Assert.assertEquals(1, result.first().size());
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).createSearchBuilder();
 +        Mockito.verify(templateDao, 
Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
 +    }
 +
 +    @Test
 +    public void testListGuestOSCategoriesByCriteria_NoResults() {
 +        ListGuestOsCategoriesCmd listCmd = 
Mockito.mock(ListGuestOsCategoriesCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        Long id = 1L;
 +        String name = "CentOS";
 +        String keyword = "Linux";
 +        Boolean featured = false;
 +        Long zoneId = 1L;
 +        CPU.CPUArch arch = CPU.CPUArch.getDefault();
 +        Boolean isIso = false;
 +        Boolean isVnf = false;
 +        Mockito.when(listCmd.getId()).thenReturn(id);
 +        Mockito.when(listCmd.getName()).thenReturn(name);
 +        Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
 +        Mockito.when(listCmd.isFeatured()).thenReturn(featured);
 +        Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
 +        Mockito.when(listCmd.getArch()).thenReturn(arch);
 +        Mockito.when(listCmd.isIso()).thenReturn(isIso);
 +        Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
 +        SearchBuilder<GuestOSCategoryVO> searchBuilder = 
Mockito.mock(SearchBuilder.class);
 +        Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
 +        SearchCriteria<GuestOSCategoryVO> searchCriteria = 
Mockito.mock(SearchCriteria.class);
 +        
Mockito.when(guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
 +        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
 +        Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, 
arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
 +        Pair<List<GuestOSCategoryVO>, Integer> mockResult = new 
Pair<>(Arrays.asList(), 0);
 +        
Mockito.when(guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), 
Mockito.any())).thenReturn(mockResult);
 +        mockGuestOsJoin();
 +        Pair<List<? extends GuestOsCategory>, Integer> result = 
spy.listGuestOSCategoriesByCriteria(listCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(0, result.second().intValue());
 +        Assert.assertEquals(0, result.first().size());
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).createSearchBuilder();
 +        Mockito.verify(templateDao, 
Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
 +    }
 +
 +    @Test
 +    public void testListGuestOSCategoriesByCriteria_NoGuestOsIdsFound() {
 +        ListGuestOsCategoriesCmd listCmd = 
Mockito.mock(ListGuestOsCategoriesCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        Long id = 1L;
 +        String name = "Ubuntu";
 +        String keyword = "Linux";
 +        Boolean featured = true;
 +        Long zoneId = 1L;
 +        CPU.CPUArch arch = CPU.CPUArch.getDefault();
 +        Boolean isIso = true;
 +        Boolean isVnf = false;
 +        Mockito.when(listCmd.getId()).thenReturn(id);
 +        Mockito.when(listCmd.getName()).thenReturn(name);
 +        Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
 +        Mockito.when(listCmd.isFeatured()).thenReturn(featured);
 +        Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
 +        Mockito.when(listCmd.getArch()).thenReturn(arch);
 +        Mockito.when(listCmd.isIso()).thenReturn(isIso);
 +        Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
 +        SearchBuilder<GuestOSCategoryVO> searchBuilder = 
Mockito.mock(SearchBuilder.class);
 +        Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
 +        SearchCriteria<GuestOSCategoryVO> searchCriteria = 
Mockito.mock(SearchCriteria.class);
 +        
Mockito.when(guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
 +        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
 +        Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, 
arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
 +        Pair<List<GuestOSCategoryVO>, Integer> mockResult = new 
Pair<>(Arrays.asList(), 0);
 +        when(guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), 
Mockito.any())).thenReturn(mockResult);
 +        mockGuestOsJoin();
 +        Pair<List<? extends GuestOsCategory>, Integer> result = 
spy.listGuestOSCategoriesByCriteria(listCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(0, result.second().intValue());
 +        Assert.assertEquals(0, result.first().size());
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).createSearchBuilder();
 +        Mockito.verify(templateDao, 
Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
 +    }
 +
 +    @Test
 +    public void testListGuestOSCategoriesByCriteria_FilterById() {
 +        ListGuestOsCategoriesCmd listCmd = 
Mockito.mock(ListGuestOsCategoriesCmd.class);
 +        GuestOSCategoryVO guestOSCategory = 
Mockito.mock(GuestOSCategoryVO.class);
 +        Long id = 1L;
 +        Mockito.when(listCmd.getId()).thenReturn(id);
 +        Mockito.when(listCmd.getZoneId()).thenReturn(null);
 +        Mockito.when(listCmd.isIso()).thenReturn(null);
 +        Mockito.when(listCmd.isVnf()).thenReturn(null);
 +        SearchBuilder<GuestOSCategoryVO> searchBuilder = 
Mockito.mock(SearchBuilder.class);
 +        Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
 +        SearchCriteria<GuestOSCategoryVO> searchCriteria = 
Mockito.mock(SearchCriteria.class);
 +        
Mockito.when(guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
 +        Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
 +        Pair<List<GuestOSCategoryVO>, Integer> mockResult = new 
Pair<>(Arrays.asList(guestOSCategory), 1);
 +        
Mockito.when(guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), 
Mockito.any())).thenReturn(mockResult);
 +        mockGuestOsJoin();
 +        Pair<List<? extends GuestOsCategory>, Integer> result = 
spy.listGuestOSCategoriesByCriteria(listCmd);
 +        Assert.assertNotNull(result);
 +        Assert.assertEquals(1, result.second().intValue());
 +        Assert.assertEquals(1, result.first().size());
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).createSearchBuilder();
 +        Mockito.verify(searchCriteria, Mockito.times(1)).setParameters("id", 
id);
 +        Mockito.verify(guestOSCategoryDao, 
Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
 +
 +    }
 +
 +    @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);
 +    }
++
+     // ============= 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(_accountMgr.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>, 
java.util.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(_accountMgr.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