From abf4e5c646316269072edd47d693e98182a663c6 Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Mon, 26 Jan 2015 10:41:46 -0800 Subject: [PATCH] CLOUDSTACK-8181: Introducing a new allocator called firstfitleastconsumed. The purpose of this allocator is to find hosts/pools with least capacity usage (in terms of percentage) within the cluster and use those resources first before others. This allocator can be used changing vm.allocation.algorithm. For hosts it would decide the least consumed host through the setting host.capacityType.to.order.clusters to base the usage on cpu or ram. Reviewed-by: Prachi --- .../com/cloud/capacity/dao/CapacityDao.java | 2 + .../cloud/capacity/dao/CapacityDaoImpl.java | 25 ++++++++++++ .../AbstractStoragePoolAllocator.java | 39 +++++++++++++++++++ .../allocator/impl/FirstFitAllocator.java | 38 ++++++++++++++++++ .../src/com/cloud/configuration/Config.java | 2 +- 5 files changed, 105 insertions(+), 1 deletion(-) diff --git a/engine/schema/src/com/cloud/capacity/dao/CapacityDao.java b/engine/schema/src/com/cloud/capacity/dao/CapacityDao.java index a900e64c127..50d6052bc46 100644 --- a/engine/schema/src/com/cloud/capacity/dao/CapacityDao.java +++ b/engine/schema/src/com/cloud/capacity/dao/CapacityDao.java @@ -56,4 +56,6 @@ public interface CapacityDao extends GenericDao { List listClustersCrossingThreshold(short capacityType, Long zoneId, String configName, long computeRequested); float findClusterConsumption(Long clusterId, short capacityType, long computeRequested); + + List orderHostsByFreeCapacity(Long clusterId, short capacityType); } diff --git a/engine/schema/src/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/com/cloud/capacity/dao/CapacityDaoImpl.java index d9e61841ed4..2bd6bcc4863 100644 --- a/engine/schema/src/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -98,6 +98,8 @@ public class CapacityDaoImpl extends GenericDaoBase implements private static final String ORDER_PODS_BY_AGGREGATE_OVERCOMMIT_CAPACITY = "SELECT capacity.pod_id, SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) FROM `cloud`.`op_host_capacity` capacity INNER JOIN `cloud`.`cluster_details` cluster_details ON (capacity.cluster_id = cluster_details.cluster_id) WHERE data_center_id=? AND capacity_type = ? AND cluster_details.name = ? GROUP BY capacity.pod_id ORDER BY SUM(used_capacity+reserved_capacity)/SUM(total_capacity * cluster_details.value) ASC"; + private static final String ORDER_HOSTS_BY_FREE_CAPACITY = "SELECT host_id, SUM(total_capacity - (used_capacity+reserved_capacity))/SUM(total_capacity) FROM `cloud`.`op_host_capacity` WHERE " + + " cluster_id = ? AND capacity_type = ? GROUP BY host_id ORDER BY SUM(total_capacity - (used_capacity+reserved_capacity))/SUM(total_capacity) DESC "; private static final String LIST_CAPACITY_BY_RESOURCE_STATE = "SELECT capacity.data_center_id, sum(capacity.used_capacity), sum(capacity.reserved_quantity), sum(capacity.total_capacity), capacity_capacity_type " @@ -857,6 +859,29 @@ public class CapacityDaoImpl extends GenericDaoBase implements } } + @Override + public List orderHostsByFreeCapacity(Long clusterId, short capacityTypeForOrdering){ + TransactionLegacy txn = TransactionLegacy.currentTxn(); + PreparedStatement pstmt = null; + List result = new ArrayList(); + StringBuilder sql = new StringBuilder(ORDER_HOSTS_BY_FREE_CAPACITY); + + try { + pstmt = txn.prepareAutoCloseStatement(sql.toString()); + pstmt.setLong(1, clusterId); + pstmt.setShort(2, capacityTypeForOrdering); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + result.add(rs.getLong(1)); + } + return result; + } catch (SQLException e) { + throw new CloudRuntimeException("DB Exception on: " + sql, e); + } catch (Throwable e) { + throw new CloudRuntimeException("Caught: " + sql, e); + } + } + @Override public List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam, short capacityType) { TransactionLegacy txn = TransactionLegacy.currentTxn(); diff --git a/engine/storage/src/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java index 1bb85c12059..73a8544f51a 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java +++ b/engine/storage/src/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java @@ -35,6 +35,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import com.cloud.capacity.Capacity; +import com.cloud.capacity.dao.CapacityDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.deploy.DeploymentPlan; @@ -72,6 +74,8 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement protected String _allocationAlgorithm = "random"; @Inject DiskOfferingDao _diskOfferingDao; + @Inject + CapacityDao _capacityDao; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -100,6 +104,39 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement return reOrder(pools, vmProfile, plan); } + protected List reorderPoolsByCapacity(DeploymentPlan plan, + List pools) { + Long clusterId = plan.getClusterId(); + short capacityType; + if(pools != null && pools.size() != 0){ + capacityType = pools.get(0).getPoolType().isShared() == true ? + Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED : Capacity.CAPACITY_TYPE_LOCAL_STORAGE; + } else{ + return null; + } + + List poolIdsByCapacity = _capacityDao.orderHostsByFreeCapacity(clusterId, capacityType); + if (s_logger.isDebugEnabled()) { + s_logger.debug("List of pools in descending order of free capacity: "+ poolIdsByCapacity); + } + + //now filter the given list of Pools by this ordered list + Map poolMap = new HashMap(); + for (StoragePool pool : pools) { + poolMap.put(pool.getId(), pool); + } + List matchingPoolIds = new ArrayList(poolMap.keySet()); + + poolIdsByCapacity.retainAll(matchingPoolIds); + + List reorderedPools = new ArrayList(); + for(Long id: poolIdsByCapacity){ + reorderedPools.add(poolMap.get(id)); + } + + return reorderedPools; + } + protected List reorderPoolsByNumberOfVolumes(DeploymentPlan plan, List pools, Account account) { if (account == null) { return pools; @@ -144,6 +181,8 @@ public abstract class AbstractStoragePoolAllocator extends AdapterBase implement Collections.shuffle(pools); } else if (_allocationAlgorithm.equals("userdispersing")) { pools = reorderPoolsByNumberOfVolumes(plan, pools, account); + } else if(_allocationAlgorithm.equals("firstfitleastconsumed")){ + pools = reorderPoolsByCapacity(plan, pools); } return pools; } diff --git a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index a746eb756ca..44552af8647 100644 --- a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -32,6 +32,9 @@ import org.springframework.stereotype.Component; import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.Config; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.dao.ClusterDao; @@ -89,6 +92,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject CapacityManager _capacityMgr; + @Inject + CapacityDao _capacityDao; boolean _checkHvm = true; protected String _allocationAlgorithm = "random"; @@ -235,6 +240,8 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { Collections.shuffle(hosts); } else if (_allocationAlgorithm.equals("userdispersing")) { hosts = reorderHostsByNumberOfVms(plan, hosts, account); + }else if(_allocationAlgorithm.equals("firstfitleastconsumed")){ + hosts = reorderHostsByCapacity(plan, hosts); } if (s_logger.isDebugEnabled()) { @@ -318,6 +325,37 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { return suitableHosts; } + // Reorder hosts in the decreasing order of free capacity. + private List reorderHostsByCapacity(DeploymentPlan plan, List hosts) { + 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; + } + List hostIdsByFreeCapacity = _capacityDao.orderHostsByFreeCapacity(clusterId, capacityType); + if (s_logger.isDebugEnabled()) { + s_logger.debug("List of hosts in descending order of free capacity in the cluster: "+ hostIdsByFreeCapacity); + } + + //now filter the given list of Hosts by this ordered list + Map hostMap = new HashMap(); + for (Host host : hosts) { + hostMap.put(host.getId(), host); + } + List matchingHostIds = new ArrayList(hostMap.keySet()); + + hostIdsByFreeCapacity.retainAll(matchingHostIds); + + List reorderedHosts = new ArrayList(); + for(Long id: hostIdsByFreeCapacity){ + reorderedHosts.add(hostMap.get(id)); + } + + return reorderedHosts; + } + private List reorderHostsByNumberOfVms(DeploymentPlan plan, List hosts, Account account) { if (account == null) { return hosts; diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index f9e9bddc30e..453a3bc6a1a 100644 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -980,7 +980,7 @@ public enum Config { String.class, "vm.allocation.algorithm", "random", - "'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit' : Order in which hosts within a cluster will be considered for VM/volume allocation.", + "'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', 'firstfitleastconsumed' : Order in which hosts within a cluster will be considered for VM/volume allocation.", null), VmDeploymentPlanner( "Advanced",