Skip to content

Commit a4b102f

Browse files
authored
Fix skip DRS for a VM (#12994)
1 parent c8b7816 commit a4b102f

4 files changed

Lines changed: 57 additions & 39 deletions

File tree

engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,6 @@ public interface ResourceDetailsDao<R extends ResourceDetail> extends GenericDao
103103
long batchExpungeForResources(List<Long> ids, Long batchSize);
104104

105105
String getActualValue(ResourceDetail resourceDetail);
106+
107+
List<R> listDetailsForResourceIdsAndKey(List<Long> resourceIds, String key);
106108
}

engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
// under the License.
1717
package org.apache.cloudstack.resourcedetail;
1818

19+
import java.util.Collections;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.stream.Collectors;
2324

25+
import com.cloud.utils.StringUtils;
2426
import org.apache.commons.collections.CollectionUtils;
2527

2628
import com.cloud.utils.crypt.DBEncryptionUtil;
@@ -47,6 +49,7 @@ public abstract class ResourceDetailsDaoBase<R extends ResourceDetail> extends G
4749
public ResourceDetailsDaoBase() {
4850
AllFieldsSearch = createSearchBuilder();
4951
AllFieldsSearch.and("resourceId", AllFieldsSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
52+
AllFieldsSearch.and("resourceIdIn", AllFieldsSearch.entity().getResourceId(), SearchCriteria.Op.IN);
5053
AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), SearchCriteria.Op.EQ);
5154
AllFieldsSearch.and("value", AllFieldsSearch.entity().getValue(), SearchCriteria.Op.EQ);
5255
// FIXME SnapshotDetailsVO doesn't have a display field
@@ -246,4 +249,15 @@ public String getActualValue(ResourceDetail resourceDetail) {
246249
}
247250
return resourceDetail.getValue();
248251
}
252+
253+
@Override
254+
public List<R> listDetailsForResourceIdsAndKey(List<Long> resourceIds, String key) {
255+
if (CollectionUtils.isEmpty(resourceIds) || StringUtils.isBlank(key)) {
256+
return Collections.emptyList();
257+
}
258+
SearchCriteria<R> sc = AllFieldsSearch.create();
259+
sc.setParameters("name", key);
260+
sc.setParameters("resourceIdIn", resourceIds.toArray());
261+
return search(sc, null);
262+
}
249263
}

server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@
5151
import com.cloud.utils.db.Transaction;
5252
import com.cloud.utils.db.TransactionCallback;
5353
import com.cloud.utils.exception.CloudRuntimeException;
54+
import com.cloud.vm.UserVmDetailVO;
5455
import com.cloud.vm.VMInstanceVO;
5556
import com.cloud.vm.VirtualMachine;
5657
import com.cloud.vm.VirtualMachineProfile;
5758
import com.cloud.vm.VirtualMachineProfileImpl;
5859
import com.cloud.vm.VmDetailConstants;
60+
import com.cloud.vm.dao.UserVmDetailsDao;
5961
import com.cloud.vm.dao.VMInstanceDao;
6062
import org.apache.cloudstack.api.ApiCommandResourceType;
6163
import org.apache.cloudstack.api.ApiConstants;
@@ -78,7 +80,6 @@
7880
import org.apache.cloudstack.jobs.JobInfo;
7981
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
8082
import org.apache.commons.collections.CollectionUtils;
81-
import org.apache.commons.collections.MapUtils;
8283
import org.apache.commons.lang3.time.DateUtils;
8384

8485
import javax.inject.Inject;
@@ -134,6 +135,9 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
134135
@Inject
135136
ServiceOfferingDao serviceOfferingDao;
136137

138+
@Inject
139+
UserVmDetailsDao userVmDetailsDao;
140+
137141
@Inject
138142
ManagementServer managementServer;
139143

@@ -475,12 +479,16 @@ private Pair<Map<Long, List<? extends Host>>, Map<Long, Map<Host, Boolean>>> get
475479
Map<Long, List<? extends Host>> vmToCompatibleHostsCache = new HashMap<>();
476480
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = new HashMap<>();
477481

