/* Copyright (c) 2016 EMC Corporation * All Rights Reserved * */ package com.emc.storageos.api.service.impl.resource.cinder; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.UUID; import org.apache.http.util.TextUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.cinder.CinderConstants; import com.emc.storageos.cinder.model.CinderUsage; import com.emc.storageos.cinder.model.UsageAndLimits; import com.emc.storageos.cinder.model.UsageStats; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.QuotaClassOfCinder; import com.emc.storageos.db.client.model.QuotaOfCinder; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.security.authentication.StorageOSUser; public class QuotaHelper { private DbClient _dbClient; private PermissionsHelper _permissionsHelper; private static final Logger _log = LoggerFactory.getLogger(QuotaHelper.class); private static QuotaHelper instQuotaHelpers = null; private static Object mutex = new Object(); private static final long GB = 1024 * 1024 * 1024; private QuotaHelper(DbClient dbClient, PermissionsHelper permissionsHelper) { _dbClient = dbClient; _permissionsHelper = permissionsHelper; } public QuotaHelper() { } public static QuotaHelper getInstance(DbClient dbClient, PermissionsHelper permissionsHelper) { if (instQuotaHelpers == null) { synchronized (mutex) { if (instQuotaHelpers == null) { instQuotaHelpers = new QuotaHelper(dbClient, permissionsHelper); } } } return instQuotaHelpers; } private CinderHelpers getCinderHelper() { return CinderHelpers.getInstance(_dbClient, _permissionsHelper); } /* * This function populates the quotas map with the vpool default quotas * @param qMap - existing quotas hashmap * @param openstackTargetTenantId - target tenant for whose vpools we are populating * @param vPoolName - if we want to specifically populate a specific pool, we have to pass this. * @return updated quotas map */ public HashMap<String, String> populateVolumeTypeDefaultsForQuotaClass(HashMap<String, String> qMap, String openstackTargetTenantId, String vPoolName){ List<URI> vpools = _dbClient.queryByType(VirtualPool.class, true); for (URI vpool : vpools) { VirtualPool pool = _dbClient.queryObject(VirtualPool.class, vpool); _log.debug("Looking up vpool {}", pool.getLabel()); if( (vPoolName != null) && (!vPoolName.equals(pool.getLabel().toString())) ){ continue; } if (pool != null && pool.getType().equalsIgnoreCase(VirtualPool.Type.block.name())) { if (_permissionsHelper.tenantHasUsageACL(URI.create(openstackTargetTenantId), pool)) { for(CinderConstants.ResourceQuotaDefaults item : CinderConstants.ResourceQuotaDefaults.class.getEnumConstants()){ if(!qMap.containsKey(item.getResource()+"_"+pool.getLabel())){ qMap.put(item.getResource()+"_"+pool.getLabel() , String.valueOf(CinderConstants.DEFAULT_VOLUME_TYPE_QUOTA)); } } } } } return qMap; } /* * This function populates the quotas map with the vpool default quotas * * @param vPoolName - vpool for which we want create default limits. If null, we create defaults string * for the project. * @return string with limits in key value pairs demarcated by : * example: "volumes=200:snapshots=100:gigabytes=10000:" */ public String createDefaultLimitsInStrFormat(String vPoolName){ String suffix = ""; if(!TextUtils.isEmpty(vPoolName)){ suffix = "_" + vPoolName ; } String strLimits = ""; for(CinderConstants.ResourceQuotaDefaults item : CinderConstants.ResourceQuotaDefaults.class.getEnumConstants()){ if(TextUtils.isEmpty(vPoolName)){ strLimits = strLimits + item.getResource() + suffix + "=" + String.valueOf(item.getLimit()) +":"; } else{ strLimits = strLimits + item.getResource() + suffix + "=" + String.valueOf(CinderConstants.DEFAULT_VOLUME_TYPE_QUOTA) +":"; } } return strLimits; } /* * This function populates the hashmap with quota class details * @param className - quota class name * @return hashmap of strings with resource name being the key and resource limit * being the value of the hashmap * */ public HashMap<String, String> loadFromDbQuotaClass(String className){ List<URI> quotasInDb = _dbClient.queryByType(QuotaClassOfCinder.class, true); for (URI quota : quotasInDb) { QuotaClassOfCinder defaultQuota = _dbClient.queryObject(QuotaClassOfCinder.class, quota); if(defaultQuota.getQuotaClass().equals(className)){ _log.debug("defaultQuota.getLimits() is {}", defaultQuota.getLimits()); return convertKeyValPairsStringToMap(defaultQuota.getLimits()); } } return null; } /* * This function populates the quotas map with the vpool default quotas * * @param quotasInStringFormat - quota limits in string representation ex:"volumes=200:snapshots=100:gigabytes=10000:" * @return hashmap of resource and quota limit pairs.{(volumes , 200) , (snapshots,100),(gigabytes,10000)} * */ public HashMap<String, String> convertKeyValPairsStringToMap(String quotasInStringFormat){ String[] splits = quotasInStringFormat.split(":"); HashMap<String, String> resp = new HashMap<String, String> (); for(String resourceLimit: splits){ _log.info("resourceLimit is {}", resourceLimit); if(!resourceLimit.equalsIgnoreCase("")){ String[] resourceAndItsLimit = resourceLimit.split("=", 2); String resource = resourceAndItsLimit[0]; String quota = resourceAndItsLimit[1]; resp.put(resource, quota); } } _log.info("resp is {}", resp.toString()); return resp; } /* * @brief This function populates the quotas map with the vpool default quotas * * @param quotasMap of resource and quota limit pairs."{(volumes , 200) , (snapshots,100),(gigabytes,10000)}" * @return String- quota limits in string representation ex:"volumes=200:snapshots=100:gigabytes=10000:" * */ public String convertMapToKeyValPairsString(HashMap<String, String> quotasMap){ String strResp = ""; for(String item : quotasMap.keySet()){ strResp = strResp+ item + "=" + quotasMap.get(item)+":"; } return strResp; } /** * @brief load default quota class details from the DB. * If the db entry is not there, then it is created with core attributes defined in the enum * CinderConstants.ResourceQuotaDefaults * @return HashMap with details of resource and its limits. */ public HashMap<String,String> loadDefaultsMapFromDb(){ HashMap<String, String> defaultQuotaMap = new HashMap<String, String>(); HashMap<String,String> map = loadFromDbQuotaClass(CinderConstants.DEFAULT_QUOTA_CLASS); if(map == null){ QuotaClassOfCinder objQuotaClassOfCinder = new QuotaClassOfCinder(); String tmpStr = ""; for(CinderConstants.ResourceQuotaDefaults item : CinderConstants.ResourceQuotaDefaults.class.getEnumConstants()){ tmpStr = tmpStr + item.getResource() + "=" + item.getLimit()+":"; defaultQuotaMap.put(item.getResource(), String.valueOf(item.getLimit())); } objQuotaClassOfCinder.setLimits(tmpStr); objQuotaClassOfCinder.setQuotaClass(CinderConstants.DEFAULT_QUOTA_CLASS); objQuotaClassOfCinder.setId(URI.create(UUID.randomUUID().toString())); _dbClient.createObject(objQuotaClassOfCinder); } else{ return map; } return defaultQuotaMap; } /** * Get quota for provided vpool * * * @prereq none * * @param tenantId * @param vpool * * @brief get vpool quota * @return quota */ public QuotaOfCinder getVPoolQuota(String tenantId, VirtualPool vpool, StorageOSUser user) { _log.debug("In getVPoolQuota"); Project project = getCinderHelper().getProject(tenantId.toString(), user); List<URI> quotas = _dbClient.queryByType(QuotaOfCinder.class, true); for (URI quota : quotas) { QuotaOfCinder quotaObj = _dbClient.queryObject(QuotaOfCinder.class, quota); URI vpoolUri = quotaObj.getVpool(); if (vpoolUri == null) { continue; } else if ((quotaObj.getProject() != null) && (quotaObj.getProject().toString().equalsIgnoreCase(project.getId().toString()))) { VirtualPool pool = _dbClient.queryObject(VirtualPool.class, vpoolUri); if ((pool != null) && (pool.getLabel().equals(vpool.getLabel())) && (vpool.getLabel() != null) && (vpool.getLabel().length() > 0)) { return quotaObj; } } } HashMap< String, String> qMap = getCompleteDefaultConfiguration(tenantId); return createVpoolDefaultQuota(project, vpool, qMap); } /** * Get quota for given project/tenant * * * @prereq none * * @param tenantId * * @brief get project quota * @return quota */ public QuotaOfCinder getProjectQuota(String tenantId, StorageOSUser user) { Project project = getCinderHelper().getProject(tenantId.toString(), user); List<URI> quotas = _dbClient.queryByType(QuotaOfCinder.class, true); for (URI quota : quotas) { QuotaOfCinder quotaObj = _dbClient.queryObject(QuotaOfCinder.class, quota); URI vpoolUri = quotaObj.getVpool(); if (vpoolUri != null) { continue; } if ((quotaObj.getProject() != null) && (quotaObj.getProject().toString().equalsIgnoreCase(project.getId().toString()))) { return quotaObj; } } HashMap< String, String> qMap = getCompleteDefaultConfiguration(tenantId); return createProjectDefaultQuota(project, qMap); } /** * Get default quota for provided project * * * @prereq none * * @param project * param defaultQuotaMap (comprehensive default quota map) * @brief get project default quota * @return quota */ public QuotaOfCinder createProjectDefaultQuota(Project project, HashMap<String,String> defaultQuotaMap) { long maxQuota = 0; if (project.getQuotaEnabled()) { maxQuota = (long) (project.getQuota().intValue()); } else { maxQuota = Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.GIGABYTES.getResource())); } QuotaOfCinder quotaObj = new QuotaOfCinder(); quotaObj.setId(URI.create(UUID.randomUUID().toString())); quotaObj.setProject(project.getId()); quotaObj.setVolumesLimit(Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.VOLUMES.getResource()))); quotaObj.setSnapshotsLimit(Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.SNAPSHOTS.getResource()))); quotaObj.setTotalQuota(maxQuota); _log.info("Creating default quota for project"); _dbClient.createObject(quotaObj); return quotaObj; } /** * Create default quota for vpool * * * @prereq none * * @param project * @param vpool * @param defaultQuotaMap (comprehensive default quota map) * @brief create default quota * @return quota */ public QuotaOfCinder createVpoolDefaultQuota(Project project, VirtualPool vpool, HashMap<String,String> defaultQuotaMap) { //HashMap<String, String> defaultQuotaMap = loadDefaultsMap(); QuotaOfCinder objQuotaOfCinder = new QuotaOfCinder(); objQuotaOfCinder.setProject(project.getId()); objQuotaOfCinder.setVolumesLimit(Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.VOLUMES.getResource()+"_"+vpool.getLabel()))); objQuotaOfCinder.setSnapshotsLimit(Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.SNAPSHOTS.getResource()+"_"+vpool.getLabel()))); objQuotaOfCinder.setTotalQuota(Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.GIGABYTES.getResource()+"_"+vpool.getLabel()))); objQuotaOfCinder.setId(URI.create(UUID.randomUUID().toString())); objQuotaOfCinder.setVpool(vpool.getId()); _log.info("Create vpool default quota"); _dbClient.createObject(objQuotaOfCinder); return objQuotaOfCinder; } /** * Get usage statistics(like total number of volumes, snapshots and total size) for given vpool * * * @prereq none * * @param vpool * * @brief get statistics * @return UsageStats */ public UsageStats getStorageStats(URI vpool, URI projectId) { UsageStats objStats = new UsageStats(); double totalSnapshotsUsed = 0; double totalSizeUsed = 0; long totalVolumesUsed = 0; URIQueryResultList uris = new URIQueryResultList(); if (vpool != null) { URIQueryResultList volUris = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVirtualPoolVolumeConstraint(vpool), volUris); for (URI voluri : volUris) { Volume volume = _dbClient.queryObject(Volume.class, voluri); if (volume != null && !volume.getInactive() && (volume.getProject().getURI().toString().equals(projectId.toString())) ) { totalSizeUsed += ((double)volume.getCapacity() / GB); totalVolumesUsed++; } URIQueryResultList snapList = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(voluri), snapList); for (URI snapUri : snapList) { BlockSnapshot blockSnap = _dbClient.queryObject(BlockSnapshot.class, snapUri); if (blockSnap != null && !blockSnap.getInactive()) { _log.info("ProvisionedCapacity = {} ", blockSnap.getProvisionedCapacity()); totalSizeUsed += ((double)blockSnap.getProvisionedCapacity() / GB); totalSnapshotsUsed++; } } } } else { _dbClient.queryByConstraint(ContainmentConstraint.Factory.getProjectVolumeConstraint(projectId), uris); for (URI volUri : uris) { Volume volume = _dbClient.queryObject(Volume.class, volUri); if (volume != null && !volume.getInactive()) { totalSizeUsed += ((double)volume.getCapacity() / GB); totalVolumesUsed++; } URIQueryResultList snapList = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(volUri), snapList); for (URI snapUri : snapList) { BlockSnapshot blockSnap = _dbClient.queryObject(BlockSnapshot.class, snapUri); if (blockSnap != null && !blockSnap.getInactive()) { totalSizeUsed += ((double)blockSnap.getProvisionedCapacity() / GB); totalSnapshotsUsed++; } } } } objStats.snapshots = (long)totalSnapshotsUsed; objStats.volumes = totalVolumesUsed; objStats.spaceUsed = (long)(Math.round(totalSizeUsed)); return objStats; } /** * Get default quota class "default" from db. If not defined * create an entry in the db for the core attributes(snapshots, volumes, gigabytes). * Additionally, it populates defaults for vpools if not already defined for the quota class. * * * @prereq none * * @param openstackTargetTenantId openstack tenant id * * @brief quotaMap * @return HashMap */ public HashMap<String,String> getCompleteDefaultConfiguration(String openstackTargetTenantId){ HashMap<String,String> qMap = loadDefaultsMapFromDb(); qMap = populateVolumeTypeDefaultsForQuotaClass(qMap , openstackTargetTenantId, null); _log.debug("getCompleteDefaultConfiguration is {}",qMap); return qMap; } /** * Get usage statistics in terms of quota attributes like gigabytes,snapshots, volumes. * The details include in_use,reserved,limit. * * * @prereq none * * @param tenantId * @param quotaMap of project and vpools * @param proj project under consideration * * @brief get usage statistics in terms of project and volume types. * @return CinderUsage */ public CinderUsage getUsageStatistics(URI tenantId , HashMap<String, String> quotaMap, Project proj){ CinderUsage objCinderUsage = new CinderUsage(); List<URI> vpools = _dbClient.queryByType(VirtualPool.class, true); for (URI vpool : vpools) { VirtualPool pool = _dbClient.queryObject(VirtualPool.class, vpool); _log.debug("Looking up vpool {}", pool.getLabel()); if (pool != null && pool.getType().equalsIgnoreCase(VirtualPool.Type.block.name())) { if (_permissionsHelper.tenantHasUsageACL(tenantId, pool)) { UsageStats stats = getStorageStats(pool.getId(), proj.getId()); UsageAndLimits objSpaceUsage = new UsageAndLimits(); objSpaceUsage.setIn_use(stats.spaceUsed); objSpaceUsage.setLimit(Long.parseLong(quotaMap.get("gigabytes" + "_" + pool.getLabel()))); UsageAndLimits objVolsUsage = new UsageAndLimits(); objVolsUsage.setIn_use(stats.volumes); objVolsUsage.setLimit(Long.parseLong(quotaMap.get("volumes" + "_" + pool.getLabel()))); UsageAndLimits objSnapsUsage = new UsageAndLimits(); objSnapsUsage.setIn_use(stats.snapshots); objSnapsUsage.setLimit(Long.parseLong(quotaMap.get("snapshots" + "_" + pool.getLabel()))); objCinderUsage.getQuota_set().put("gigabytes" + "_" + pool.getLabel(),objSpaceUsage); objCinderUsage.getQuota_set().put("snapshots" + "_" + pool.getLabel(),objSnapsUsage); objCinderUsage.getQuota_set().put("volumes" + "_" + pool.getLabel(), objVolsUsage); } } } //now get the usage information for the project UsageStats stats = getStorageStats(null, proj.getId()); UsageAndLimits objSpaceUsage = new UsageAndLimits(); objSpaceUsage.setIn_use(stats.spaceUsed); objSpaceUsage.setLimit(Long.parseLong(quotaMap.get("gigabytes"))); UsageAndLimits objVolsUsage = new UsageAndLimits(); objVolsUsage.setIn_use(stats.volumes); objVolsUsage.setLimit(Long.parseLong(quotaMap.get("volumes"))); UsageAndLimits objSnapsUsage = new UsageAndLimits(); objSnapsUsage.setIn_use(stats.snapshots); objSnapsUsage.setLimit(Long.parseLong(quotaMap.get("snapshots"))); objCinderUsage.getQuota_set().put("gigabytes",objSpaceUsage); objCinderUsage.getQuota_set().put("snapshots",objSnapsUsage); objCinderUsage.getQuota_set().put("volumes", objVolsUsage); return objCinderUsage; } }