/* 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.Map; import java.util.UUID; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.DOMException; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.resource.TaskResourceService; import com.emc.storageos.api.service.impl.resource.utils.CinderApiUtils; import com.emc.storageos.api.service.impl.response.ProjOwnedResRepFilter; import com.emc.storageos.api.service.impl.response.ResRepFilter; import com.emc.storageos.cinder.CinderConstants; import com.emc.storageos.cinder.model.CinderQuotaClassDetails; import com.emc.storageos.db.client.model.DataObject; 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.model.RelatedResourceRep; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.CheckPermission; import com.emc.storageos.security.authorization.DefaultPermissions; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.sun.jersey.api.client.ClientResponse; @Path("/v2/{tenant_id}/os-quota-class-sets") @DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL }) @SuppressWarnings({ "unchecked", "rawtypes" }) public class QuotaClassService extends TaskResourceService { private static final Logger _log = LoggerFactory.getLogger(QuotaClassService.class); private static final String EVENT_SERVICE_TYPE = "block"; private CinderHelpers getCinderHelper() { return CinderHelpers.getInstance(_dbClient, _permissionsHelper); } private QuotaHelper getQuotaHelper() { return QuotaHelper.getInstance(_dbClient, _permissionsHelper); } /** * Update a quota class * * @prereq none * * @param tenant_id the URN of the tenant * @param quota_class_name the name of quota class which needs to be modified * * @brief Update Quota class. If the quota class exists, then we update the quota class. * If the quota class does not exist, then we create a quota class. * * @return Quota details of quota class */ @PUT @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{quota_class_name}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public Response updateQuotaClass(@PathParam("tenant_id") String openstackTenantId, CinderQuotaClassDetails quotaClassUpdates, @Context HttpHeaders header) { String quotaClassName = quotaClassUpdates.quota_class_set.get(CinderConstants.CLASS_NAME_KEY); _log.debug("quotaClassUpdates.quota_class_set is {}",quotaClassUpdates.quota_class_set); boolean bVpoolQuotaUpdate = isVpoolQuotaUpdate(quotaClassUpdates.quota_class_set); String vpoolName = null; VirtualPool objVpool = null; if(bVpoolQuotaUpdate){ vpoolName = getVpoolName(quotaClassUpdates.quota_class_set); _log.debug("Vpool for which quota is being updated is {}", vpoolName); objVpool = getCinderHelper().getVpool(vpoolName); if (objVpool == null) { _log.error("vpool with the given name doesnt exist"); throw APIException.badRequests.parameterIsNotValid(vpoolName); } } List<URI> quotaClasses = _dbClient.queryByType(QuotaClassOfCinder.class, true); CinderQuotaClassDetails resp = new CinderQuotaClassDetails(); _log.debug("quotaClassName is {}", quotaClassName); for (URI quota : quotaClasses) { QuotaClassOfCinder quotaClass = _dbClient.queryObject(QuotaClassOfCinder.class, quota); if(quotaClass.getQuotaClass().equals(quotaClassName)){ _log.debug("quotaClass.getLimits() is {}",quotaClass.getLimits()); HashMap<String,String> qMap = (HashMap<String, String>)getQuotaHelper().convertKeyValPairsStringToMap(quotaClass.getLimits()); qMap.putAll(quotaClassUpdates.quota_class_set); qMap.remove("class_name"); quotaClass.setLimits(getQuotaHelper().convertMapToKeyValPairsString(qMap)); _dbClient.updateObject(quotaClass); //Till this step, we have updated the quota class in the DB. //Let us say that the update request body is as below //{"quota_class_set": {"class_name": "default", "gigabytes_vnx-vpool-1": 102, "snapshots_vnx-vpool-1": 102, "volumes_vnx-vpool-1": 102}} //now the result should contain the limits already defined in db for the project or volume types //and the default class limits for attributes pertaining to project and volume types, which haven't been defined //and the attribute that have just now been updated. // //{"quota_class_set": {"gigabytes_ViPR-VMAX": -1, "snapshots_ViPR-VMAX": -1, // "snapshots": 10, "volumes_ViPR-VMAX": -1, // "snapshots_vnx-vpool-1": 102, // // "gigabytes_vnx-vpool-1": 102, "volumes_vnx-vpool-1": 102, // "gigabytes": 1000, // "gigabytes_vt-1": -1, "volumes": 10 }} qMap = getQuotaHelper().populateVolumeTypeDefaultsForQuotaClass(qMap , openstackTenantId, null); resp.quota_class_set.putAll(qMap); _log.debug("resp.quota_class_set is {}" , resp.quota_class_set.toString()); return getQuotaClassDetailFormat(header, resp); } else{ continue; } } //If we reached here, it means that we don't have an entry in db for the requested //quota class with the name quotaClassUpdates.quota_class_set.get(CinderConstants.CLASS_NAME_KEY). //Then We must create quota class and populate it in DB. QuotaClassOfCinder objQuotaClass = new QuotaClassOfCinder(); objQuotaClass.setQuotaClass(quotaClassName); HashMap<String,String> qMap = new HashMap<String,String>(); //populating the default project level quota attributes. qMap.putAll(getQuotaHelper().convertKeyValPairsStringToMap(getQuotaHelper().createDefaultLimitsInStrFormat(null))); //project level quota attributes, if defined explicitly in the Update request, then we overwrite the defaults we //loaded in the previous step qMap.putAll(quotaClassUpdates.quota_class_set); //remove this class_name attribute as it is not needed in the PUT response. qMap.remove(CinderConstants.CLASS_NAME_KEY); objQuotaClass.setLimits(getQuotaHelper().convertMapToKeyValPairsString(qMap)); objQuotaClass.setId(URI.create(UUID.randomUUID().toString())); _dbClient.createObject(objQuotaClass); resp.quota_class_set.putAll(qMap); return getQuotaClassDetailFormat(header, resp); } /** * Get the quota class details * * * @prereq none * * @param tenant_id the URN of the tenant asking for quotas * @param quota_class_name the quota class name * @brief This function loads the quota class details from the DB. For quota class attributes, which dont * have explicit value defined by the user, we populate the default values in the hashmap before returning. * @return Default Quota details of target_tenant_id */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{quota_class_name}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public Response getQuotaClass( @PathParam("quota_class_name") String quotaClassName, @PathParam("tenant_id") String tenantId, @Context HttpHeaders header) { _log.info("In getQuotaDefaults"); CinderQuotaClassDetails respCinderQuota = new CinderQuotaClassDetails(); HashMap<String, String> defaultQuotaMap = getQuotaHelper().loadFromDbQuotaClass(quotaClassName); if(defaultQuotaMap == null){ if(quotaClassName.equals(CinderConstants.DEFAULT_QUOTA_CLASS)){ defaultQuotaMap = getQuotaHelper().loadDefaultsMapFromDb(); } else{ _log.error("quota class with the given name doesnt exist"); throw APIException.badRequests.parameterIsNotValid(quotaClassName); } } _log.debug("defaultQuotaMap is {}", defaultQuotaMap.toString()); defaultQuotaMap = getQuotaHelper().populateVolumeTypeDefaultsForQuotaClass(defaultQuotaMap , tenantId, null); respCinderQuota.quota_class_set.putAll(defaultQuotaMap); _log.debug("respCinderQuota is {}", respCinderQuota.quota_class_set.toString()); return getQuotaClassDetailFormat(header, respCinderQuota); } /** *Depending on mediatype either xml/json Quota class details response is returned */ private Response getQuotaClassDetailFormat(HttpHeaders header, CinderQuotaClassDetails respCinderClassQuota) { if (CinderApiUtils.getMediaType(header).equals("xml")) { try { return CinderApiUtils.getCinderResponse(CinderApiUtils .convertMapToXML(respCinderClassQuota.quota_class_set, "quota_set",String.class), header, false, CinderConstants.STATUS_OK); }catch (DOMException e) { _log.info("DOM exception occured during converting Map to XML"); return Response.status(500).build(); } catch (IllegalArgumentException e) { _log.info("Illegal argument exception occured during converting Map to XML"); return Response.status(500).build(); } catch (IllegalAccessException e) { _log.info("Illegal access exception occured during converting Map to XML"); return Response.status(500).build(); } } else if (CinderApiUtils.getMediaType(header).equals("json")) { return CinderApiUtils.getCinderResponse(respCinderClassQuota, header, false, CinderConstants.STATUS_OK); } else { return Response.status(ClientResponse.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode()).entity(ClientResponse.Status.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase()) .build(); } } /** *This function will return true, if the user is updating the quota of a vpool w.r.t a project * otherwise it will be set to false, if the user is updating the quota of the project */ private boolean isVpoolQuotaUpdate(Map<String, String> updateMap) { for (String iter : updateMap.keySet()) { if (iter.startsWith("volumes_") || iter.startsWith("snapshots_") || iter.startsWith("gigabytes_")) { return true; } } return false; } /** * Vpool name for the passed quota set * */ private String getVpoolName(Map<String, String> updateMap) { for (String iter : updateMap.keySet()) { if (iter.startsWith("volumes_") || iter.startsWith("snapshots_") || iter.startsWith("gigabytes_")) { String[] splits = iter.split("_", 2); return splits[1]; } } return null; } /** * returns tenant owner */ @Override protected URI getTenantOwner(URI id) { QuotaOfCinder objQuota = (QuotaOfCinder) queryResource(id); Project objProj = _dbClient.queryObject(Project.class, objQuota.getProject()); return objProj.getTenantOrg().getURI(); } /** * Quota is not a zone level resource */ @Override protected boolean isZoneLevelResource() { return false; } /** * returns resource type */ @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.VOLUME; } /** * returns service type */ @Override public String getServiceType() { return EVENT_SERVICE_TYPE; } /** * Get object specific permissions filter * */ @Override protected ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { return new ProjOwnedResRepFilter(user, permissionsHelper, Volume.class); } /** * returns quota object */ @Override protected DataObject queryResource(URI id) { QuotaOfCinder objQuotaOfCinder = _dbClient.queryObject(QuotaOfCinder.class, id); return objQuotaOfCinder; } }