From 10cd150554c0a9c862138d92acf4408ac7639241 Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Tue, 10 Jun 2025 14:53:50 +0530 Subject: [PATCH 1/4] CPU to Memory weight based algorithm to order cluster host.capacityType.to.order.clusters config will support new algorithm: COMBINED which will work with host.capacityType.to.order.clusters.cputomemoryweight and capacity will be computed based on CPU and memory both and using weight factor --- .../apache/cloudstack/api/ApiConstants.java | 5 + .../configuration/ConfigurationManager.java | 5 + .../com/cloud/capacity/dao/CapacityDao.java | 10 +- .../cloud/capacity/dao/CapacityDaoImpl.java | 56 ++++++++- .../capacity/dao/CapacityDaoImplTest.java | 6 +- .../implicitplanner/ImplicitPlannerTest.java | 2 +- .../allocator/impl/FirstFitAllocator.java | 60 ++++++++-- .../java/com/cloud/configuration/Config.java | 5 +- .../ConfigurationManagerImpl.java | 2 +- .../com/cloud/deploy/FirstFitPlanner.java | 112 +++++++++++++++--- .../com/cloud/vm/FirstFitPlannerTest.java | 2 +- 11 files changed, 231 insertions(+), 34 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 304e43009f26..4e5eaacdd83c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -90,9 +90,11 @@ public class ApiConstants { public static final String CONVERT_INSTANCE_HOST_ID = "convertinstancehostid"; public static final String CONVERT_INSTANCE_STORAGE_POOL_ID = "convertinstancepoolid"; public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck"; + public static final String COMBINED_CAPACITY_ORDERING = "COMBINED"; public static final String CONTROLLER = "controller"; public static final String CONTROLLER_UNIT = "controllerunit"; public static final String COPY_IMAGE_TAGS = "copyimagetags"; + public static final String CPU_OVERCOMMIT_RATIO = "cpuOvercommitRatio"; public static final String CSR = "csr"; public static final String PRIVATE_KEY = "privatekey"; public static final String DATASTORE_HOST = "datastorehost"; @@ -120,6 +122,7 @@ public class ApiConstants { public static final String COMMAND = "command"; public static final String CMD_EVENT_TYPE = "cmdeventtype"; public static final String COMPONENT = "component"; + public static final String CPU = "CPU"; public static final String CPU_CORE_PER_SOCKET = "cpucorepersocket"; public static final String CPU_NUMBER = "cpunumber"; public static final String CPU_SPEED = "cpuspeed"; @@ -337,6 +340,7 @@ public class ApiConstants { public static final String MAX_BACKUPS = "maxbackups"; public static final String MAX_CPU_NUMBER = "maxcpunumber"; public static final String MAX_MEMORY = "maxmemory"; + public static final String MEMORY_OVERCOMMIT_RATIO = "memoryOvercommitRatio"; public static final String MIN_CPU_NUMBER = "mincpunumber"; public static final String MIN_MEMORY = "minmemory"; public static final String MIGRATION_TYPE = "migrationtype"; @@ -432,6 +436,7 @@ public class ApiConstants { public static final String PUBLIC_END_PORT = "publicendport"; public static final String PUBLIC_ZONE = "publiczone"; public static final String PURGE_RESOURCES = "purgeresources"; + public static final String RAM = "RAM"; public static final String RECEIVED_BYTES = "receivedbytes"; public static final String RECONNECT = "reconnect"; public static final String RECOVER = "recover"; diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 859d11762130..e2da1a643fa0 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -65,6 +65,11 @@ public interface ConfigurationManager { "allow.non.rfc1918.compliant.ips", "Advanced", "false", "Allows non-compliant RFC 1918 IPs for Shared, Isolated networks and VPCs", true, null); + ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey(ConfigKey.CATEGORY_ADVANCED, Float.class, "host.capacityType.to.order.clusters.cputomemoryweight", + "0.5", + "CPU to Memory weight used for COMBINED capacityTye to order cluster and host", + true, ConfigKey.Scope.Global); + /** * @param offering * @return diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java index 7a4c96c6e1f4..75a17aaf8b94 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java @@ -30,7 +30,7 @@ public interface CapacityDao extends GenericDao { List listByHostIdTypes(Long hostId, List capacityTypes); - List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); + List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, boolean isZone); List listHostsWithEnoughCapacity(int requiredCpu, long requiredRam, Long clusterId, String hostType); @@ -48,7 +48,7 @@ public interface CapacityDao extends GenericDao { List findFilteredCapacityBy(Integer capacityType, Long zoneId, Long podId, Long clusterId, List hostIds, List poolIds); - List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam, short capacityType); + List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam); Pair, Map> orderPodsByAggregateCapacity(long zoneId, short capacityType); @@ -65,4 +65,10 @@ List listCapacitiesGroupedByLevelAndType(Integer capacityType, L float findClusterConsumption(Long clusterId, short capacityType, long computeRequested); Pair, Map> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityType); + + List listHostCapacityByCapacityTypes(Long zoneId, Long clusterId, List capacityTypes); + + List listPodCapacityByCapacityTypes(Long zoneId, List capacityTypes); + + List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, long vmId, List capacityTypes); } diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index 0860f14518f6..2d2e165e1092 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -684,7 +684,7 @@ public List listByHostIdTypes(Long hostId, List capacityTypes } @Override - public List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone) { + public List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, boolean isZone) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); @@ -1068,7 +1068,59 @@ public Pair, Map> orderHostsByFreeCapacity(Long zoneId, } @Override - public List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam, short capacityType) { + public List listHostCapacityByCapacityTypes(Long zoneId, Long clusterId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.done(); + + SearchCriteria sc = sb.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (clusterId != null) { + sc.setParameters("clusterId", clusterId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listPodCapacityByCapacityTypes(Long zoneId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.done(); + SearchCriteria sc = sb.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, long vmId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.done(); + + SearchCriteria sc = sb.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (podId != null) { + sc.setParameters("podId", podId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); diff --git a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java index 8e9a0bd34c77..a60892a64ad0 100644 --- a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java @@ -205,11 +205,11 @@ public void testListClustersInZoneOrPodByHostCapacitiesEmpty() throws Exception when(pstmt.executeQuery()).thenReturn(resultSet); when(resultSet.next()).thenReturn(false); - List resultZone = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, (short)0, true); + List resultZone = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, true); assertNotNull(resultZone); assertTrue(resultZone.isEmpty()); - List resultPod = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, (short)0, false); + List resultPod = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, false); assertNotNull(resultPod); assertTrue(resultPod.isEmpty()); } @@ -281,7 +281,7 @@ public void testListPodsByHostCapacitiesEmptyResult() throws Exception { when(pstmt.executeQuery()).thenReturn(resultSet); when(resultSet.next()).thenReturn(false); - List result = capacityDao.listPodsByHostCapacities(1L, 2, 1024L, (short)0); + List result = capacityDao.listPodsByHostCapacities(1L, 2, 1024L); assertNotNull(result); assertTrue(result.isEmpty()); } diff --git a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java index 2d2b4c78261e..a00fb49bb3fb 100644 --- a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java +++ b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -359,7 +359,7 @@ private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDe clustersWithEnoughCapacity.add(3L); when( capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, 12L, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, - Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); + true)).thenReturn(clustersWithEnoughCapacity); Map clusterCapacityMap = new HashMap(); clusterCapacityMap.put(1L, 2048D); 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 68a901a68a20..7571264366d4 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 @@ -30,10 +30,12 @@ import javax.naming.ConfigurationException; import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.capacity.Capacity; import com.cloud.capacity.CapacityManager; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.deploy.DeploymentPlan; @@ -64,9 +66,11 @@ import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; /** @@ -372,13 +376,7 @@ protected List allocateTo(DeploymentPlan plan, ServiceOffering offering, V private List reorderHostsByCapacity(DeploymentPlan plan, List hosts) { Long zoneId = plan.getDataCenterId(); Long clusterId = plan.getClusterId(); - //Get capacity by which we should reorder - String capacityTypeToOrder = _configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); - short capacityType = CapacityVO.CAPACITY_TYPE_CPU; - if("RAM".equalsIgnoreCase(capacityTypeToOrder)){ - capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; - } - Pair, Map> result = _capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType); + Pair, Map> result = getOrderedHostsByCapacity(zoneId, clusterId); List hostIdsByFreeCapacity = result.first(); Map sortedHostByCapacity = result.second().entrySet() .stream() @@ -407,6 +405,54 @@ private List reorderHostsByCapacity(DeploymentPlan plan, List, Map> getOrderedHostsByCapacity(Long zoneId, Long clusterId) { + double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); + // Get capacity by which we should reorder + short capacityType = getCapacityType(cpuToMemoryWeight); + if (capacityType >= 0) { // for CPU or RAM + return _capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType); + } + List capacities = _capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, + List.of(Capacity.CAPACITY_TYPE_CPU, Capacity.CAPACITY_TYPE_MEMORY)); + Map hostByComputedCapacity = getHostByCombinedCapacities(capacities, cpuToMemoryWeight); + return new Pair<>(new ArrayList<>(hostByComputedCapacity.keySet()), hostByComputedCapacity); + } + + short getCapacityType(double cpuToMemoryWeight) { + String capacityTypeToOrder = _configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); + short capacityType = CapacityVO.CAPACITY_TYPE_CPU; + if (ApiConstants.RAM.equalsIgnoreCase(capacityTypeToOrder)){ + capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; + } else if (ApiConstants.COMBINED_CAPACITY_ORDERING.equalsIgnoreCase(capacityTypeToOrder)) { + capacityType = -1; + } + + if (cpuToMemoryWeight == 1) { + capacityType = CapacityVO.CAPACITY_TYPE_CPU; + } + if (cpuToMemoryWeight == 0) { + capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; + } + return capacityType; + } + + + @NotNull + private static Map getHostByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + Map hostByComputedCapacity = new HashMap<>(); + for (CapacityVO capacityVO : capacities) { + long hostId = capacityVO.getHostOrPoolId(); + double applicableWeight = capacityVO.getCapacityType() == Capacity.CAPACITY_TYPE_CPU ? cpuToMemoryWeight : 1 - cpuToMemoryWeight; + double capacityMetric = applicableWeight * (capacityVO.getTotalCapacity() - (capacityVO.getUsedCapacity() + capacityVO.getReservedCapacity()))/capacityVO.getTotalCapacity(); + hostByComputedCapacity.merge(hostId, capacityMetric, Double::sum); + } + + return hostByComputedCapacity.entrySet() + .stream() + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + private List reorderHostsByNumberOfVms(DeploymentPlan plan, List hosts, Account account) { if (account == null) { return hosts; diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index a6efaffcb56d..d35bfac501fd 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -897,8 +897,9 @@ public enum Config { String.class, "host.capacityType.to.order.clusters", "CPU", - "The host capacity type (CPU or RAM) is used by deployment planner to order clusters during VM resource allocation", - "CPU,RAM"), + "The host capacity type (CPU, RAM, COMBINED) is used by deployment planner to order clusters during VM resource allocation", + "CPU,RAM,COMBINED"), + ApplyAllocationAlgorithmToPods( "Advanced", ManagementServer.class, diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 908f3d7dad07..b93d03a80344 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -8252,7 +8252,7 @@ public ConfigKey[] getConfigKeys() { BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, VM_SERVICE_OFFERING_MAX_CPU_CORES, VM_SERVICE_OFFERING_MAX_RAM_SIZE, MIGRATE_VM_ACROSS_CLUSTERS, ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN, ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN, - ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs + ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs, HostCapacityTypeCpuMemoryWeight }; } diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index abaf48400e23..93958ca3ffe2 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -20,14 +20,19 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.capacity.CapacityVO; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.ClusterDetailsVO; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -457,17 +462,14 @@ protected Pair, Map> listClustersByCapacity(long id, lo logger.debug("Listing clusters in order of aggregate capacity, that have (at least one host with) enough CPU and RAM capacity under this " + (isZone ? "Zone: " : "Pod: ") + id); } - String capacityTypeToOrder = configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); - short capacityType = Capacity.CAPACITY_TYPE_CPU; - if ("RAM".equalsIgnoreCase(capacityTypeToOrder)) { - capacityType = Capacity.CAPACITY_TYPE_MEMORY; - } - List clusterIdswithEnoughCapacity = capacityDao.listClustersInZoneOrPodByHostCapacities(id, vmId, requiredCpu, requiredRam, capacityType, isZone); + List clusterIdswithEnoughCapacity = capacityDao.listClustersInZoneOrPodByHostCapacities(id, vmId, requiredCpu, requiredRam, isZone); if (logger.isTraceEnabled()) { logger.trace("ClusterId List having enough CPU and RAM capacity: " + clusterIdswithEnoughCapacity); } - Pair, Map> result = capacityDao.orderClustersByAggregateCapacity(id, vmId, capacityType, isZone); + + + Pair, Map> result = getOrderedClustersByCapacity(id, vmId, isZone); List clusterIdsOrderedByAggregateCapacity = result.first(); //only keep the clusters that have enough capacity to host this VM if (logger.isTraceEnabled()) { @@ -491,17 +493,12 @@ protected Pair, Map> listPodsByCapacity(long zoneId, in if (logger.isDebugEnabled()) { logger.debug("Listing pods in order of aggregate capacity, that have (at least one host with) enough CPU and RAM capacity under this Zone: " + zoneId); } - String capacityTypeToOrder = configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); - short capacityType = Capacity.CAPACITY_TYPE_CPU; - if ("RAM".equalsIgnoreCase(capacityTypeToOrder)) { - capacityType = Capacity.CAPACITY_TYPE_MEMORY; - } - - List podIdswithEnoughCapacity = capacityDao.listPodsByHostCapacities(zoneId, requiredCpu, requiredRam, capacityType); + List podIdswithEnoughCapacity = capacityDao.listPodsByHostCapacities(zoneId, requiredCpu, requiredRam); if (logger.isTraceEnabled()) { logger.trace("PodId List having enough CPU and RAM capacity: " + podIdswithEnoughCapacity); } - Pair, Map> result = capacityDao.orderPodsByAggregateCapacity(zoneId, capacityType); + + Pair, Map> result = getOrderedPodsByCapacity(zoneId); List podIdsOrderedByAggregateCapacity = result.first(); //only keep the clusters that have enough capacity to host this VM if (logger.isTraceEnabled()) { @@ -517,6 +514,91 @@ protected Pair, Map> listPodsByCapacity(long zoneId, in } + private Pair, Map> getOrderedPodsByCapacity(long zoneId) { + double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); + short capacityType = getCapacityType(cpuToMemoryWeight); + if (capacityType >= 0) { // for capacityType other than COMBINED + return capacityDao.orderPodsByAggregateCapacity(zoneId, capacityType); + } + List capacities = capacityDao.listPodCapacityByCapacityTypes(zoneId, List.of(Capacity.CAPACITY_TYPE_CPU, Capacity.CAPACITY_TYPE_MEMORY)); + Map podsByCombinedCapacities = getPodByCombinedCapacities(capacities, cpuToMemoryWeight); + return new Pair<>(new ArrayList<>(podsByCombinedCapacities.keySet()), podsByCombinedCapacities); + } + + // order pods by combining cpu and memory capacity considering cpuToMemoeryWeight + private Map getPodByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + Map podByCombinedCapacity = new HashMap<>(); + for (CapacityVO capacityVO : capacities) { + boolean isCPUCapacity = capacityVO.getCapacityType() == Capacity.CAPACITY_TYPE_CPU; + long podId = capacityVO.getPodId(); + double applicableWeight = isCPUCapacity ? cpuToMemoryWeight : 1 - cpuToMemoryWeight; + String overCommitRatioParam = isCPUCapacity ? ApiConstants.CPU_OVERCOMMIT_RATIO : ApiConstants.MEMORY_OVERCOMMIT_RATIO; + ClusterDetailsVO overCommitRatioVO = clusterDetailsDao.findDetail(capacityVO.getClusterId(), overCommitRatioParam); + float overCommitRatio = Float.parseFloat(overCommitRatioVO.getValue()); + double capacityMetric = applicableWeight * + (capacityVO.getUsedCapacity() + capacityVO.getReservedCapacity())/(capacityVO.getTotalCapacity() * overCommitRatio); + podByCombinedCapacity.merge(podId, capacityMetric, Double::sum); + } + return podByCombinedCapacity.entrySet() + .stream() + .sorted(Map.Entry.comparingByValue()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + + private Pair, Map> getOrderedClustersByCapacity(long id, long vmId, boolean isZone) { + double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); + short capacityType = getCapacityType(cpuToMemoryWeight); + if (capacityType >= 0) { // for capacityType other than COMBINED + return capacityDao.orderClustersByAggregateCapacity(id, vmId, capacityType, isZone); + } + + Long zoneId = isZone ? id : null; + Long podId = isZone ? null : id; + List capacities = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, vmId, + List.of(Capacity.CAPACITY_TYPE_CPU, Capacity.CAPACITY_TYPE_MEMORY)); + + Map podsByCombinedCapacities = getClusterByCombinedCapacities(capacities, cpuToMemoryWeight); + return new Pair<>(new ArrayList<>(podsByCombinedCapacities.keySet()), podsByCombinedCapacities); + } + + private Map getClusterByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + Map clusterByCombinedCapacity = new HashMap<>(); + for (CapacityVO capacityVO : capacities) { + boolean isCPUCapacity = capacityVO.getCapacityType() == Capacity.CAPACITY_TYPE_CPU; + long clusterId = capacityVO.getClusterId(); + double applicableWeight = isCPUCapacity ? cpuToMemoryWeight : 1 - cpuToMemoryWeight; + String overCommitRatioParam = isCPUCapacity ? ApiConstants.CPU_OVERCOMMIT_RATIO : ApiConstants.MEMORY_OVERCOMMIT_RATIO; + ClusterDetailsVO overCommitRatioVO = clusterDetailsDao.findDetail(clusterId, overCommitRatioParam); + float overCommitRatio = Float.parseFloat(overCommitRatioVO.getValue()); + double capacityMetric = applicableWeight * + (capacityVO.getUsedCapacity() + capacityVO.getReservedCapacity())/(capacityVO.getTotalCapacity() * overCommitRatio); + clusterByCombinedCapacity.merge(clusterId, capacityMetric, Double::sum); + } + return clusterByCombinedCapacity.entrySet() + .stream() + .sorted(Map.Entry.comparingByValue()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + short getCapacityType(double cpuToMemoryWeight) { + String capacityTypeToOrder = configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); + short capacityType = CapacityVO.CAPACITY_TYPE_CPU; + if (ApiConstants.RAM.equalsIgnoreCase(capacityTypeToOrder)){ + capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; + } else if (ApiConstants.COMBINED_CAPACITY_ORDERING.equalsIgnoreCase(capacityTypeToOrder)) { + capacityType = -1; + } + + if (cpuToMemoryWeight == 1) { + capacityType = CapacityVO.CAPACITY_TYPE_CPU; + } + if (cpuToMemoryWeight == 0) { + capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; + } + return capacityType; + } + private void removeClustersWithoutMatchingTag(List clusterListForVmAllocation, String hostTagOnOffering) { List matchingClusters = hostDao.listClustersByHostTag(hostTagOnOffering); diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java index 7df4857f4fdf..b2dad395a09b 100644 --- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java +++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java @@ -293,7 +293,7 @@ private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDe when( capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, 12L, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, - Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); + true)).thenReturn(clustersWithEnoughCapacity); Map clusterCapacityMap = new HashMap(); clusterCapacityMap.put(1L, 2048D); From 88c080430b9b2bdb2792b347ceb166fbd2ebb7f2 Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Wed, 11 Jun 2025 18:07:25 +0530 Subject: [PATCH 2/4] minor changes --- .../java/com/cloud/configuration/ConfigurationManager.java | 2 +- .../main/java/com/cloud/capacity/dao/CapacityDaoImpl.java | 6 ++++++ .../src/main/resources/META-INF/db/schema-42010to42100.sql | 6 ++++++ .../agent/manager/allocator/impl/FirstFitAllocator.java | 2 +- server/src/main/java/com/cloud/deploy/FirstFitPlanner.java | 4 ++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index e2da1a643fa0..497ef02d51c0 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -65,7 +65,7 @@ public interface ConfigurationManager { "allow.non.rfc1918.compliant.ips", "Advanced", "false", "Allows non-compliant RFC 1918 IPs for Shared, Isolated networks and VPCs", true, null); - ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey(ConfigKey.CATEGORY_ADVANCED, Float.class, "host.capacityType.to.order.clusters.cputomemoryweight", + ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Float.class, "host.capacityType.to.order.clusters.cputomemoryweight", "0.5", "CPU to Memory weight used for COMBINED capacityTye to order cluster and host", true, ConfigKey.Scope.Global); diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index 2d2e165e1092..da77c8cc3caf 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -1073,9 +1073,11 @@ public List listHostCapacityByCapacityTypes(Long zoneId, Long cluste sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ); sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); sb.done(); SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); if (zoneId != null) { sc.setParameters("zoneId", zoneId); } @@ -1091,8 +1093,10 @@ public List listPodCapacityByCapacityTypes(Long zoneId, List SearchBuilder sb = createSearchBuilder(); sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); sb.done(); SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); if (zoneId != null) { sc.setParameters("zoneId", zoneId); } @@ -1106,9 +1110,11 @@ public List listClusterCapacityByCapacityTypes(Long zoneId, Long pod sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ); sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); sb.done(); SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); if (zoneId != null) { sc.setParameters("zoneId", zoneId); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index b6747ca60716..d2a43d94a211 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -80,3 +80,9 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'storage_access_groups', 'var CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.cluster', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the cluster"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_pod_ref', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the pod"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.data_center', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the zone"'); + +-- Update description for configuration: host.capacityType.to.order.clusters +UPDATE `cloud`.`configuration` SET + `description` = 'The host capacity type (CPU, RAM or COMBINED) is used by deployment planner to order clusters during VM resource allocation' +WHERE `name` = 'host.capacityType.to.order.clusters' + AND `description` = 'The host capacity type (CPU or RAM) is used by deployment planner to order clusters during VM resource allocation'; 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 7571264366d4..e15623b5194c 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 @@ -299,7 +299,7 @@ protected List allocateTo(DeploymentPlan plan, ServiceOffering offering, V Collections.shuffle(hosts); } else if (vmAllocationAlgorithm.equals("userdispersing")) { hosts = reorderHostsByNumberOfVms(plan, hosts, account); - }else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){ + } else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){ hosts = reorderHostsByCapacity(plan, hosts); } diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index 93958ca3ffe2..e3f9e7aa771d 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -558,8 +558,8 @@ private Pair, Map> getOrderedClustersByCapacity(long id List capacities = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, vmId, List.of(Capacity.CAPACITY_TYPE_CPU, Capacity.CAPACITY_TYPE_MEMORY)); - Map podsByCombinedCapacities = getClusterByCombinedCapacities(capacities, cpuToMemoryWeight); - return new Pair<>(new ArrayList<>(podsByCombinedCapacities.keySet()), podsByCombinedCapacities); + Map clusterByCombinedCapacities = getClusterByCombinedCapacities(capacities, cpuToMemoryWeight); + return new Pair<>(new ArrayList<>(clusterByCombinedCapacities.keySet()), clusterByCombinedCapacities); } private Map getClusterByCombinedCapacities(List capacities, double cpuToMemoryWeight) { From c8b6734ab66586b3f90982da518f37250f24d59e Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Tue, 17 Jun 2025 21:21:25 +0530 Subject: [PATCH 3/4] add unit tests --- .../configuration/ConfigurationManager.java | 3 +- .../com/cloud/capacity/dao/CapacityDao.java | 2 +- .../cloud/capacity/dao/CapacityDaoImpl.java | 2 +- .../capacity/dao/CapacityDaoImplTest.java | 220 ++++++++++++++++++ .../allocator/impl/FirstFitAllocator.java | 26 +-- .../com/cloud/deploy/FirstFitPlanner.java | 38 +-- .../allocator/impl/FirstFitAllocatorTest.java | 62 +++++ .../com/cloud/vm/FirstFitPlannerTest.java | 140 ++++++++++- 8 files changed, 446 insertions(+), 47 deletions(-) diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 497ef02d51c0..9ea3733e0d4d 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -65,7 +65,8 @@ public interface ConfigurationManager { "allow.non.rfc1918.compliant.ips", "Advanced", "false", "Allows non-compliant RFC 1918 IPs for Shared, Isolated networks and VPCs", true, null); - ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Float.class, "host.capacityType.to.order.clusters.cputomemoryweight", + ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Float.class, + "host.capacityType.to.order.clusters.cputomemoryweight", "0.5", "CPU to Memory weight used for COMBINED capacityTye to order cluster and host", true, ConfigKey.Scope.Global); diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java index 75a17aaf8b94..42313efa512f 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java @@ -70,5 +70,5 @@ List listCapacitiesGroupedByLevelAndType(Integer capacityType, L List listPodCapacityByCapacityTypes(Long zoneId, List capacityTypes); - List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, long vmId, List capacityTypes); + List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, List capacityTypes); } diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index da77c8cc3caf..d54d1632bd08 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -1105,7 +1105,7 @@ public List listPodCapacityByCapacityTypes(Long zoneId, List } @Override - public List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, long vmId, List capacityTypes) { + public List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, List capacityTypes) { SearchBuilder sb = createSearchBuilder(); sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ); diff --git a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java index a60892a64ad0..f9528d5d57ff 100644 --- a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java @@ -51,6 +51,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -61,6 +62,9 @@ public class CapacityDaoImplTest { @InjectMocks CapacityDaoImpl capacityDao = new CapacityDaoImpl(); + @Mock + private CapacityVO mockEntity; + @Mock private TransactionLegacy txn; @Mock @@ -71,6 +75,8 @@ public class CapacityDaoImplTest { private SearchBuilder searchBuilder; private SearchCriteria searchCriteria; + private List capacityTypes; + private List expectedCapacities; @Before public void setUp() { @@ -83,6 +89,17 @@ public void setUp() { mockedTransactionLegacy = Mockito.mockStatic(TransactionLegacy.class); mockedTransactionLegacy.when(TransactionLegacy::currentTxn).thenReturn(txn); + + // Setup common test data + capacityTypes = Arrays.asList((short) 1, (short) 2, (short) 3); + expectedCapacities = Arrays.asList(mock(CapacityVO.class), mock(CapacityVO.class)); + doReturn(expectedCapacities).when(capacityDao).listBy(searchCriteria); + } + + private CapacityVO createMockCapacityVO(Long id) { + CapacityVO capacity = mock(CapacityVO.class); + when(capacity.getId()).thenReturn(id); + return capacity; } @After @@ -330,4 +347,207 @@ public void testFindCapacityByEmptyResult() throws Exception { assertNotNull(result); assertTrue(result.isEmpty()); } + + @Test + public void testListHostCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + Long clusterId = 200L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("clusterId", mockEntity.getClusterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("clusterId", clusterId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + verify(capacityDao).listBy(searchCriteria); + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithNullZoneId() { + // Given + Long clusterId = 200L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(null, clusterId, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, Mockito.times(0)).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("clusterId", clusterId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithNullClusterId() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria, never()).setParameters(eq("clusterId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithEmptyCapacityTypes() { + // Given + Long zoneId = 100L; + Long clusterId = 200L; + List emptyCapacityTypes = Collections.emptyList(); + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, emptyCapacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityTypes", emptyCapacityTypes.toArray()); + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListPodCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listPodCapacityByCapacityTypes(zoneId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListPodCapacityByCapacityTypes_WithNullZoneId() { + // When + List result = capacityDao.listPodCapacityByCapacityTypes(null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + Long podId = 300L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("podId", mockEntity.getPodId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("podId", podId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithNullZoneId() { + // Given + Long podId = 300L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(null, podId, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("podId", podId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithNullPodId() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(zoneId, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria, never()).setParameters(eq("podId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithBothIdsNull() { + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(null, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria, never()).setParameters(eq("podId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testAllMethods_VerifySearchBuilderSetup() { + // Test that all methods properly set up the search builder + Long zoneId = 100L; + Long clusterId = 200L; + Long podId = 300L; + + // Test host capacity method + capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, capacityTypes); + + // Test pod capacity method + capacityDao.listPodCapacityByCapacityTypes(zoneId, capacityTypes); + + // Test cluster capacity method + capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, capacityTypes); + + // Verify createSearchBuilder was called 3 times + verify(capacityDao, times(3)).createSearchBuilder(); + + // Verify done() was called 3 times + verify(searchBuilder, times(3)).done(); + + // Verify listBy was called 3 times + verify(capacityDao, times(3)).listBy(searchCriteria); + } } 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 e15623b5194c..716a05129a1c 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 @@ -41,6 +41,7 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentClusterPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.FirstFitPlanner; import com.cloud.gpu.GPU; import com.cloud.host.DetailVO; import com.cloud.host.Host; @@ -66,7 +67,6 @@ import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -408,7 +408,8 @@ private List reorderHostsByCapacity(DeploymentPlan plan, List, Map> getOrderedHostsByCapacity(Long zoneId, Long clusterId) { double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); // Get capacity by which we should reorder - short capacityType = getCapacityType(cpuToMemoryWeight); + short capacityType = FirstFitPlanner.getHostCapacityTypeToOrderCluster( + _configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()), cpuToMemoryWeight); if (capacityType >= 0) { // for CPU or RAM return _capacityDao.orderHostsByFreeCapacity(zoneId, clusterId, capacityType); } @@ -418,27 +419,8 @@ private Pair, Map> getOrderedHostsByCapacity(Long zoneI return new Pair<>(new ArrayList<>(hostByComputedCapacity.keySet()), hostByComputedCapacity); } - short getCapacityType(double cpuToMemoryWeight) { - String capacityTypeToOrder = _configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); - short capacityType = CapacityVO.CAPACITY_TYPE_CPU; - if (ApiConstants.RAM.equalsIgnoreCase(capacityTypeToOrder)){ - capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; - } else if (ApiConstants.COMBINED_CAPACITY_ORDERING.equalsIgnoreCase(capacityTypeToOrder)) { - capacityType = -1; - } - - if (cpuToMemoryWeight == 1) { - capacityType = CapacityVO.CAPACITY_TYPE_CPU; - } - if (cpuToMemoryWeight == 0) { - capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; - } - return capacityType; - } - - @NotNull - private static Map getHostByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + public static Map getHostByCombinedCapacities(List capacities, double cpuToMemoryWeight) { Map hostByComputedCapacity = new HashMap<>(); for (CapacityVO capacityVO : capacities) { long hostId = capacityVO.getHostOrPoolId(); diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index e3f9e7aa771d..497a72467e4b 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -516,7 +516,8 @@ protected Pair, Map> listPodsByCapacity(long zoneId, in private Pair, Map> getOrderedPodsByCapacity(long zoneId) { double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); - short capacityType = getCapacityType(cpuToMemoryWeight); + short capacityType = getHostCapacityTypeToOrderCluster( + configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()), cpuToMemoryWeight); if (capacityType >= 0) { // for capacityType other than COMBINED return capacityDao.orderPodsByAggregateCapacity(zoneId, capacityType); } @@ -526,7 +527,7 @@ private Pair, Map> getOrderedPodsByCapacity(long zoneId } // order pods by combining cpu and memory capacity considering cpuToMemoeryWeight - private Map getPodByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + public Map getPodByCombinedCapacities(List capacities, double cpuToMemoryWeight) { Map podByCombinedCapacity = new HashMap<>(); for (CapacityVO capacityVO : capacities) { boolean isCPUCapacity = capacityVO.getCapacityType() == Capacity.CAPACITY_TYPE_CPU; @@ -548,21 +549,22 @@ private Map getPodByCombinedCapacities(List capacities private Pair, Map> getOrderedClustersByCapacity(long id, long vmId, boolean isZone) { double cpuToMemoryWeight = ConfigurationManager.HostCapacityTypeCpuMemoryWeight.value(); - short capacityType = getCapacityType(cpuToMemoryWeight); + short capacityType = getHostCapacityTypeToOrderCluster( + configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()), cpuToMemoryWeight); if (capacityType >= 0) { // for capacityType other than COMBINED return capacityDao.orderClustersByAggregateCapacity(id, vmId, capacityType, isZone); } Long zoneId = isZone ? id : null; Long podId = isZone ? null : id; - List capacities = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, vmId, + List capacities = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, List.of(Capacity.CAPACITY_TYPE_CPU, Capacity.CAPACITY_TYPE_MEMORY)); Map clusterByCombinedCapacities = getClusterByCombinedCapacities(capacities, cpuToMemoryWeight); return new Pair<>(new ArrayList<>(clusterByCombinedCapacities.keySet()), clusterByCombinedCapacities); } - private Map getClusterByCombinedCapacities(List capacities, double cpuToMemoryWeight) { + public Map getClusterByCombinedCapacities(List capacities, double cpuToMemoryWeight) { Map clusterByCombinedCapacity = new HashMap<>(); for (CapacityVO capacityVO : capacities) { boolean isCPUCapacity = capacityVO.getCapacityType() == Capacity.CAPACITY_TYPE_CPU; @@ -581,22 +583,20 @@ private Map getClusterByCombinedCapacities(List capaci .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); } - short getCapacityType(double cpuToMemoryWeight) { - String capacityTypeToOrder = configDao.getValue(Config.HostCapacityTypeToOrderClusters.key()); - short capacityType = CapacityVO.CAPACITY_TYPE_CPU; - if (ApiConstants.RAM.equalsIgnoreCase(capacityTypeToOrder)){ - capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; - } else if (ApiConstants.COMBINED_CAPACITY_ORDERING.equalsIgnoreCase(capacityTypeToOrder)) { - capacityType = -1; + public static short getHostCapacityTypeToOrderCluster(String capacityTypeToOrder, double cpuToMemoryWeight) { + if (ApiConstants.RAM.equalsIgnoreCase(capacityTypeToOrder)) { + return CapacityVO.CAPACITY_TYPE_MEMORY; } - - if (cpuToMemoryWeight == 1) { - capacityType = CapacityVO.CAPACITY_TYPE_CPU; - } - if (cpuToMemoryWeight == 0) { - capacityType = CapacityVO.CAPACITY_TYPE_MEMORY; + if (ApiConstants.COMBINED_CAPACITY_ORDERING.equalsIgnoreCase(capacityTypeToOrder)) { + if (cpuToMemoryWeight == 1.0) { + return CapacityVO.CAPACITY_TYPE_CPU; + } + if (cpuToMemoryWeight == 0.0) { + return CapacityVO.CAPACITY_TYPE_MEMORY; + } + return -1; // represents COMBINED } - return capacityType; + return CapacityVO.CAPACITY_TYPE_CPU; } private void removeClustersWithoutMatchingTag(List clusterListForVmAllocation, String hostTagOnOffering) { 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 index 4524943db38d..83498fbbe766 100644 --- 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 @@ -19,6 +19,7 @@ package com.cloud.agent.manager.allocator.impl; import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.CapacityVO; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.host.Host; @@ -28,12 +29,14 @@ import com.cloud.user.Account; import com.cloud.utils.Pair; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -47,6 +50,7 @@ public class FirstFitAllocatorTest { + private static final double TOLERANCE = 0.0001; private FirstFitAllocator allocator; private CapacityManager capacityMgr; private ServiceOfferingDetailsDao offeringDetailsDao; @@ -156,4 +160,62 @@ public void testAllocateTo_GPUNotAvailable() { assertTrue(result.isEmpty()); } + + @Test + public void testHostByCombinedCapacityOrder() { + // Test scenario 1: Default capacity usage (0.5 weight) + List mockCapacity = getHostCapacities(); + Map hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.5); + + // Verify host ordering and capacity values + Long firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 1 should be first in ordering", Long.valueOf(1L), firstHostId); + Assert.assertEquals("Host 1 combined capacity should match expected value", + 0.9609375, hostByCombinedCapacity.get(1L), TOLERANCE); + Assert.assertEquals("Host 2 combined capacity should match expected value", + 0.9296875, hostByCombinedCapacity.get(2L), TOLERANCE); + + // Test scenario 2: Modified capacity usage (0.7 weight) + when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); + hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.7); + + // Verify new ordering after capacity change + firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 2 should be first after capacity change", Long.valueOf(2L), firstHostId); + Assert.assertEquals("Host 2 combined capacity should match expected value after change", + 0.9515625, hostByCombinedCapacity.get(2L), TOLERANCE); + Assert.assertEquals("Host 1 combined capacity should match expected value after change", + 0.9484375, hostByCombinedCapacity.get(1L), TOLERANCE); + } + + List getHostCapacities() { + CapacityVO cpuCapacity1 = mock(CapacityVO.class); + when(cpuCapacity1.getHostOrPoolId()).thenReturn(1L); + when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO cpuCapacity2 = mock(CapacityVO.class); + when(cpuCapacity2.getHostOrPoolId()).thenReturn(2L); + when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO memCapacity1 = mock(CapacityVO.class); + when(memCapacity1.getHostOrPoolId()).thenReturn(1L); + when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity1.getReservedCapacity()).thenReturn(0L); + when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + + CapacityVO memCapacity2 = mock(CapacityVO.class); + when(memCapacity2.getHostOrPoolId()).thenReturn(2L); + when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity2.getReservedCapacity()).thenReturn(0L); + when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); + } } diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java index b2dad395a09b..4ac00a7710bc 100644 --- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java +++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java @@ -17,6 +17,7 @@ package com.cloud.vm; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,6 +31,8 @@ import javax.inject.Inject; +import com.cloud.capacity.CapacityVO; +import com.cloud.dc.ClusterDetailsVO; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.framework.config.ConfigDepot; @@ -42,10 +45,10 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.test.utils.SpringUtils; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -138,7 +141,9 @@ public class FirstFitPlannerTest { ScopedConfigStorage scopedStorage; @Inject HostDao hostDao; - + @Inject + private ClusterDetailsDao clusterDetailsDao; + private static final double TOLERANCE = 0.0001; private static long domainId = 1L; long dataCenterId = 1L; long accountId = 1L; @@ -241,6 +246,69 @@ public void checkClusterReorderingForStartVMWithThresholdCheckDisabled() throws assertTrue("Reordered cluster list does not have clusters exceeding threshold", (clusterList.containsAll(clustersCrossingThreshold))); } + + @Test + public void testGetClusterOrderCapacityType() { + Assert.assertEquals(1, FirstFitPlanner.getHostCapacityTypeToOrderCluster("CPU", 0.5)); + Assert.assertEquals(0, FirstFitPlanner.getHostCapacityTypeToOrderCluster("RAM", 0.5)); + String combinedOrder = "COMBINED"; + Assert.assertEquals(1, FirstFitPlanner.getHostCapacityTypeToOrderCluster(combinedOrder, 1)); // cputomemoryweight:1 -> CPU + Assert.assertEquals(0, FirstFitPlanner.getHostCapacityTypeToOrderCluster(combinedOrder, 0)); // cputomemoryweight: 0 -> RAM + Assert.assertEquals(-1, FirstFitPlanner.getHostCapacityTypeToOrderCluster(combinedOrder, 0.5)); + } + + @Test + public void testGetPodByCombinedCapacities() { + List mockCapacity = getPodCapacities(); + ClusterDetailsVO clusterDetailsOverCommitRatio = mock(ClusterDetailsVO.class); + when(clusterDetailsOverCommitRatio.getValue()).thenReturn("1.0"); + when(clusterDetailsDao.findDetail(anyLong(), anyString())).thenReturn(clusterDetailsOverCommitRatio); + + Map podByCombinedCapacity = planner.getPodByCombinedCapacities(mockCapacity, 0.5); + Long firstPodId = podByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Pod with ID 1 should be first in ordering", Long.valueOf(1L), firstPodId); + Assert.assertEquals("Pod 1 combined capacity should match expected value", + 0.0390625, podByCombinedCapacity.get(1L), TOLERANCE); + Assert.assertEquals("Pod 2 combined capacity should match expected value", + 0.0703125, podByCombinedCapacity.get(2L), TOLERANCE); + + // Test scenario 2: Modified capacity usage (0.7 weight) + when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); + podByCombinedCapacity = planner.getPodByCombinedCapacities(mockCapacity, 0.7); + firstPodId = podByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Pod with ID 2 should be first in ordering", Long.valueOf(2L), firstPodId); + Assert.assertEquals("Pod 2 combined capacity should match expected value", + 0.04843750, podByCombinedCapacity.get(2L), TOLERANCE); + Assert.assertEquals("Pod 1 combined capacity should match expected value", + 0.05156250, podByCombinedCapacity.get(1L), TOLERANCE); + } + + @Test + public void testGetClusterByCombinedCapacities() { + List mockCapacity = getClusterCapacities(); + ClusterDetailsVO clusterDetailsOverCommitRatio = mock(ClusterDetailsVO.class); + when(clusterDetailsOverCommitRatio.getValue()).thenReturn("1.0"); + when(clusterDetailsDao.findDetail(anyLong(), anyString())).thenReturn(clusterDetailsOverCommitRatio); + + Map clusterByCombinedCapacity = planner.getClusterByCombinedCapacities(mockCapacity, 0.5); + Long firstClusterId = clusterByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Cluster with ID 1 should be first in ordering", Long.valueOf(1L), firstClusterId); + Assert.assertEquals("Cluster 1 combined capacity should match expected value", + 0.046875, clusterByCombinedCapacity.get(1L), TOLERANCE); + Assert.assertEquals("Cluster 2 combined capacity should match expected value", + 0.07421875, clusterByCombinedCapacity.get(2L), TOLERANCE); + + // Test scenario 2: Modified capacity usage (0.7 weight) + when(mockCapacity.get(0).getUsedCapacity()).thenReturn(2000L); + clusterByCombinedCapacity = planner.getClusterByCombinedCapacities(mockCapacity, 0.7); + firstClusterId = clusterByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Cluster with ID 2 should be first in ordering", Long.valueOf(2L), firstClusterId); + Assert.assertEquals("Cluster 2 combined capacity should match expected value", + 0.05390625, clusterByCombinedCapacity.get(2L), TOLERANCE); + Assert.assertEquals("Cluster 1 combined capacity should match expected value", + 0.0625, clusterByCombinedCapacity.get(1L), TOLERANCE); + } + private List initializeForClusterThresholdDisabled() { when(configDepot.getConfigStringValue(DeploymentClusterPlanner.ClusterThresholdEnabled.key(), ConfigKey.Scope.Global, null)).thenReturn(Boolean.FALSE.toString()); @@ -327,7 +395,7 @@ private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDe hostList6.add(new Long(15)); String[] implicitHostTags = {"GPU"}; int ramInBytes = ramInOffering * 1024 * 1024; - when(serviceOfferingDetailsDao.findDetail(ArgumentMatchers.anyLong(), anyString())).thenReturn(null); + when(serviceOfferingDetailsDao.findDetail(anyLong(), anyString())).thenReturn(null); when(hostGpuGroupsDao.listHostIds()).thenReturn(hostList0); when(capacityDao.listHostsWithEnoughCapacity(noOfCpusInOffering * cpuSpeedInOffering, ramInBytes, new Long(1), Host.Type.Routing.toString())).thenReturn(hostList1); when(capacityDao.listHostsWithEnoughCapacity(noOfCpusInOffering * cpuSpeedInOffering, ramInBytes, new Long(2), Host.Type.Routing.toString())).thenReturn(hostList2); @@ -505,4 +573,70 @@ public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOEx } } } + + List getClusterCapacities() { + CapacityVO cpuCapacity1 = mock(CapacityVO.class); + when(cpuCapacity1.getClusterId()).thenReturn(1L); + when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity1.getUsedCapacity()).thenReturn(1000L); + when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO cpuCapacity2 = mock(CapacityVO.class); + when(cpuCapacity2.getClusterId()).thenReturn(2L); + when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity2.getUsedCapacity()).thenReturn(750L); + when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO memCapacity1 = mock(CapacityVO.class); + when(memCapacity1.getClusterId()).thenReturn(1L); + when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity1.getReservedCapacity()).thenReturn(0L); + when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + + CapacityVO memCapacity2 = mock(CapacityVO.class); + when(memCapacity2.getClusterId()).thenReturn(2L); + when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity2.getReservedCapacity()).thenReturn(0L); + when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); + } + + List getPodCapacities() { + CapacityVO cpuCapacity1 = mock(CapacityVO.class); + when(cpuCapacity1.getPodId()).thenReturn(1L); + when(cpuCapacity1.getClusterId()).thenReturn(1L); + when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO cpuCapacity2 = mock(CapacityVO.class); + when(cpuCapacity2.getPodId()).thenReturn(2L); + when(cpuCapacity1.getClusterId()).thenReturn(1L); + when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO memCapacity1 = mock(CapacityVO.class); + when(memCapacity1.getPodId()).thenReturn(1L); + when(cpuCapacity1.getClusterId()).thenReturn(1L); + when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity1.getReservedCapacity()).thenReturn(0L); + when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + + CapacityVO memCapacity2 = mock(CapacityVO.class); + when(memCapacity2.getPodId()).thenReturn(2L); + when(cpuCapacity1.getClusterId()).thenReturn(1L); + when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity2.getReservedCapacity()).thenReturn(0L); + when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); + } } From 99bd5927f98ff54958b1a19ed666039be9eb57cb Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Thu, 19 Jun 2025 14:27:23 +0530 Subject: [PATCH 4/4] update desc and add validation --- .../main/java/com/cloud/configuration/ConfigurationManager.java | 2 +- .../java/com/cloud/configuration/ConfigurationManagerImpl.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 9ea3733e0d4d..5655a0593c83 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -68,7 +68,7 @@ public interface ConfigurationManager { ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Float.class, "host.capacityType.to.order.clusters.cputomemoryweight", "0.5", - "CPU to Memory weight used for COMBINED capacityTye to order cluster and host", + "Weight for CPU (as a value between 0 and 1) applied to compute capacity for Pods, Clusters and Hosts for COMBINED capacityType for ordering. Weight for RAM will be (1 - weight of CPU)", true, ConfigKey.Scope.Global); /** diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index b93d03a80344..b31581b582db 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -614,6 +614,7 @@ protected void weightBasedParametersForValidation() { weightBasedParametersForValidation.add(CapacityManager.SecondaryStorageCapacityThreshold.key()); weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceThreshold.key()); weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceSkipThreshold.key()); + weightBasedParametersForValidation.add(ConfigurationManager.HostCapacityTypeCpuMemoryWeight.key()); } protected void overProvisioningFactorsForValidation() {