From b26ad3931eab27918433465d11a9769d0f42d450 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:04:22 +0000 Subject: [PATCH 1/6] Initial plan From b43afc02e8296fa48a6dbfc865231914ddd37440 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:20:54 +0000 Subject: [PATCH 2/6] Fix domain-dedicated resource eligibility in ImplicitDedicationPlanner (issue #5803) - Fix findAvoidSetForNonExplicitUserVM to check domain/account dedication before adding pods/clusters/hosts to the avoid list - Domain-dedicated resources are now accessible to users in the same domain - Add 4 unit tests covering same-domain, different-domain, same-account, different-account scenarios - Use mutable ArrayList in test return values to support list.clear() calls in production code --- .../deploy/DeploymentPlanningManagerImpl.java | 68 +++++- .../DeploymentPlanningManagerImplTest.java | 207 ++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 230f97f6fd35..b830fbf8e01d 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -963,8 +963,74 @@ private void findAvoiSetForRouterVM(ExcludeList avoids, VirtualMachine vm, List< } private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine vm, List allPodsInDc, List allClustersInDc, List allHostsInDc) { + long vmAccountId = vm.getAccountId(); + long vmDomainId = vm.getDomainId(); + + List allPodsFromDedicatedID = new ArrayList<>(); + List allClustersFromDedicatedID = new ArrayList<>(); + List allHostsFromDedicatedID = new ArrayList<>(); + + // Check if the VM owner's domain has explicit dedication affinity group mappings. + // If so, resources dedicated to that domain are accessible to the VM owner (fixes + // issue where users in a domain-dedicated pod could not deploy with ImplicitDedicationPlanner). + List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); + + List tempStorage; + + if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { + // No domain-level affinity groups: search for resources dedicated to this specific account + tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allPodsFromDedicatedID.add(vo.getPodId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allClustersFromDedicatedID.add(vo.getClusterId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allHostsFromDedicatedID.add(vo.getHostId()); + } + + allPodsInDc.removeAll(allPodsFromDedicatedID); + allClustersInDc.removeAll(allClustersFromDedicatedID); + allHostsInDc.removeAll(allHostsFromDedicatedID); + } else { + // Domain has explicit dedication affinity groups: search for resources dedicated to this domain + tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allPodsFromDedicatedID.add(vo.getPodId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allClustersFromDedicatedID.add(vo.getClusterId()); + } + + tempStorage.clear(); + tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, null, null, + new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); + for (DedicatedResourceVO vo : tempStorage) { + allHostsFromDedicatedID.add(vo.getHostId()); + } + + allPodsInDc.removeAll(allPodsFromDedicatedID); + allClustersInDc.removeAll(allClustersFromDedicatedID); + allHostsInDc.removeAll(allHostsFromDedicatedID); + } + logger.debug(() -> LogUtils.logGsonWithoutException("Adding pods [%s], clusters [%s] and hosts [%s] to the avoid list in the deploy process of user VM [%s], " - + "because this VM is not explicitly dedicated to these components.", allPodsInDc, allClustersInDc, allHostsInDc, vm)); + + "because this VM is not dedicated to these components.", allPodsInDc, allClustersInDc, allHostsInDc, vm)); avoids.addPodList(allPodsInDc); avoids.addClusterList(allClustersInDc); avoids.addHostList(allHostsInDc); diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index 5b03260d2d66..fd6f86339285 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -27,6 +27,7 @@ import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; @@ -73,6 +74,7 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.db.Filter; import com.cloud.vm.DiskProfile; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -82,6 +84,7 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; @@ -227,6 +230,9 @@ public class DeploymentPlanningManagerImplTest { @Inject HostPodDao _podDao; + @Inject + AffinityGroupDomainMapDao _affinityGroupDomainMapDao; + private static final long dataCenterId = 1L; private static final long instanceId = 123L; private static final long hostId = 0L; @@ -937,6 +943,207 @@ private void assertAvoidIsEmpty(ExcludeList avoids, boolean isDcEmpty, boolean i Assert.assertEquals(isHostsEmpty, CollectionUtils.isEmpty(avoids.getHostsToAvoid())); } + /** + * Helper: set up mocks for checkForNonDedicatedResources tests. + * Sets up a non-root, non-explicit user VM with the given domain and account. + */ + private VMInstanceVO setupNonExplicitUserVmMocks(long vmDomainId, long vmAccountId, DataCenter mockDc) { + // Zone is not dedicated + Mockito.when(_dedicatedDao.findByZoneId(Mockito.anyLong())).thenReturn(null); + // Not root admin + Mockito.when(_accountMgr.isRootAdmin(Mockito.anyLong())).thenReturn(false); + // No explicit dedication affinity group for the VM + Mockito.when(_affinityGroupVMMapDao.findByVmIdType(Mockito.anyLong(), Mockito.eq("ExplicitDedication"))) + .thenReturn(new ArrayList<>()); + + VMInstanceVO mockVm = Mockito.mock(VMInstanceVO.class); + Mockito.when(mockVm.getType()).thenReturn(VirtualMachine.Type.User); + Mockito.when(mockVm.getDomainId()).thenReturn(vmDomainId); + Mockito.when(mockVm.getAccountId()).thenReturn(vmAccountId); + Mockito.when(mockVm.getId()).thenReturn(instanceId); + + // No dedicated clusters or hosts in DC + Mockito.when(_clusterDao.listAllClusterIds(Mockito.anyLong())).thenReturn(new ArrayList<>()); + Mockito.when(_dedicatedDao.listAllClusters()).thenReturn(new ArrayList<>()); + Mockito.when(_hostDao.listAllHosts(Mockito.anyLong())).thenReturn(new ArrayList<>()); + Mockito.when(_dedicatedDao.listAllHosts()).thenReturn(new ArrayList<>()); + Mockito.when(_dedicatedDao.searchDedicatedClusters(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(new ArrayList<>(), 0)); + Mockito.when(_dedicatedDao.searchDedicatedHosts(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(new Pair<>(new ArrayList<>(), 0)); + + return mockVm; + } + + /** + * Issue #5803: A regular user in the same domain as a domain-dedicated pod + * must be allowed to deploy on that pod (i.e., the pod must NOT appear in the avoid list). + */ + @Test + public void checkForNonDedicatedResources_domainDedicatedPod_sameDomainUser_podNotInAvoidList() { + long vmDomainId = 10L; + long vmAccountId = 200L; + long dedicatedPodId = 42L; + + DataCenter mockDc = Mockito.mock(DataCenter.class); + Mockito.when(mockDc.getId()).thenReturn(dataCenterId); + + VMInstanceVO mockVm = setupNonExplicitUserVmMocks(vmDomainId, vmAccountId, mockDc); + + // Pod in DC and it is dedicated + List podsInDc = new ArrayList<>(Arrays.asList(dedicatedPodId)); + Mockito.when(_podDao.listAllPods(dataCenterId)).thenReturn(podsInDc); + Mockito.when(_dedicatedDao.listAllPods()).thenReturn(new ArrayList<>(Arrays.asList(dedicatedPodId))); + + // Domain has affinity group mappings (pod dedicated to this domain) + AffinityGroupDomainMapVO domainMap = Mockito.mock(AffinityGroupDomainMapVO.class); + Mockito.when(_affinityGroupDomainMapDao.listByDomain(vmDomainId)) + .thenReturn(Arrays.asList(domainMap)); + + // The pod is found when searching by domain (accountId = null) + DedicatedResourceVO dedicatedPod = Mockito.mock(DedicatedResourceVO.class); + Mockito.when(dedicatedPod.getPodId()).thenReturn(dedicatedPodId); + Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), + Mockito.isNull(), Mockito.isNull(), Mockito.any(Filter.class))) + .thenReturn(new Pair<>(new ArrayList<>(Arrays.asList(dedicatedPod)), 1)); + + VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); + Mockito.when(mockProfile.getVirtualMachine()).thenReturn(mockVm); + Mockito.when(mockProfile.getOwner()).thenReturn(Mockito.mock(com.cloud.user.Account.class)); + + ExcludeList avoids = new ExcludeList(); + _dpm.checkForNonDedicatedResources(mockProfile, mockDc, avoids); + + // The dedicated pod belongs to the same domain as the VM owner: must NOT be in avoid list + assertTrue("Domain-dedicated pod should not be in avoid list for same-domain user", + CollectionUtils.isEmpty(avoids.getPodsToAvoid()) || !avoids.getPodsToAvoid().contains(dedicatedPodId)); + } + + /** + * A user from a different domain must NOT be allowed to deploy on a pod dedicated to another domain. + */ + @Test + public void checkForNonDedicatedResources_domainDedicatedPod_differentDomainUser_podInAvoidList() { + long podDomainId = 10L; // domain the pod is dedicated to + long vmDomainId = 20L; // VM owner belongs to a DIFFERENT domain + long vmAccountId = 300L; + long dedicatedPodId = 43L; + + DataCenter mockDc = Mockito.mock(DataCenter.class); + Mockito.when(mockDc.getId()).thenReturn(dataCenterId); + + VMInstanceVO mockVm = setupNonExplicitUserVmMocks(vmDomainId, vmAccountId, mockDc); + + // Pod in DC and it is dedicated (to podDomainId, NOT to vmDomainId) + List podsInDc = new ArrayList<>(Arrays.asList(dedicatedPodId)); + Mockito.when(_podDao.listAllPods(dataCenterId)).thenReturn(podsInDc); + Mockito.when(_dedicatedDao.listAllPods()).thenReturn(new ArrayList<>(Arrays.asList(dedicatedPodId))); + + // VM owner's domain has NO affinity group mappings (no dedicated resources for vmDomainId) + Mockito.when(_affinityGroupDomainMapDao.listByDomain(vmDomainId)) + .thenReturn(new ArrayList<>()); + + // No pods dedicated to the VM owner's account + Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + .thenReturn(new Pair<>(new ArrayList<>(), 0)); + + VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); + Mockito.when(mockProfile.getVirtualMachine()).thenReturn(mockVm); + Mockito.when(mockProfile.getOwner()).thenReturn(Mockito.mock(com.cloud.user.Account.class)); + + ExcludeList avoids = new ExcludeList(); + _dpm.checkForNonDedicatedResources(mockProfile, mockDc, avoids); + + // The pod is dedicated to a different domain: must be in avoid list + assertTrue("Domain-dedicated pod should be in avoid list for different-domain user", + avoids.getPodsToAvoid() != null && avoids.getPodsToAvoid().contains(dedicatedPodId)); + } + + /** + * An account-dedicated pod should be accessible to the owning account + * (no domain-level affinity groups, but account-level dedicated resource exists). + */ + @Test + public void checkForNonDedicatedResources_accountDedicatedPod_sameAccount_podNotInAvoidList() { + long vmDomainId = 10L; + long vmAccountId = 200L; + long dedicatedPodId = 44L; + + DataCenter mockDc = Mockito.mock(DataCenter.class); + Mockito.when(mockDc.getId()).thenReturn(dataCenterId); + + VMInstanceVO mockVm = setupNonExplicitUserVmMocks(vmDomainId, vmAccountId, mockDc); + + // Pod in DC and it is dedicated + List podsInDc = new ArrayList<>(Arrays.asList(dedicatedPodId)); + Mockito.when(_podDao.listAllPods(dataCenterId)).thenReturn(podsInDc); + Mockito.when(_dedicatedDao.listAllPods()).thenReturn(new ArrayList<>(Arrays.asList(dedicatedPodId))); + + // Domain has NO affinity group mappings (account-level dedication, not domain-level) + Mockito.when(_affinityGroupDomainMapDao.listByDomain(vmDomainId)) + .thenReturn(new ArrayList<>()); + + // The pod IS found when searching by account + DedicatedResourceVO dedicatedPod = Mockito.mock(DedicatedResourceVO.class); + Mockito.when(dedicatedPod.getPodId()).thenReturn(dedicatedPodId); + Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + .thenReturn(new Pair<>(new ArrayList<>(Arrays.asList(dedicatedPod)), 1)); + + VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); + Mockito.when(mockProfile.getVirtualMachine()).thenReturn(mockVm); + Mockito.when(mockProfile.getOwner()).thenReturn(Mockito.mock(com.cloud.user.Account.class)); + + ExcludeList avoids = new ExcludeList(); + _dpm.checkForNonDedicatedResources(mockProfile, mockDc, avoids); + + // The pod is dedicated to this account: must NOT be in avoid list + assertTrue("Account-dedicated pod should not be in avoid list for same-account user", + CollectionUtils.isEmpty(avoids.getPodsToAvoid()) || !avoids.getPodsToAvoid().contains(dedicatedPodId)); + } + + /** + * A user whose account is different from the account a pod is dedicated to + * must NOT be able to deploy on that pod. + */ + @Test + public void checkForNonDedicatedResources_accountDedicatedPod_differentAccount_podInAvoidList() { + long vmDomainId = 10L; + long vmAccountId = 300L; // different account + long dedicatedPodId = 45L; + + DataCenter mockDc = Mockito.mock(DataCenter.class); + Mockito.when(mockDc.getId()).thenReturn(dataCenterId); + + VMInstanceVO mockVm = setupNonExplicitUserVmMocks(vmDomainId, vmAccountId, mockDc); + + // Pod in DC and it is dedicated (to a different account) + List podsInDc = new ArrayList<>(Arrays.asList(dedicatedPodId)); + Mockito.when(_podDao.listAllPods(dataCenterId)).thenReturn(podsInDc); + Mockito.when(_dedicatedDao.listAllPods()).thenReturn(new ArrayList<>(Arrays.asList(dedicatedPodId))); + + // Domain has NO affinity group mappings + Mockito.when(_affinityGroupDomainMapDao.listByDomain(vmDomainId)) + .thenReturn(new ArrayList<>()); + + // No pods dedicated to this account + Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + .thenReturn(new Pair<>(new ArrayList<>(), 0)); + + VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); + Mockito.when(mockProfile.getVirtualMachine()).thenReturn(mockVm); + Mockito.when(mockProfile.getOwner()).thenReturn(Mockito.mock(com.cloud.user.Account.class)); + + ExcludeList avoids = new ExcludeList(); + _dpm.checkForNonDedicatedResources(mockProfile, mockDc, avoids); + + // The pod is dedicated to a different account: must be in avoid list + assertTrue("Account-dedicated pod should be in avoid list for different-account user", + avoids.getPodsToAvoid() != null && avoids.getPodsToAvoid().contains(dedicatedPodId)); + } + @Configuration @ComponentScan(basePackageClasses = {DeploymentPlanningManagerImpl.class}, includeFilters = {@Filter(value = TestConfiguration.Library.class, From 630c8616df6a4226de068a5f2e70eef1a82d85cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:23:05 +0000 Subject: [PATCH 3/6] Refactor findAvoidSetForNonExplicitUserVM: remove pagination limit and tempStorage.clear() - Use Filter without offset/limit to retrieve all dedicated resources (not just the first) - Eliminate tempStorage variable and clear() calls to avoid potential side effects - Code is now cleaner and consistent with correct behavior --- .../deploy/DeploymentPlanningManagerImpl.java | 46 +++++-------------- 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index b830fbf8e01d..bea4d69f360a 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -975,60 +975,36 @@ private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine // issue where users in a domain-dedicated pod could not deploy with ImplicitDedicationPlanner). List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); - List tempStorage; + Filter filter = new Filter(DedicatedResourceVO.class, "id", true); if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { // No domain-level affinity groups: search for resources dedicated to this specific account - tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, filter).first()) { allPodsFromDedicatedID.add(vo.getPodId()); } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, filter).first()) { allClustersFromDedicatedID.add(vo.getClusterId()); } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, filter).first()) { allHostsFromDedicatedID.add(vo.getHostId()); } - - allPodsInDc.removeAll(allPodsFromDedicatedID); - allClustersInDc.removeAll(allClustersFromDedicatedID); - allHostsInDc.removeAll(allHostsFromDedicatedID); } else { // Domain has explicit dedication affinity groups: search for resources dedicated to this domain - tempStorage = _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, filter).first()) { allPodsFromDedicatedID.add(vo.getPodId()); } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedClusters(null, vmDomainId, null, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedClusters(null, vmDomainId, null, null, filter).first()) { allClustersFromDedicatedID.add(vo.getClusterId()); } - - tempStorage.clear(); - tempStorage = _dedicatedDao.searchDedicatedHosts(null, vmDomainId, null, null, - new Filter(DedicatedResourceVO.class, "id", true, 0L, 1L)).first(); - for (DedicatedResourceVO vo : tempStorage) { + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedHosts(null, vmDomainId, null, null, filter).first()) { allHostsFromDedicatedID.add(vo.getHostId()); } - - allPodsInDc.removeAll(allPodsFromDedicatedID); - allClustersInDc.removeAll(allClustersFromDedicatedID); - allHostsInDc.removeAll(allHostsFromDedicatedID); } + allPodsInDc.removeAll(allPodsFromDedicatedID); + allClustersInDc.removeAll(allClustersFromDedicatedID); + allHostsInDc.removeAll(allHostsFromDedicatedID); + logger.debug(() -> LogUtils.logGsonWithoutException("Adding pods [%s], clusters [%s] and hosts [%s] to the avoid list in the deploy process of user VM [%s], " + "because this VM is not dedicated to these components.", allPodsInDc, allClustersInDc, allHostsInDc, vm)); avoids.addPodList(allPodsInDc); From b6eda5f983ceadafa0532a804d69009ace094080 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:24:00 +0000 Subject: [PATCH 4/6] Address code review: document filter purpose and clarify test mock intent --- .../java/com/cloud/deploy/DeploymentPlanningManagerImpl.java | 1 + .../com/cloud/deploy/DeploymentPlanningManagerImplTest.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index bea4d69f360a..9ad8fab653db 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -975,6 +975,7 @@ private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine // issue where users in a domain-dedicated pod could not deploy with ImplicitDedicationPlanner). List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); + // Sort by id ascending, no pagination — retrieve all dedicated resources for this domain/account Filter filter = new Filter(DedicatedResourceVO.class, "id", true); if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index fd6f86339285..0bc6c6dd27c3 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -995,7 +995,8 @@ public void checkForNonDedicatedResources_domainDedicatedPod_sameDomainUser_podN Mockito.when(_podDao.listAllPods(dataCenterId)).thenReturn(podsInDc); Mockito.when(_dedicatedDao.listAllPods()).thenReturn(new ArrayList<>(Arrays.asList(dedicatedPodId))); - // Domain has affinity group mappings (pod dedicated to this domain) + // Domain has affinity group mappings (pod dedicated to this domain). + // The content of the list entries does not matter; only emptiness is checked. AffinityGroupDomainMapVO domainMap = Mockito.mock(AffinityGroupDomainMapVO.class); Mockito.when(_affinityGroupDomainMapDao.listByDomain(vmDomainId)) .thenReturn(Arrays.asList(domainMap)); From 1d81b0439d47b22fd860c21d008af3e883e1e990 Mon Sep 17 00:00:00 2001 From: dahn Date: Mon, 6 Jul 2026 16:04:41 +0200 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../deploy/DeploymentPlanningManagerImpl.java | 26 +++++++++---------- .../DeploymentPlanningManagerImplTest.java | 9 +++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 9ad8fab653db..d1fdf3f19aa5 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -978,19 +978,19 @@ private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine // Sort by id ascending, no pagination — retrieve all dedicated resources for this domain/account Filter filter = new Filter(DedicatedResourceVO.class, "id", true); - if (domainGroupMappings == null || domainGroupMappings.isEmpty()) { - // No domain-level affinity groups: search for resources dedicated to this specific account - for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, filter).first()) { - allPodsFromDedicatedID.add(vo.getPodId()); - } - for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, filter).first()) { - allClustersFromDedicatedID.add(vo.getClusterId()); - } - for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, filter).first()) { - allHostsFromDedicatedID.add(vo.getHostId()); - } - } else { - // Domain has explicit dedication affinity groups: search for resources dedicated to this domain + // Always allow resources dedicated to the VM owner's account. + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, vmAccountId, null, filter).first()) { + allPodsFromDedicatedID.add(vo.getPodId()); + } + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedClusters(null, vmDomainId, vmAccountId, null, filter).first()) { + allClustersFromDedicatedID.add(vo.getClusterId()); + } + for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedHosts(null, vmDomainId, vmAccountId, null, filter).first()) { + allHostsFromDedicatedID.add(vo.getHostId()); + } + + if (domainGroupMappings != null && !domainGroupMappings.isEmpty()) { + // Domain has explicit dedication affinity groups: also allow resources dedicated to this domain for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, filter).first()) { allPodsFromDedicatedID.add(vo.getPodId()); } diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index 0bc6c6dd27c3..8e1340f1a60c 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -74,7 +74,6 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; -import com.cloud.utils.db.Filter; import com.cloud.vm.DiskProfile; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -1005,7 +1004,7 @@ public void checkForNonDedicatedResources_domainDedicatedPod_sameDomainUser_podN DedicatedResourceVO dedicatedPod = Mockito.mock(DedicatedResourceVO.class); Mockito.when(dedicatedPod.getPodId()).thenReturn(dedicatedPodId); Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), - Mockito.isNull(), Mockito.isNull(), Mockito.any(Filter.class))) + Mockito.isNull(), Mockito.isNull(), Mockito.any(com.cloud.utils.db.Filter.class))) .thenReturn(new Pair<>(new ArrayList<>(Arrays.asList(dedicatedPod)), 1)); VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); @@ -1046,7 +1045,7 @@ public void checkForNonDedicatedResources_domainDedicatedPod_differentDomainUser // No pods dedicated to the VM owner's account Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), - Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(com.cloud.utils.db.Filter.class))) .thenReturn(new Pair<>(new ArrayList<>(), 0)); VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); @@ -1089,7 +1088,7 @@ public void checkForNonDedicatedResources_accountDedicatedPod_sameAccount_podNot DedicatedResourceVO dedicatedPod = Mockito.mock(DedicatedResourceVO.class); Mockito.when(dedicatedPod.getPodId()).thenReturn(dedicatedPodId); Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), - Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(com.cloud.utils.db.Filter.class))) .thenReturn(new Pair<>(new ArrayList<>(Arrays.asList(dedicatedPod)), 1)); VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); @@ -1130,7 +1129,7 @@ public void checkForNonDedicatedResources_accountDedicatedPod_differentAccount_p // No pods dedicated to this account Mockito.when(_dedicatedDao.searchDedicatedPods(Mockito.isNull(), Mockito.eq(vmDomainId), - Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(Filter.class))) + Mockito.eq(vmAccountId), Mockito.isNull(), Mockito.any(com.cloud.utils.db.Filter.class))) .thenReturn(new Pair<>(new ArrayList<>(), 0)); VirtualMachineProfileImpl mockProfile = Mockito.mock(VirtualMachineProfileImpl.class); From 52ee410e618460f4a9cd2d4ea2a7383bc5767220 Mon Sep 17 00:00:00 2001 From: dahn Date: Mon, 6 Jul 2026 16:24:30 +0200 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: dahn --- .../cloud/deploy/DeploymentPlanningManagerImpl.java | 10 +++++----- .../deploy/DeploymentPlanningManagerImplTest.java | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index d1fdf3f19aa5..6f5dd81024d6 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -970,10 +970,10 @@ private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine List allClustersFromDedicatedID = new ArrayList<>(); List allHostsFromDedicatedID = new ArrayList<>(); - // Check if the VM owner's domain has explicit dedication affinity group mappings. - // If so, resources dedicated to that domain are accessible to the VM owner (fixes - // issue where users in a domain-dedicated pod could not deploy with ImplicitDedicationPlanner). - List domainGroupMappings = _affinityGroupDomainMapDao.listByDomain(vmDomainId); + // If the VM owner's domain has an ExplicitDedication domain-level affinity group, + // resources dedicated to that domain are accessible to the VM owner (fixes issue #5803). + boolean hasDomainExplicitDedicationGroup = + _affinityGroupDao.findDomainLevelGroupByType(vmDomainId, "ExplicitDedication") != null; // Sort by id ascending, no pagination — retrieve all dedicated resources for this domain/account Filter filter = new Filter(DedicatedResourceVO.class, "id", true); @@ -989,7 +989,7 @@ private void findAvoidSetForNonExplicitUserVM(ExcludeList avoids, VirtualMachine allHostsFromDedicatedID.add(vo.getHostId()); } - if (domainGroupMappings != null && !domainGroupMappings.isEmpty()) { + if (hasDomainExplicitDedicationGroup) { // Domain has explicit dedication affinity groups: also allow resources dedicated to this domain for (DedicatedResourceVO vo : _dedicatedDao.searchDedicatedPods(null, vmDomainId, null, null, filter).first()) { allPodsFromDedicatedID.add(vo.getPodId()); diff --git a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java index 8e1340f1a60c..2e353b4e4c97 100644 --- a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java +++ b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java @@ -229,6 +229,9 @@ public class DeploymentPlanningManagerImplTest { @Inject HostPodDao _podDao; + @Inject + HostDao _hostDao; + @Inject AffinityGroupDomainMapDao _affinityGroupDomainMapDao;