From d5e5b48e906cc4a7e547a9c99e6117fa27d4cee1 Mon Sep 17 00:00:00 2001 From: Ranier Amorim Date: Mon, 4 May 2026 15:12:28 -0300 Subject: [PATCH] fix: skip VMs with host passthrough during DRS plan generation When iterating over VMs in getBestMigration, the call to listHostsForMigrationOfVM throws InvalidParameterValueException for VMs using a vGPU passthrough profile. This exception was not caught inside the loop, causing it to propagate up and abort the entire DRS plan generation for the cluster, leaving all other eligible VMs without a migration plan. Fix wraps the listHostsForMigrationOfVM call in a try-catch block. When the exception is caught, the VM is skipped with a debug log message and the loop continues evaluating the remaining VMs. Also adds unit test testGetBestMigrationSkipsPassthroughVm to verify that a VM throwing InvalidParameterValueException is skipped while other eligible VMs are still considered for migration. Fixes: #13098 --- .../cluster/ClusterDrsServiceImpl.java | 12 ++++-- .../cluster/ClusterDrsServiceImplTest.java | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java index a662d47d4541..dcde1e219754 100644 --- a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java @@ -449,9 +449,15 @@ Pair getBestMigration(Cluster cluster, ClusterDrsAlgorithm ) { continue; } - Ternary, Integer>, List, Map> hostsForMigrationOfVM = managementServer - .listHostsForMigrationOfVM( - vm, 0L, 500L, null, vmList); + Ternary, Integer>, List, Map> hostsForMigrationOfVM; + try { + hostsForMigrationOfVM = managementServer + .listHostsForMigrationOfVM( + vm, 0L, 500L, null, vmList); + } catch (InvalidParameterValueException e) { + logger.debug("Skipping VM {} for DRS, unsupported operation: {}", vm, e.getMessage()); + continue; + } List compatibleDestinationHosts = hostsForMigrationOfVM.first().first(); List suitableDestinationHosts = hostsForMigrationOfVM.second(); diff --git a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java index 81aac9e4b54d..381fb08b08e1 100644 --- a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java @@ -404,6 +404,49 @@ public void testGetBestMigration() throws ConfigurationException { assertEquals(vm1, bestMigration.first()); } + @Test + public void testGetBestMigrationSkipsPassthroughVm() throws ConfigurationException { + ClusterVO cluster = Mockito.mock(ClusterVO.class); + Mockito.when(cluster.getId()).thenReturn(1L); + + HostVO destHost = Mockito.mock(HostVO.class); + Mockito.when(destHost.getClusterId()).thenReturn(1L); + + VMInstanceVO vmPassthrough = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmPassthrough.getId()).thenReturn(1L); + Mockito.when(vmPassthrough.getType()).thenReturn(VirtualMachine.Type.User); + Mockito.when(vmPassthrough.getState()).thenReturn(VirtualMachine.State.Running); + Mockito.when(vmPassthrough.getDetails()).thenReturn(Collections.emptyMap()); + + VMInstanceVO vmNormal = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmNormal.getId()).thenReturn(2L); + Mockito.when(vmNormal.getType()).thenReturn(VirtualMachine.Type.User); + Mockito.when(vmNormal.getState()).thenReturn(VirtualMachine.State.Running); + Mockito.when(vmNormal.getDetails()).thenReturn(Collections.emptyMap()); + + List vmList = new ArrayList<>(); + vmList.add(vmPassthrough); + vmList.add(vmNormal); + + ServiceOffering serviceOffering = Mockito.mock(ServiceOffering.class); + Map vmIdServiceOfferingMap = new HashMap<>(); + vmIdServiceOfferingMap.put(vmPassthrough.getId(), serviceOffering); + vmIdServiceOfferingMap.put(vmNormal.getId(), serviceOffering); + + Mockito.when(managementServer.listHostsForMigrationOfVM(vmPassthrough, 0L, 500L, null, vmList)) + .thenThrow(new InvalidParameterValueException("Unsupported operation, VM uses host passthrough, cannot migrate")); + Mockito.when(managementServer.listHostsForMigrationOfVM(vmNormal, 0L, 500L, null, vmList)).thenReturn( + new Ternary<>(new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost, false))); + Mockito.when(balancedAlgorithm.getMetrics(cluster, vmNormal, serviceOffering, destHost, new HashMap<>(), + new HashMap<>(), false)).thenReturn(new Ternary<>(1.0, 0.5, 1.5)); + + Pair bestMigration = clusterDrsService.getBestMigration(cluster, balancedAlgorithm, + vmList, vmIdServiceOfferingMap, new HashMap<>(), new HashMap<>()); + + assertEquals(vmNormal, bestMigration.first()); + assertEquals(destHost, bestMigration.second()); + } + @Test public void testGetBestMigrationDifferentCluster() throws ConfigurationException { ClusterVO cluster = Mockito.mock(ClusterVO.class);