/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.utils.attrmatchers;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StoragePool.PoolServiceType;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.util.SizeUtil;
import com.emc.storageos.volumecontroller.AttributeMatcher;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.google.common.base.Joiner;
/**
* Capacity Matcher- Pools which satisfy the required capacity values will be retained.
* This is invoked in following scenarios:
* 1. During Provisioning to Match pools based on provisioning attributes.
* 2. Invoked independently to find the recommendation pool based on no. of resources & its capacity.
*
*/
public class CapacityMatcher extends AttributeMatcher {
private static final int BYTESCONVERTER = 1024;
public static final String MAX_THIN_POOL_SUBSCRIPTION_PERCENTAGE = "controller_max_thin_pool_subscription_percentage"; // default
// percentage of
// subscription
// rate.
public static final String MAX_POOL_UTILIZATION_PERCENTAGE = "controller_max_pool_utilization_percentage"; // percentage of pool
// utilization limit.
public static final String ZERO = "0L";
private static final Logger _log = LoggerFactory.getLogger(CapacityMatcher.class);
@Override
protected boolean isAttributeOn(Map<String, Object> attributeMap) {
if (null != attributeMap && attributeMap.containsKey(Attributes.size.toString())) {
return true;
}
return false;
}
@Override
protected List<StoragePool> matchStoragePoolsWithAttributeOn(List<StoragePool> pools, Map<String, Object> attributeMap,
StringBuffer errorMessage) {
_log.info("Pools Matching capacity Started:" + Joiner.on("\t").join(getNativeGuidFromPools(pools)));
List<StoragePool> filteredPoolList = new ArrayList<StoragePool>(pools);
Long resourceSize = (Long) attributeMap.get(Attributes.size.toString());
// During initial pool matching it will be always one. This is need as the logic used to check
// multiVolume Size.
long requiredCapacity = 1L * resourceSize;
Iterator<StoragePool> poolIterator = pools.iterator();
boolean supportsThinProvisioning = false;
long thinVolumePreAllocationSize = 0;
if (attributeMap.containsKey(Attributes.provisioning_type.toString())
&& VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(
attributeMap.get(Attributes.provisioning_type.toString()).toString())) {
supportsThinProvisioning = true;
if (attributeMap.containsKey(Attributes.thin_volume_preallocation_size.toString())) {
thinVolumePreAllocationSize = (Long) attributeMap
.get(Attributes.thin_volume_preallocation_size.toString());
}
}
while (poolIterator.hasNext()) {
StoragePool pool = poolIterator.next();
// During provisioning matching, this matcher runs against the volume size requested.
if (!poolMatchesCapacity(pool, requiredCapacity, resourceSize, true, supportsThinProvisioning, thinVolumePreAllocationSize)) {
filteredPoolList.remove(pool);
}
}
if (CollectionUtils.isEmpty(filteredPoolList)) {
errorMessage.append(String.format(
"No matching storage pool with %s GB free capacity found. "
+ "Consider increasing Utilization Threshold for the system storage pools in the virtual pool, "
+ "or adding storage pools to the vPool",
SizeUtil.translateSize(requiredCapacity, SizeUtil.SIZE_GB)));
_log.error(errorMessage.toString());
}
_log.info("Pools Matching capacity Ended :" + Joiner.on("\t").join(getNativeGuidFromPools(filteredPoolList)));
return filteredPoolList;
}
/**
* Decision is made as following:
* 1. Check volume size against maximum volume size limit of storage pool (if required).
* 2.
* --- Thick pool: solely based on the pool utilization capacity including the current request
* & the requested capacity < pool freeCapacity.
* --- Thin pool: It depends on two factors.
* 1. pool utilization -> Pool should be less utilized
* 2. pool subscribedCapacity -> pool should subscribed less than user configured.
*
* @param pool
* storage pool
* @param requiredCapacity : requested size
* This capacity changes as per the provisioning logic.
* It could be requiredCapacity = resourceSize * resourceCount.
* This should be computed whoever calls this method.
* @param resourceSize : always size of the requested value capacity
* @param checkPoolMaxSizeLimit
* indicates if the size should be checked against max. limits for volume
* size in the pool
* @param supportsThinProvisioning : flag tells whether Thin or Thick.
* @return true/false
*/
public boolean poolMatchesCapacity(StoragePool pool, long requiredCapacity, long resourceSize,
boolean checkPoolMaxSizeLimit, boolean supportsThinProvisioning, Long thinVolumePreAllocationResourceSize) {
if (null == pool.getTotalCapacity() || pool.getTotalCapacity() == 0) {
return false;
}
long preAllocationSizeInKB = 0;
// requiredCapacity in KB.
long sizeInKB = getSizeInKB(requiredCapacity);
// single resource size in KB.
long resourceSizeInKB = getSizeInKB(resourceSize);
// Step 1: Check for Maximum Volume size limit if block type pool.
if ((PoolServiceType.block.toString().equalsIgnoreCase(pool.getPoolServiceType()) ||
PoolServiceType.block_file.name().equalsIgnoreCase(pool.getPoolServiceType()))
&& checkPoolMaxSizeLimit) {
// check against maximum volume size limit of storage pool
// limit in kilobytes
Long maxVolumeSizeLimit = getMaxVolumeSizeLimit(pool, supportsThinProvisioning);
if (maxVolumeSizeLimit == null || maxVolumeSizeLimit == 0) {
String errorMsg = String.format("Pool %s does not have maximum size limit for %s volumes set.",
pool.getId(), pool.getSupportedResourceTypes());
_log.error(errorMsg);
return false;
}
// Clarion volumes in Unified pools can not be created as composite
// volumes (meta volumes).
if (null != pool.getPoolClassName()
&& (pool.getPoolClassName().equals(StoragePool.PoolClassNames.Clar_UnifiedStoragePool.toString())
|| pool.getPoolClassName().equals(StoragePool.PoolClassNames.VNXe_Pool.name()))
&& resourceSizeInKB > maxVolumeSizeLimit) {
_log.info(String
.format("Pool %s is not matching as the pool's maximum volume size (%s KB) is below the requested volume size %s KB.",
pool.getId(), maxVolumeSizeLimit, resourceSizeInKB));
return false;
}
}
// Step 2: Check whether StoragePool is Thick or not.
if (pool.getSupportedResourceTypes() != null
&& pool.getSupportedResourceTypes().equals(StoragePool.SupportedResourceTypes.THICK_ONLY.name())
|| !supportsThinProvisioning) {
// If it is thick then check whether pool has more utilization or not.
return isPoolMatchesCapacityForThickProvisioning(pool, sizeInKB);
}
if (null != thinVolumePreAllocationResourceSize) {
preAllocationSizeInKB = getSizeInKB(thinVolumePreAllocationResourceSize);
}
// Step 3: Check whether StoragePool is Thin or not.
return isPoolMatchesCapacityForThinProvisioning(pool, sizeInKB, preAllocationSizeInKB, _coordinator);
}
/**
* return size in KB
*
* @param resourceSize
* @return
*/
private long getSizeInKB(long resourceSize) {
return (resourceSize % BYTESCONVERTER == 0) ? resourceSize / BYTESCONVERTER : resourceSize / BYTESCONVERTER + 1;
}
/**
* Return VolumeSizeLimit based on supportsThinProvisioning flag.
*
* @param pool
* @param supportsThinProvisioning
* @return
*/
private Long getMaxVolumeSizeLimit(StoragePool pool, boolean supportsThinProvisioning) {
Long maxVolumeSizeLimit = null;
if (supportsThinProvisioning) {
maxVolumeSizeLimit = pool.getMaximumThinVolumeSize();
} else {
maxVolumeSizeLimit = pool.getMaximumThickVolumeSize();
}
return maxVolumeSizeLimit;
}
/**
* Check whether Thick pool is matching with the capacity requirement or not.
*
* @param pool
* @param requiredCapacityInKB
* @return
*/
private boolean isPoolMatchesCapacityForThickProvisioning(StoragePool pool, long requiredCapacityInKB) {
if (requiredCapacityInKB > pool.getFreeCapacity()) {
_log.info(String
.format("Pool %s is not matching as it doesn't have enough space to create resources. Pool has %dKB, Required capacity of request %dKB",
pool.getId(),
pool.getFreeCapacity().longValue(),
requiredCapacityInKB));
return false;
}
if (!checkThickPoolCandidacy(pool, requiredCapacityInKB, _coordinator)) {
String msg = String
.format("Pool %s is not matching as it will have utilization of %s percent after allocation. Pool's max utilization percentage is %s percent .",
pool.getId(), 100 - getThickPoolFreeCapacityPercentage(pool, requiredCapacityInKB),
getMaxPoolUtilizationPercentage(pool, _coordinator));
_log.info(msg);
return false;
}
return true;
}
/**
* Return the thickPoolFreeCapacity percentage.
*
* @param pool
* @param requiredCapacityInKB
* @return
*/
public static double getThickPoolFreeCapacityPercentage(StoragePool pool, long requiredCapacityInKB) {
return ((pool.getFreeCapacity().doubleValue() - requiredCapacityInKB) / pool
.getTotalCapacity()) * 100;
}
/**
* Verifies whether Thick pool is more utilized or less utilized based on the user configured max utilized value.
*
* Step 1: Calculate the new freeCapacity Percentage including the current requested capacity.
* Step 2: Determine the maximumPoolUtlization Percentage If user is configured else use the default.
* Step 3: Check whether requiredCapacityInKB is less than Pool FreeCapacity & isPoolLessUtilized.
*
* Formulae : [newPoolUtilizationPercentage <= poolMaxUtilizationPercentage]
*
* newPoolUtilizationPercentage = [100 - poolFreeCapacityPercentage]
*
* poolFreeCapacityPercentage = [(poolFreeCapacity - requestedCapacity)/poolTotalCapacity) * 100]
*
* @param pool : Pool to verify
* @param requiredCapacityInKB : requiredCapacity to check against.
* @return
*/
public static boolean checkThickPoolCandidacy(StoragePool pool, long requiredCapacityInKB, CoordinatorClient coordinator) {
return (100 - getThickPoolFreeCapacityPercentage(pool, requiredCapacityInKB)) <= getMaxPoolUtilizationPercentage(pool, coordinator);
}
/**
* return the pool max utilization if it is set else return default value.
*
* @param pool
* @return
*/
public static double getMaxPoolUtilizationPercentage(StoragePool pool, CoordinatorClient coordinator) {
return (pool.getMaxPoolUtilizationPercentage() != null && pool
.getMaxPoolUtilizationPercentage() != 0L) ? pool.getMaxPoolUtilizationPercentage()
: Integer.valueOf(ControllerUtils.getPropertyValueFromCoordinator(coordinator, MAX_POOL_UTILIZATION_PERCENTAGE));
}
/**
* Check whether Thin Pool matches with the pool capacity requirement or not.
*
* Formulae: [isPoolLessUtilized && isPoolLessSubscribed]
*
* @param pool
* @return
*/
public boolean isPoolMatchesCapacityForThinProvisioning(StoragePool pool,
long requestedCapacityInKB, long preAllocationSize,
CoordinatorClient coordinator) {
if (!isThinPoolLessUtilized(pool, preAllocationSize, coordinator)) {
String msg = String
.format("Thin pool %s is not matching as it will have utilization of %s percent after allocation. Pool's max utilization percentage is %s percent .",
pool.getId(), 100 - getThinPoolFreeCapacityPercentage(pool, preAllocationSize),
getMaxPoolUtilizationPercentage(pool, coordinator));
_log.info(msg);
return false;
}
DbClient dbClient = _objectCache.getDbClient();
StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, pool.getStorageDevice());
if (DiscoveredDataObject.Type.isThinPoolSubscribedCheckNeeded(storageSystem.getSystemType())
&& !isThinPoolLessSubscribed(pool, requestedCapacityInKB, coordinator)) {
String msg = String
.format("Thin pool %s is not matching as it will have %s percent subscribed after allocation. Pool's max subscription percentage is %s percent .",
pool.getId(), getThinPoolSubscribedCapacityPercentage(pool, requestedCapacityInKB),
getMaxPoolSubscriptionPercentage(pool, coordinator));
_log.info(msg);
return false;
}
return true;
}
/**
* Verifies whether pool is more utilized or less utilized based on the user configured max utilized value.
*
* Step 1: Calculate the pool freeCapacity Percentage.
* Step 2: Determine the maximumPoolUtlization Percentage If user is configured else use the default.
* Step 3: Check whether pool is isLessUtilized or not.
*
* Formulae: [(100 - poolFreeCapacityPercentage) <= maximumPoolUtilizationPercentage]
*
* @param pool : Pool to verify
* @return
*/
private static boolean isThinPoolLessUtilized(StoragePool pool, long preAllocationSize, CoordinatorClient coordinator) {
final boolean isPoolUtilizedLess = (100 - getThinPoolFreeCapacityPercentage(pool, preAllocationSize)) <= getMaxPoolUtilizationPercentage(
pool, coordinator);
return isPoolUtilizedLess;
}
/**
* Calculates the ThinPoolFreeCapacityPercentage.
*
* Formulae: [(poolFreeCapacity - thinVolumePreAllocationSize/poolTotalCapacity) * 100]
*
* @param pool
* @return
*/
private static double getThinPoolFreeCapacityPercentage(StoragePool pool, long preAllocationSize) {
return ((pool.getFreeCapacity().doubleValue() - preAllocationSize) / pool.getTotalCapacity()) * 100;
}
/**
* Returns true if Thin pool is subscribed less than user configured value else false.
*
* Formulae: [poolSubscribedCapacityPercentage <= poolMaxSubscriptionPercentage]
*
* @param pool
* @param requestedCapacityInKB
* @return
*/
public static boolean isThinPoolLessSubscribed(StoragePool pool, long requestedCapacityInKB, CoordinatorClient coordinator) {
return getThinPoolSubscribedCapacityPercentage(pool, requestedCapacityInKB) <= getMaxPoolSubscriptionPercentage(pool, coordinator);
}
/**
* return thinPoolSubscribeCapacityPercentage which include the current request resource size.
*
* Formulae: [((poolSubscribedCapacity+requestedCapacity)/poolTotalCapacity) * 100]
*
* @param pool
* @param requestedCapacityInKB
* @return
*/
private static double getThinPoolSubscribedCapacityPercentage(StoragePool pool, long requestedCapacityInKB) {
// thinPoolSubscribedCapacity includes the current resource capacity.
return (((pool.getSubscribedCapacity().doubleValue() + requestedCapacityInKB) / pool.getTotalCapacity()) * 100);
}
/**
* return the pool subscribed percentage if it is set else return default value.
*
* @param pool
* @return
*/
public static double getMaxPoolSubscriptionPercentage(StoragePool pool, CoordinatorClient coordinator) {
return (pool.getMaxThinPoolSubscriptionPercentage() != null && pool.getMaxThinPoolSubscriptionPercentage() != 0L)
? pool.getMaxThinPoolSubscriptionPercentage() : Integer.valueOf(
ControllerUtils.getPropertyValueFromCoordinator(coordinator, MAX_THIN_POOL_SUBSCRIPTION_PERCENTAGE));
}
}