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

DaanHoogland pushed a commit to branch fix-vm-deployment-issue
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit f603e1b33881fa30490788e816fd70ae99f39d5f
Author: Daan Hoogland <[email protected]>
AuthorDate: Sat Aug 22 20:33:09 2026 +0200

    Scope host tag-rule matches to deployment plan in allocators
---
 .../src/main/java/com/cloud/host/dao/HostDao.java  |  1 +
 .../main/java/com/cloud/host/dao/HostDaoImpl.java  | 15 ++++
 .../manager/allocator/impl/RandomAllocator.java    |  2 +-
 .../allocator/impl/RandomAllocatorTest.java        | 42 +++++++++++
 .../manager/allocator/impl/FirstFitAllocator.java  |  4 +-
 .../allocator/impl/FirstFitAllocatorTest.java      | 85 ++++++++++++++++++++++
 6 files changed, 146 insertions(+), 3 deletions(-)

diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java 
b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
index 57ac35d3eb1..f9e0288cba0 100644
--- a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
+++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java
@@ -182,6 +182,7 @@ public interface HostDao extends GenericDao<HostVO, Long>, 
StateDao<Status, Stat
     List<String> listOrderedHostsHypervisorVersionsInDatacenter(long 
datacenterId, HypervisorType hypervisorType);
 
     List<HostVO> findHostsWithTagRuleThatMatchComputeOferringTags(String 
computeOfferingTags);
+    List<HostVO> findHostsWithTagRuleThatMatchComputeOferringTags(String 
computeOfferingTags, Long clusterId, Long podId, Long dcId);
 
     List<Long> findClustersThatMatchHostTagRule(String computeOfferingTags);
 
diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
index 8c3604d352b..cca57129de5 100644
--- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java
@@ -1456,6 +1456,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, 
Long> implements HostDao
         }
     }
 
+    @Override
     public List<HostVO> 
findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags) {
         List<HostTagVO> hostTagVOList = _hostTagsDao.findHostRuleTags();
         List<HostVO> result = new ArrayList<>();
@@ -1468,6 +1469,20 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, 
Long> implements HostDao
         return result;
     }
 
+    @Override
+    public List<HostVO> 
findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags, 
Long clusterId, Long podId, Long dcId) {
+        List<HostVO> hosts = 
findHostsWithTagRuleThatMatchComputeOferringTags(computeOfferingTags);
+        if (dcId == null && podId == null && clusterId == null) {
+            return hosts;
+        }
+
+        return hosts.stream()
+                .filter(host -> host != null && (dcId == null || 
host.getDataCenterId() == dcId))
+                .filter(host -> podId == null || 
Objects.equals(host.getPodId(), podId))
+                .filter(host -> clusterId == null || 
Objects.equals(host.getClusterId(), clusterId))
+                .collect(Collectors.toList());
+    }
+
     public List<Long> findClustersThatMatchHostTagRule(String 
computeOfferingTags) {
         Set<Long> result = new HashSet<>();
         List<HostVO> hosts = 
findHostsWithTagRuleThatMatchComputeOferringTags(computeOfferingTags);
diff --git 
a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
 
b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
index 42129944a19..d8455737cfb 100644
--- 
a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
+++ 
b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java
@@ -122,7 +122,7 @@ public class RandomAllocator extends AdapterBase implements 
HostAllocator {
                 hostsCopy = _hostDao.listAllHostsThatHaveNoRuleTag(type, 
clusterId, podId, dcId);
             }
         }
-        hostsCopy = ListUtils.union(hostsCopy, 
_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringHostTag));
+        hostsCopy = ListUtils.union(hostsCopy, 
_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringHostTag, 
clusterId, podId, dcId));
 
         if (hostsCopy.isEmpty()) {
             logger.info("No suitable host found for VM [{}] in {}.", 
vmProfile, hostTag);
diff --git 
a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java
 
b/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java
index 538d7157184..6dbde0131e1 100644
--- 
a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java
+++ 
b/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java
@@ -28,15 +28,24 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import com.cloud.deploy.DeploymentPlan;
+import com.cloud.deploy.DeploymentPlanner.ExcludeList;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.utils.Pair;
+import com.cloud.vm.VirtualMachineProfile;
 
 @RunWith(MockitoJUnitRunner.class)
 public class RandomAllocatorTest {
 
     @Mock
     HostDao hostDao;
+    @Mock
+    CapacityManager capacityManager;
     @InjectMocks
     RandomAllocator randomAllocator;
 
@@ -77,4 +86,37 @@ public class RandomAllocatorTest {
         Assert.assertFalse(CollectionUtils.isEmpty(result));
         Assert.assertEquals(1, result.size());
     }
+
+    @Test
+    public void testAllocateToUsesScopedRuleTagLookup() {
+        Host.Type type = Host.Type.Routing;
+        long dcId = 1L;
+        Long podId = 2L;
+        Long clusterId = 3L;
+        String offeringTag = "compute";
+
+        DeploymentPlan plan = Mockito.mock(DeploymentPlan.class);
+        Mockito.when(plan.getDataCenterId()).thenReturn(dcId);
+        Mockito.when(plan.getPodId()).thenReturn(podId);
+        Mockito.when(plan.getClusterId()).thenReturn(clusterId);
+
+        VirtualMachineProfile vmProfile = 
Mockito.mock(VirtualMachineProfile.class);
+        ServiceOffering offering = Mockito.mock(ServiceOffering.class);
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        Mockito.when(vmProfile.getServiceOffering()).thenReturn(offering);
+        Mockito.when(vmProfile.getTemplate()).thenReturn(template);
+        Mockito.when(offering.getHostTag()).thenReturn(offeringTag);
+
+        HostVO host = Mockito.mock(HostVO.class);
+        List<Host> hosts = List.of(host);
+        Mockito.when(hostDao.listByHostTag(type, clusterId, podId, dcId, 
offeringTag)).thenReturn(List.of(host));
+        
Mockito.when(hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag,
 clusterId, podId, dcId)).thenReturn(new ArrayList<>());
+        
Mockito.when(capacityManager.checkIfHostHasCpuCapabilityAndCapacity(host, 
offering, true)).thenReturn(new Pair<>(true, true));
+
+        List<Host> result = randomAllocator.allocateTo(vmProfile, plan, type, 
new ExcludeList(), hosts, 1, true);
+
+        Assert.assertEquals(1, result.size());
+        
Mockito.verify(hostDao).findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag,
 clusterId, podId, dcId);
+        Mockito.verify(hostDao, 
Mockito.never()).findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag);
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
 