482+
List<Long> vmIds = vmList.stream().map(VirtualMachine::getId).collect(Collectors.toList());
483+
Set<Long> skipDrsVmIds = userVmDetailsDao.listDetailsForResourceIdsAndKey(vmIds, VmDetailConstants.SKIP_DRS)
484+
.stream().filter(d -> "true".equalsIgnoreCase(d.getValue()))
485+
.map(UserVmDetailVO::getResourceId)
486+
.collect(Collectors.toSet());
487+
478488
for (VirtualMachine vm : vmList) {
479489
// Skip ineligible VMs
480-
if (vm.getType().isUsedBySystem() ||
481-
vm.getState() != VirtualMachine.State.Running ||
482-
(MapUtils.isNotEmpty(vm.getDetails()) &&
483-
"true".equalsIgnoreCase(vm.getDetails().get(VmDetailConstants.SKIP_DRS)))) {
490+
if (shouldSkipVMForDRS(vm, skipDrsVmIds)) {
491+
logger.debug("Skipping VM {} for DRS as it is ineligible.", vm);
484492
continue;
485493
}
486494

@@ -607,7 +615,7 @@ Pair<VirtualMachine, Host> getBestMigration(Cluster cluster, ClusterDrsAlgorithm
607615
ExcludeList excludes = vmToExcludesMap.get(vm.getId());
608616

609617
ServiceOffering serviceOffering = vmIdServiceOfferingMap.get(vm.getId());
610-
if (skipDrs(vm, compatibleHosts, serviceOffering)) {
618+
if (CollectionUtils.isEmpty(compatibleHosts) || serviceOffering == null) {
611619
continue;
612620
}
613621

@@ -633,21 +641,11 @@ Pair<VirtualMachine, Host> getBestMigration(Cluster cluster, ClusterDrsAlgorithm
633641
return bestMigration;
634642
}
635643

636-
private boolean skipDrs(VirtualMachine vm, List<? extends Host> compatibleHosts, ServiceOffering serviceOffering) {
644+
private boolean shouldSkipVMForDRS(VirtualMachine vm, Set<Long> skipDrsVmIds) {
637645
if (vm.getType().isUsedBySystem() || vm.getState() != VirtualMachine.State.Running) {
638646
return true;
639647
}
640-
if (MapUtils.isNotEmpty(vm.getDetails()) &&
641-
"true".equalsIgnoreCase(vm.getDetails().get(VmDetailConstants.SKIP_DRS))) {
642-
return true;
643-
}
644-
if (CollectionUtils.isEmpty(compatibleHosts)) {
645-
return true;
646-
}
647-
if (serviceOffering == null) {
648-
return true;
649-
}
650-
return false;
648+
return skipDrsVmIds.contains(vm.getId());
651649
}
652650

653651
private Pair<double[], Map<Long, Integer>> getBaseMetricsArrayAndHostIdIndexMap(

server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@
4040
import com.cloud.utils.Ternary;
4141
import com.cloud.utils.db.GlobalLock;
4242
import com.cloud.utils.exception.CloudRuntimeException;
43+
import com.cloud.vm.UserVmDetailVO;
4344
import com.cloud.vm.VMInstanceVO;
4445
import com.cloud.vm.VirtualMachine;
4546
import com.cloud.vm.VmDetailConstants;
47+
import com.cloud.vm.dao.UserVmDetailsDao;
4648
import com.cloud.vm.dao.VMInstanceDao;
4749
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
4850
import org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
@@ -121,6 +123,9 @@ public class ClusterDrsServiceImplTest {
121123
@Mock
122124
private AffinityGroupVMMapDao affinityGroupVMMapDao;
123125

126+
@Mock
127+
private UserVmDetailsDao userVmDetailsDao;
128+
124129
@Spy
125130
@InjectMocks
126131
private ClusterDrsServiceImpl clusterDrsService = new ClusterDrsServiceImpl();
@@ -294,6 +299,8 @@ public void testGetDrsPlanWithSystemVMs() throws ConfigurationException {
294299

295300
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
296301
assertEquals(0, result.size());
302+
Mockito.verify(managementServer, Mockito.never()).listHostsForMigrationOfVM(
303+
Mockito.eq(systemVm), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyList());
297304
}
298305

299306
@Test
@@ -334,6 +341,8 @@ public void testGetDrsPlanWithNonRunningVMs() throws ConfigurationException {
334341

335342
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
336343
assertEquals(0, result.size());
344+
Mockito.verify(managementServer, Mockito.never()).listHostsForMigrationOfVM(
345+
Mockito.eq(stoppedVm), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyList());
337346
}
338347

339348
@Test
@@ -350,9 +359,6 @@ public void testGetDrsPlanWithSkipDrsFlag() throws ConfigurationException {
350359
Mockito.when(skippedVm.getHostId()).thenReturn(1L);
351360
Mockito.when(skippedVm.getType()).thenReturn(VirtualMachine.Type.User);
352361
Mockito.when(skippedVm.getState()).thenReturn(VirtualMachine.State.Running);
353-
Map<String, String> details = new HashMap<>();
354-
details.put(VmDetailConstants.SKIP_DRS, "true");
355-
Mockito.when(skippedVm.getDetails()).thenReturn(details);
356362

357363
List<HostVO> hostList = new ArrayList<>();
358364
hostList.add(host1);
@@ -370,13 +376,21 @@ public void testGetDrsPlanWithSkipDrsFlag() throws ConfigurationException {
370376
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
371377
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
372378

379+
// Return the SKIP_DRS detail for skippedVm so the flag is actually honoured
380+
UserVmDetailVO skipDrsDetail = new UserVmDetailVO(1L, VmDetailConstants.SKIP_DRS, "true", true);
381+
Mockito.when(userVmDetailsDao.listDetailsForResourceIdsAndKey(Mockito.anyList(),
382+
Mockito.eq(VmDetailConstants.SKIP_DRS))).thenReturn(List.of(skipDrsDetail));
383+
373384
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
374385
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
375386
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
376387
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
377388

378389
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
379390
assertEquals(0, result.size());
391+
// Verify the VM was skipped before any host-compatibility lookup was attempted
392+
Mockito.verify(managementServer, Mockito.never()).listHostsForMigrationOfVM(
393+
Mockito.eq(skippedVm), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyList());
380394
}
381395

382396
@Test
@@ -393,7 +407,6 @@ public void testGetDrsPlanWithNoCompatibleHosts() throws ConfigurationException
393407
Mockito.when(vm1.getHostId()).thenReturn(1L);
394408
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
395409
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
396-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
397410

398411
List<HostVO> hostList = new ArrayList<>();
399412
hostList.add(host1);
@@ -418,6 +431,10 @@ public void testGetDrsPlanWithNoCompatibleHosts() throws ConfigurationException
418431
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
419432
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
420433
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
434+
// Return a Ternary with an empty suitable-hosts list to exercise the "no compatible hosts" path
435+
Mockito.when(managementServer.listHostsForMigrationOfVM(Mockito.eq(vm1), Mockito.anyLong(),
436+
Mockito.anyLong(), Mockito.any(), Mockito.anyList()))
437+
.thenReturn(new Ternary<>(new Pair<>(Collections.emptyList(), 0), Collections.emptyList(), Collections.emptyMap()));
421438

422439
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
423440
assertEquals(0, result.size());
@@ -438,7 +455,6 @@ public void testGetDrsPlanWithExceptionInCompatibilityCheck() throws Configurati
438455
Mockito.when(vm1.getHostId()).thenReturn(1L);
439456
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
440457
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
441-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
442458

443459
List<HostVO> hostList = new ArrayList<>();
444460
hostList.add(host1);
@@ -463,6 +479,10 @@ public void testGetDrsPlanWithExceptionInCompatibilityCheck() throws Configurati
463479
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
464480
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
465481
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
482+
// Throw an explicit exception so the catch-and-log path is exercised intentionally
483+
Mockito.when(managementServer.listHostsForMigrationOfVM(Mockito.eq(vm1), Mockito.anyLong(),
484+
Mockito.anyLong(), Mockito.any(), Mockito.anyList()))
485+
.thenThrow(new RuntimeException("Simulated host compatibility check failure"));
466486

467487
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
468488
assertEquals(0, result.size());
@@ -484,7 +504,6 @@ public void testGetDrsPlanWithNoBestMigration() throws ConfigurationException {
484504
Mockito.when(vm1.getHostId()).thenReturn(1L);
485505
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
486506
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
487-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
488507

489508
List<HostVO> hostList = new ArrayList<>();
490509
hostList.add(host1);
@@ -539,14 +558,12 @@ public void testGetDrsPlanWithMultipleIterations() throws ConfigurationException
539558
Mockito.when(vm1.getHostId()).thenReturn(1L);
540559
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
541560
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
542-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
543561

544562
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
545563
Mockito.when(vm2.getId()).thenReturn(2L);
546564
Mockito.when(vm2.getHostId()).thenReturn(1L);
547565
Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
548566
Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
549-
Mockito.when(vm2.getDetails()).thenReturn(Collections.emptyMap());
550567

551568
List<HostVO> hostList = new ArrayList<>();
552569
hostList.add(host1);
@@ -619,7 +636,6 @@ public void testGetDrsPlanWithMigrationToOriginalHost() throws ConfigurationExce
619636
Mockito.when(vm1.getHostId()).thenReturn(1L);
620637
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
621638
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
622-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
623639

624640
List<HostVO> hostList = new ArrayList<>();
625641
hostList.add(host1);
@@ -811,15 +827,9 @@ public void testGetBestMigration() throws ConfigurationException {
811827

812828
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
813829
Mockito.when(vm1.getId()).thenReturn(1L);
814-
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
815-
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
816-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
817830

818831
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
819832
Mockito.when(vm2.getId()).thenReturn(2L);
820-
Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
821-
Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
822-
Mockito.when(vm2.getDetails()).thenReturn(Collections.emptyMap());
823833

824834
List<VirtualMachine> vmList = new ArrayList<>();
825835
vmList.add(vm1);
@@ -890,15 +900,9 @@ public void testGetBestMigrationDifferentCluster() throws ConfigurationException
890900

891901
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
892902
Mockito.when(vm1.getId()).thenReturn(1L);
893-
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
894-
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
895-
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
896903

897904
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
898905
Mockito.when(vm2.getId()).thenReturn(2L);
899-
Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
900-
Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
901-
Mockito.when(vm2.getDetails()).thenReturn(Collections.emptyMap());
902906

903907
List<VirtualMachine> vmList = new ArrayList<>();
904908
vmList.add(vm1);

0 commit comments

Comments
 (0)