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