/* 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.QueryParam;
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.cinder.model.CinderQuotaDetails;
import com.emc.storageos.cinder.model.CinderUsage;
import com.emc.storageos.cinder.model.UsageAndLimits;
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-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 QuotaService extends TaskResourceService {
private static final Logger _log = LoggerFactory.getLogger(QuotaService.class);
private static final String EVENT_SERVICE_TYPE = "block";
private CinderHelpers helper = null;
public static final long DEFAULT_VOLUME_TYPE_SNAPSHOTS_QUOTA = -1;
public static final long DEFAULT_VOLUME_TYPE_VOLUMES_QUOTA = -1;
public static final long DEFAULT_VOLUME_TYPE_TOTALGB_QUOTA = -1;
private CinderHelpers getCinderHelper() {
return CinderHelpers.getInstance(_dbClient, _permissionsHelper);
}
private QuotaHelper getQuotaHelper() {
return QuotaHelper.getInstance(_dbClient, _permissionsHelper);
}
/**
* Get the summary list of all Quotas for the given tenant
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant asking for quotas
* @param target_tenant_id
* @brief Get the summary list of all Quotas
* @return Quota details of target_tenant_id
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{target_tenant_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getQuotaDetails(
@PathParam("target_tenant_id") String openstackTargetTenantId,
@QueryParam("usage") String usage, @Context HttpHeaders header) {
Project project = getCinderHelper().getProject(openstackTargetTenantId.toString(),
getUserFromContext());
if (project == null) {
throw APIException.badRequests.projectWithTagNonexistent(openstackTargetTenantId);
}
HashMap<String, String> defaultQuotaMap = getQuotaHelper().getCompleteDefaultConfiguration(openstackTargetTenantId);
List<URI> quotas = _dbClient.queryByType(QuotaOfCinder.class, true);
Map<String, String> vpoolsMap = new HashMap<String, String>();
boolean bDefProjQuotasExist = false;
CinderQuotaDetails respCinderQuota = new CinderQuotaDetails();
for (URI quota : quotas) {
QuotaOfCinder quotaObj = _dbClient.queryObject(QuotaOfCinder.class, quota);
if ((quotaObj.getProject() != null) &&
(quotaObj.getProject().toString().equalsIgnoreCase(project.getId().toString()))) {
if (quotaObj.getVpool() != null) {
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, quotaObj.getVpool());
respCinderQuota.quota_set.put("gigabytes" + "_" + pool.getLabel(), String.valueOf(quotaObj.getTotalQuota()));
respCinderQuota.quota_set.put("snapshots" + "_" + pool.getLabel(), String.valueOf(quotaObj.getSnapshotsLimit()));
respCinderQuota.quota_set.put("volumes" + "_" + pool.getLabel(), String.valueOf(quotaObj.getVolumesLimit()));
vpoolsMap.put(pool.getLabel(), pool.getLabel());
}
else {
respCinderQuota.quota_set.put("gigabytes", String.valueOf(quotaObj.getTotalQuota()));
respCinderQuota.quota_set.put("snapshots", String.valueOf(quotaObj.getSnapshotsLimit()));
respCinderQuota.quota_set.put("volumes", String.valueOf(quotaObj.getVolumesLimit().intValue()));
bDefProjQuotasExist = true;
}
}
}
if (!bDefProjQuotasExist) {
QuotaOfCinder objRet = getQuotaHelper().createProjectDefaultQuota(project, defaultQuotaMap);
respCinderQuota.quota_set.put("gigabytes", String.valueOf(objRet.getTotalQuota()));
respCinderQuota.quota_set.put("snapshots", String.valueOf(objRet.getSnapshotsLimit()));
respCinderQuota.quota_set.put("volumes", String.valueOf(objRet.getVolumesLimit()));
}
StorageOSUser user = getUserFromContext();
URI tenantId = URI.create(user.getTenantId());
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)) {
if (vpoolsMap.containsKey(pool.getLabel())) {
continue;
}
else {
QuotaOfCinder objRet = getQuotaHelper().createVpoolDefaultQuota(project, pool, defaultQuotaMap);
respCinderQuota.quota_set.put("gigabytes" + "_" + pool.getLabel(),
String.valueOf(objRet.getTotalQuota()));
respCinderQuota.quota_set.put("snapshots" + "_" + pool.getLabel(),
String.valueOf(objRet.getSnapshotsLimit()));
respCinderQuota.quota_set.put("volumes" + "_" + pool.getLabel(),
String.valueOf(objRet.getVolumesLimit()));
}
}
}
}
if( (usage!=null) && (usage.equals("True")) ){
CinderUsage objUsage = getQuotaHelper().getUsageStatistics(URI.create(openstackTargetTenantId) ,
(HashMap<String,String>)respCinderQuota.getQuota_set(), project );
return getQuotaUsageFormat(header, objUsage);
}
return getQuotaDetailFormat(header, respCinderQuota);
}
/**
* Get the summary list of all Default Quotas for the given tenant
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant asking for quotas
* @param target_tenant_id
* @brief
* @return Default Quota details of target_tenant_id
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{target_tenant_id}/defaults")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getQuotaDefaults(
@PathParam("target_tenant_id") String openstackTargetTenantId, @Context HttpHeaders header) {
_log.info("In getQuotaDefaults");
CinderQuotaDetails respCinderQuota = new CinderQuotaDetails();
HashMap<String, String> defaultQuotaMap = getQuotaHelper().getCompleteDefaultConfiguration(openstackTargetTenantId);
_log.debug("defaultQuotaMap is {}", defaultQuotaMap.toString());
//defaultQuotaMap = getQuotaHelper().populateVolumeTypeQuotasWhenNotDefined(defaultQuotaMap , openstack_target_tenant_id, null);
respCinderQuota.quota_set.putAll(defaultQuotaMap);
_log.debug("respCinderQuota is {}", respCinderQuota.quota_set.toString());
return getQuotaDetailFormat(header, respCinderQuota);
}
/**
* Update a quota
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
* @param target_tenant_id the URN of the target tenant
* for which quota is being modified
*
* @brief Update Quota
* @return Quota details of target_tenant_id
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{target_tenant_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response updateQuota(@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("target_tenant_id") String openstackTargetTenantId,
CinderQuotaDetails quotaUpdates, @Context HttpHeaders header) {
_log.info("Updating Quota");
Project project = getCinderHelper().getProject(openstackTargetTenantId.toString(),
getUserFromContext());
HashMap<String, String> defaultQuotaMap = getQuotaHelper().getCompleteDefaultConfiguration(openstackTargetTenantId);
if (project == null) {
throw APIException.badRequests.projectWithTagNonexistent(openstackTargetTenantId);
}
long maxQuota = 0L;
if (project.getQuotaEnabled())
{
maxQuota = (long) (project.getQuota().intValue());
}
else
{
maxQuota = Long.valueOf(defaultQuotaMap.get(CinderConstants.ResourceQuotaDefaults.GIGABYTES.getResource()));
}
// bVpoolQuotaUpdate will be set to true if the user is updating the quota of a vpool w.r.t a project
// bVpoolQuotaUpdate will be set to false if the user is updating the quota of the project
boolean bVpoolQuotaUpdate = isVpoolQuotaUpdate(quotaUpdates.quota_set);
String vpoolName = null;
VirtualPool objVpool = null;
if (bVpoolQuotaUpdate) {
vpoolName = getVpoolName(quotaUpdates.quota_set);
_log.info("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);
}
if(!_permissionsHelper.tenantHasUsageACL(URI.create(openstackTargetTenantId), objVpool)){
_log.error("tenant {} does not have access to vpool with the given name {}",openstackTargetTenantId, vpoolName);
throw APIException.badRequests.parameterIsNotValid(vpoolName);
}
_log.info("objVpool.getLabel() is {}", objVpool.getLabel());
}
List<URI> quotas = _dbClient.queryByType(QuotaOfCinder.class, true);
boolean noEntriesInDB = true;
for (URI quota : quotas) {
QuotaOfCinder quotaObj = _dbClient.queryObject(QuotaOfCinder.class, quota);
if ((quotaObj.getProject() != null) &&
(quotaObj.getProject().toString().equalsIgnoreCase(project.getId().toString()))) {
_log.info("QuotaObj being updated is {}", quotaObj.toString());
URI vpoolUri = quotaObj.getVpool();
if ((!bVpoolQuotaUpdate) && (vpoolUri != null)) {
// The user requested update of project quota.
// But the current db entry quota for vpool w.r.t project.
// Hence just skip the db entry as this is not our concern.
continue;
}
if ((bVpoolQuotaUpdate) && (vpoolUri != null)) {
// The user requested quota update for a vpool w.r.t a project.
// The current db entry that we looking into has vpool entry.
// Hence we should further check if the vpool value is same as the vpool for which the user wants to set quota
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, vpoolUri);
if ((pool != null) && (pool.getLabel().equals(vpoolName)) && (vpoolName != null) && (vpoolName.length() > 0)) {
if (quotaUpdates.quota_set.containsKey("gigabytes_" + vpoolName))
quotaObj.setTotalQuota(new Long(quotaUpdates.quota_set.get("gigabytes_" + vpoolName)));
if (quotaUpdates.quota_set.containsKey("volumes_" + vpoolName))
quotaObj.setVolumesLimit(new Long(quotaUpdates.quota_set.get("volumes_" + vpoolName)));
if (quotaUpdates.quota_set.containsKey("snapshots_" + vpoolName))
quotaObj.setSnapshotsLimit(new Long(quotaUpdates.quota_set.get("snapshots_" + vpoolName)));
noEntriesInDB = false;
_dbClient.updateObject(quotaObj);
return getQuotaDetailFormat(header, quotaUpdates);
}
}
else if (!bVpoolQuotaUpdate) {
// The user requested update of project quota.
// The current db entry is a project quota entity.(because to reach here vpoolUri should be Null.
if (quotaUpdates.quota_set.containsKey("gigabytes"))
quotaObj.setTotalQuota(new Long(quotaUpdates.quota_set.get("gigabytes")));
if (quotaUpdates.quota_set.containsKey("volumes"))
quotaObj.setVolumesLimit(new Long(quotaUpdates.quota_set.get("volumes")));
if (quotaUpdates.quota_set.containsKey("snapshots"))
quotaObj.setSnapshotsLimit(new Long(quotaUpdates.quota_set.get("snapshots")));
noEntriesInDB = false;
_dbClient.updateObject(quotaObj);
return getQuotaDetailFormat(header, quotaUpdates);
}
}
}
if (noEntriesInDB) {
_log.info("No entries in the QuotaOfCinder column family");
QuotaOfCinder objQuotaOfCinder = new QuotaOfCinder();
objQuotaOfCinder.setProject(project.getId());
if (bVpoolQuotaUpdate) {
objQuotaOfCinder.setVpool(objVpool.getId());
_log.info("Updating Quota of Vpool");
if (quotaUpdates.quota_set.containsKey("gigabytes_" + vpoolName))
objQuotaOfCinder.setTotalQuota(new Long(quotaUpdates.quota_set.get("gigabytes_" + vpoolName)));
else
objQuotaOfCinder.setTotalQuota(Long.valueOf(defaultQuotaMap.get("gigabytes_" + vpoolName)));
if (quotaUpdates.quota_set.containsKey("volumes_" + vpoolName))
objQuotaOfCinder.setVolumesLimit(new Long(quotaUpdates.quota_set.get("volumes_" + vpoolName)));
else
objQuotaOfCinder.setVolumesLimit(Long.valueOf(defaultQuotaMap.get("volumes_" + vpoolName)));
if (quotaUpdates.quota_set.containsKey("snapshots_" + vpoolName))
objQuotaOfCinder.setSnapshotsLimit(new Long(quotaUpdates.quota_set.get("snapshots_" + vpoolName)));
else
objQuotaOfCinder.setSnapshotsLimit(Long.valueOf(defaultQuotaMap.get("snapshots_" + vpoolName)));
}
else {
if (quotaUpdates.quota_set.containsKey("gigabytes"))
objQuotaOfCinder.setTotalQuota(new Long(quotaUpdates.quota_set.get("gigabytes")));
else
objQuotaOfCinder.setTotalQuota(maxQuota);
if (quotaUpdates.quota_set.containsKey("volumes"))
objQuotaOfCinder.setVolumesLimit(new Long(quotaUpdates.quota_set.get("volumes")));
else
objQuotaOfCinder.setVolumesLimit(Long.valueOf(defaultQuotaMap.get("volumes")));
if (quotaUpdates.quota_set.containsKey("snapshots"))
objQuotaOfCinder.setSnapshotsLimit(new Long(quotaUpdates.quota_set.get("snapshots")));
else
objQuotaOfCinder.setSnapshotsLimit(Long.valueOf(defaultQuotaMap.get("snapshots")));
}
objQuotaOfCinder.setId(URI.create(UUID.randomUUID().toString()));
_dbClient.createObject(objQuotaOfCinder);
return getQuotaDetailFormat(header, quotaUpdates);
}
return getQuotaDetailFormat(header, quotaUpdates);
}
// internal function
/**
*Depending on mediatype either xml/json Quota details response is returned
*/
private Response getQuotaDetailFormat(HttpHeaders header, CinderQuotaDetails respCinderQuota){
if (CinderApiUtils.getMediaType(header).equals("xml")) {
try {
return CinderApiUtils.getCinderResponse(CinderApiUtils
.convertMapToXML(respCinderQuota.quota_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(respCinderQuota, header, false,CinderConstants.STATUS_OK);
} else {
return Response.status(ClientResponse.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode()).entity(ClientResponse.Status.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase())
.build();
}
}
// internal function
/**
*Depending on mediatype either xml/json Quota usage response is returned
*/
private Response getQuotaUsageFormat(HttpHeaders header, CinderUsage respCinderUsage){
if (CinderApiUtils.getMediaType(header).equals("xml")) {
try {
return CinderApiUtils.getCinderResponse(CinderApiUtils
.convertMapToXML(respCinderUsage.getQuota_set(), "quota_set", UsageAndLimits.class),
header, false,CinderConstants.STATUS_OK);
} catch (IllegalAccessException e) {
_log.info("Illegal access exception encountered while converting Usage details to XML format");
return Response.status(404).build();
}
} else if (CinderApiUtils.getMediaType(header).equals("json")) {
return CinderApiUtils.getCinderResponse(respCinderUsage, 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;
}
}