/* * Copyright (c) 2008-2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; import static com.emc.storageos.api.mapper.FileMapper.map; import static com.emc.storageos.api.mapper.TaskMapper.toTask; import java.net.URI; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.functions.MapQuotaDirectory; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.placement.FileStorageScheduler; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.api.service.impl.response.ProjOwnedResRepFilter; import com.emc.storageos.api.service.impl.response.ResRepFilter; import com.emc.storageos.api.service.impl.response.SearchedResRepList; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.ContainmentPrefixConstraint; import com.emc.storageos.db.client.constraint.PrefixConstraint; import com.emc.storageos.db.client.model.FileShare; import com.emc.storageos.db.client.model.OpStatusMap; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.QuotaDirectory; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.db.client.util.NameGenerator; import com.emc.storageos.db.client.util.SizeUtil; import com.emc.storageos.model.BulkIdParam; import com.emc.storageos.model.BulkRestRep; import com.emc.storageos.model.RelatedResourceRep; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.file.QuotaDirectoryBulkRep; import com.emc.storageos.model.file.QuotaDirectoryDeleteParam; import com.emc.storageos.model.file.QuotaDirectoryRestRep; import com.emc.storageos.model.file.QuotaDirectoryUpdateParam; import com.emc.storageos.security.audit.AuditLogManager; 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.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.resources.InternalException; import com.emc.storageos.volumecontroller.FileController; import com.emc.storageos.volumecontroller.FileShareQuotaDirectory; @Path("/file/quotadirectories") @DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL }) public class FileQuotaDirectoryService extends TaskResourceService { private static final Logger _log = LoggerFactory.getLogger(FileQuotaDirectoryService.class); public static final String UNLIMITED_USERS = "unlimited"; private static final String EVENT_SERVICE_TYPE = "file"; @Override public String getServiceType() { return EVENT_SERVICE_TYPE; } @SuppressWarnings("unchecked") @Override public Class<QuotaDirectory> getResourceClass() { return QuotaDirectory.class; } @Override public QuotaDirectoryBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<QuotaDirectory> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); return new QuotaDirectoryBulkRep(BulkList.wrapping(_dbIterator, MapQuotaDirectory.getInstance())); } @Override protected BulkRestRep queryFilteredBulkResourceReps( List<URI> ids) { Iterator<QuotaDirectory> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); BulkList.ResourceFilter<QuotaDirectory> filter = new BulkList.ProjectResourceFilter<QuotaDirectory>( getUserFromContext(), _permissionsHelper); return new QuotaDirectoryBulkRep(BulkList.wrapping(_dbIterator, MapQuotaDirectory.getInstance(), filter)); } private FileStorageScheduler _fileScheduler; private NameGenerator _nameGenerator; public NameGenerator getNameGenerator() { return _nameGenerator; } public void setNameGenerator(NameGenerator nameGenerator) { _nameGenerator = nameGenerator; } public void setFileScheduler(FileStorageScheduler fileScheduler) { _fileScheduler = fileScheduler; } protected FileShare queryFileShareResource(URI id) { ArgValidator.checkUri(id); FileShare fs = _permissionsHelper.getObjectById(id, FileShare.class); ArgValidator.checkEntityNotNull(fs, id, isIdEmbeddedInURL(id)); return fs; } @Override protected QuotaDirectory queryResource(URI id) { ArgValidator.checkUri(id); QuotaDirectory qd = _permissionsHelper.getObjectById(id, QuotaDirectory.class); ArgValidator.checkEntityNotNull(qd, id, isIdEmbeddedInURL(id)); return qd; } @Override protected URI getTenantOwner(URI id) { QuotaDirectory qd = queryResource(id); FileShare fs = queryFileShareResource(qd.getParent().getURI()); return fs.getTenant().getURI(); } /** * Retrieve resource representations based on input ids. * * @param param * POST data containing the id list. * @brief List data of file share resources * @return list of representations. * * @throws com.emc.storageos.db.exceptions.DatabaseException * When an error occurs querying the database. */ @Override @POST @Path("/bulk") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public QuotaDirectoryBulkRep getBulkResources(BulkIdParam param) { return (QuotaDirectoryBulkRep) super.getBulkResources(param); } /** * Filesystem is not a zone level resource */ @Override protected boolean isZoneLevelResource() { return false; } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.QUOTA_DIR; } /** * Get search results by name in zone or project. * * @return SearchedResRepList */ @Override protected SearchedResRepList getNamedSearchResults(String name, URI projectId) { SearchedResRepList resRepList = new SearchedResRepList(getResourceType()); if (projectId == null) { _dbClient.queryByConstraint( PrefixConstraint.Factory.getLabelPrefixConstraint(getResourceClass(), name), resRepList); } else { _dbClient.queryByConstraint( ContainmentPrefixConstraint.Factory.getFileshareUnderProjectConstraint( projectId, name), resRepList); } return resRepList; } /** * Get search results by project alone. * * @return SearchedResRepList */ @Override protected SearchedResRepList getProjectSearchResults(URI projectId) { SearchedResRepList resRepList = new SearchedResRepList(getResourceType()); _dbClient.queryByConstraint( ContainmentConstraint.Factory.getProjectFileshareConstraint(projectId), resRepList); return resRepList; } /** * Get object specific permissions filter * */ @Override public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { return new ProjOwnedResRepFilter(user, permissionsHelper, FileShare.class); } /** * Update Quota Directory for a file share * <p> * NOTE: This is an asynchronous operation. * * @param id * the URN of a ViPR Quota directory * @param param * File system Quota directory update parameters * @brief Update file system Quota directory * @return Task resource representation * @throws com.emc.storageos.svcs.errorhandling.resources.InternalException */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL }) public TaskResourceRep updateQuotaDirectory(@PathParam("id") URI id, QuotaDirectoryUpdateParam param) throws InternalException { _log.info("FileService::Update Quota directory Request recieved {}", id); QuotaDirectory quotaDir = queryResource(id); String task = UUID.randomUUID().toString(); if (param.getSecurityStyle() != null) { ArgValidator.checkFieldValueFromEnum(param.getSecurityStyle(), "security_style", EnumSet.allOf(QuotaDirectory.SecurityStyles.class)); } // Get the FileSystem object FileShare fs = queryFileShareResource(quotaDir.getParent().getURI()); ArgValidator.checkFieldNotNull(fs, "filesystem"); // Update the quota directory object to store in ViPR database quotaDir.setOpStatus(new OpStatusMap()); // Set all other optional parameters too. if (param.getOpLock() != null) { quotaDir.setOpLock(param.getOpLock()); } if (param.getSecurityStyle() != null) { quotaDir.setSecurityStyle(param.getSecurityStyle()); } if (param.getSize() != null) { Long quotaSize = SizeUtil.translateSize(param.getSize());// converts the input string in format "<value>GB" // to Bytes if (quotaSize > 0) { ArgValidator.checkFieldMaximum(quotaSize, fs.getCapacity(), SizeUtil.SIZE_B, "size", true); quotaDir.setSize(quotaSize); } } ArgValidator.checkFieldMaximum(param.getSoftLimit(), 100, "softLimit"); ArgValidator.checkFieldMaximum(param.getNotificationLimit(), 100, "notificationLimit"); if (param.getSoftLimit() != 0L) { ArgValidator.checkFieldMinimum(param.getSoftGrace(), 1L, "softGrace"); } quotaDir.setSoftLimit(param.getSoftLimit()>0 ? param.getSoftLimit() : quotaDir.getSoftLimit()>0 ? quotaDir.getSoftLimit() : fs.getSoftLimit().intValue()>0 ? fs.getSoftLimit().intValue() : 0); quotaDir.setSoftGrace(param.getSoftGrace()>0 ? param.getSoftGrace() : quotaDir.getSoftGrace()>0? quotaDir.getSoftGrace() : fs.getSoftGracePeriod()>0 ? fs.getSoftGracePeriod() : 0); quotaDir.setNotificationLimit(param.getNotificationLimit()>0 ? param.getNotificationLimit() : quotaDir.getNotificationLimit()>0 ? quotaDir.getNotificationLimit() : fs.getNotificationLimit().intValue()>0 ? fs.getNotificationLimit().intValue() : 0); Operation op = new Operation(); op.setResourceType(ResourceOperationTypeEnum.UPDATE_FILE_SYSTEM_QUOTA_DIR); quotaDir.getOpStatus().createTaskStatus(task, op); fs.setOpStatus(new OpStatusMap()); fs.getOpStatus().createTaskStatus(task, op); _dbClient.persistObject(fs); _dbClient.persistObject(quotaDir); // Create an object of type "FileShareQtree" to be passed into the south-bound layers. FileShareQuotaDirectory qt = new FileShareQuotaDirectory(quotaDir); // Now get ready to make calls into the controller StorageSystem device = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice()); FileController controller = getController(FileController.class, device.getSystemType()); try { controller.updateQuotaDirectory(device.getId(), qt, fs.getId(), task); } catch (InternalException e) { _log.error("Error during update of Quota Directory {}", e); // treating all controller exceptions as internal error for now. controller // should discriminate between validation problems vs. internal errors throw e; } auditOp(OperationTypeEnum.UPDATE_FILE_SYSTEM_QUOTA_DIR, true, AuditLogManager.AUDITOP_BEGIN, quotaDir.getLabel(), quotaDir.getId().toString(), fs.getId().toString()); fs = _dbClient.queryObject(FileShare.class, fs.getId()); _log.debug("FileService::Quota directory Before sending response, FS ID : {}, Taks : {} ; Status {}", fs.getOpStatus().get(task), fs.getOpStatus().get(task).getStatus()); return toTask(quotaDir, task, op); } /** * Deactivate Quota directory of file system, this will move the * Quota directory to a "marked-for-delete" state * <p> * NOTE: This is an asynchronous operation. * * @param id * the URN of the QuotaDirectory * @param param * QuotaDirectory delete param for optional force delete * @brief Delete file system Quota Dir * @return Task resource representation * @throws com.emc.storageos.svcs.errorhandling.resources.InternalException */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deactivate") @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL }) public TaskResourceRep deactivateQuotaDirectory(@PathParam("id") URI id, QuotaDirectoryDeleteParam param) throws InternalException { _log.info("FileService::deactivateQtree Request recieved {}", id); String task = UUID.randomUUID().toString(); ArgValidator.checkFieldUriType(id, QuotaDirectory.class, "id"); QuotaDirectory quotaDirectory = queryResource(id); FileShare fs = queryFileShareResource(quotaDirectory.getParent().getURI()); ArgValidator.checkFieldNotNull(fs, "filesystem"); // <TODO> Implement Force delete option when shares and exports for Quota Directory are supported Operation op = new Operation(); op.setResourceType(ResourceOperationTypeEnum.DELETE_FILE_SYSTEM_QUOTA_DIR); quotaDirectory.getOpStatus().createTaskStatus(task, op); fs.setOpStatus(new OpStatusMap()); fs.getOpStatus().createTaskStatus(task, op); _dbClient.persistObject(fs); _dbClient.persistObject(quotaDirectory); // Now get ready to make calls into the controller StorageSystem device = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice()); FileController controller = getController(FileController.class, device.getSystemType()); try { controller.deleteQuotaDirectory(device.getId(), quotaDirectory.getId(), fs.getId(), task); // If delete operation is successful, then remove obj from ViPR db by setting inactive=true quotaDirectory.setInactive(true); _dbClient.persistObject(quotaDirectory); } catch (InternalException e) { // treating all controller exceptions as internal error for now. controller // should discriminate between validation problems vs. internal errors throw e; } auditOp(OperationTypeEnum.DELETE_FILE_SYSTEM_QUOTA_DIR, true, AuditLogManager.AUDITOP_BEGIN, quotaDirectory.getLabel(), quotaDirectory.getId().toString(), fs.getId().toString()); fs = _dbClient.queryObject(FileShare.class, fs.getId()); _log.debug("FileService::Quota directory Before sending response, FS ID : {}, Taks : {} ; Status {}", fs.getOpStatus().get(task), fs.getOpStatus().get(task).getStatus()); return toTask(quotaDirectory, task, op); } /** * Get info for file system quota directory * * @param id * the URN of a ViPR Quota directory * @brief Show file system quota directory * @return File system quota directory details */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public QuotaDirectoryRestRep getQuotaDirectory(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, QuotaDirectory.class, "id"); QuotaDirectory quotaDir = queryResource(id); return map(quotaDir); } private List<QuotaDirectory> queryDBQuotaDirectories(FileShare fs) { _log.info("Querying all quota directories Using FsId {}", fs.getId()); try { ContainmentConstraint containmentConstraint = ContainmentConstraint.Factory.getQuotaDirectoryConstraint(fs.getId()); List<QuotaDirectory> fsQuotaDirs = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, QuotaDirectory.class, containmentConstraint); return fsQuotaDirs; } catch (Exception e) { _log.error("Error while querying {}", e); } return null; } }