b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
index 590db3406c2..17b9834ec71 100644
--- 
a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
+++ 
b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java
@@ -198,7 +198,7 @@ public class FirstFitAllocator extends AdapterBase 
implements HostAllocator {
             clusterHosts.retainAll(hostsMatchingUefiTag);
         }
 
-        
clusterHosts.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering));
+        
clusterHosts.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering,
 clusterId, podId, dcId));
 
 
         if (clusterHosts.isEmpty()) {
@@ -274,7 +274,7 @@ public class FirstFitAllocator extends AdapterBase 
implements HostAllocator {
             }
         }
 
-        
hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering));
+        
hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering,
 clusterId, podId, dcId));
 
         if (!hostsCopy.isEmpty()) {
             suitableHosts = allocateTo(plan, offering, template, avoid, 
hostsCopy, returnUpTo, considerReservedCapacity, account);
diff --git 
a/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java
 
b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java
new file mode 100644
index 00000000000..4fed8662ef7
--- /dev/null
+++ 
b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.manager.allocator.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.deploy.DeploymentPlan;
+import com.cloud.deploy.DeploymentPlanner.ExcludeList;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.user.Account;
+import com.cloud.vm.VirtualMachineProfile;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FirstFitAllocatorTest {
+
+    @Mock
+    HostDao hostDao;
+    @Spy
+    @InjectMocks
+    FirstFitAllocator firstFitAllocator;
+
+    @Test
+    public void testAllocateToWithHostsUsesScopedRuleTagLookup() {
+        Host.Type type = Host.Type.Routing;
+        long dcId = 1L;
+        Long podId = 2L;
+        Long clusterId = 3L;
+        String offeringTag = "compute";
+
+        DeploymentPlan plan = Mockito.mock(DeploymentPlan.class);
+        Mockito.when(plan.getDataCenterId()).thenReturn(dcId);
+        Mockito.when(plan.getPodId()).thenReturn(podId);
+        Mockito.when(plan.getClusterId()).thenReturn(clusterId);
+
+        VirtualMachineProfile vmProfile = 
Mockito.mock(VirtualMachineProfile.class);
+        ServiceOffering offering = Mockito.mock(ServiceOffering.class);
+        VMTemplateVO template = Mockito.mock(VMTemplateVO.class);
+        Account account = Mockito.mock(Account.class);
+        Mockito.when(vmProfile.getServiceOffering()).thenReturn(offering);
+        Mockito.when(vmProfile.getTemplate()).thenReturn(template);
+        Mockito.when(vmProfile.getOwner()).thenReturn(account);
+        Mockito.when(offering.getHostTag()).thenReturn(offeringTag);
+
+        HostVO host = Mockito.mock(HostVO.class);
+        List<Host> selectedHosts = List.of(host);
+        Mockito.when(hostDao.listByHostTag(type, clusterId, podId, dcId, 
offeringTag)).thenReturn(List.of(host));
+        
Mockito.when(hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag,
 clusterId, podId, dcId)).thenReturn(new ArrayList<>());
+        Mockito.doReturn(selectedHosts).when(firstFitAllocator).allocateTo(
+                Mockito.eq(plan), Mockito.eq(offering), Mockito.eq(template), 
Mockito.any(ExcludeList.class), Mockito.anyList(), Mockito.eq(1), 
Mockito.eq(true), Mockito.eq(account));
+
+        List<Host> result = firstFitAllocator.allocateTo(vmProfile, plan, 
type, new ExcludeList(), selectedHosts, 1, true);
+
+        Assert.assertEquals(1, result.size());
+        
Mockito.verify(hostDao).findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag,
 clusterId, podId, dcId);
+        Mockito.verify(hostDao, 
Mockito.never()).findHostsWithTagRuleThatMatchComputeOferringTags(offeringTag);
+    }
+}

Reply via email to