/* * Copyright (c) 2008-2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; import static com.emc.storageos.api.mapper.DbObjectMapper.map; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; 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.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.storageos.api.mapper.DbObjectMapper; import com.emc.storageos.api.mapper.functions.MapProject; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.authorization.PermissionsHelper.ACLInputFilter; import com.emc.storageos.api.service.impl.resource.utils.CapacityUtils; import com.emc.storageos.api.service.impl.resource.utils.ProjectUtility; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.api.service.impl.response.ResRepFilter; import com.emc.storageos.customconfigcontroller.CustomConfigConstants; import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.QueryResultList; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.DiscoveredDataObject.DiscoveryStatus; import com.emc.storageos.db.client.model.FileShare; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.QuotaOfCinder; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.TenantOrg; import com.emc.storageos.db.client.model.VirtualNAS; import com.emc.storageos.db.client.model.VirtualNAS.VirtualNasState; import com.emc.storageos.db.common.VdcUtil; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.model.BulkIdParam; import com.emc.storageos.model.RelatedResourceRep; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.TypedRelatedResourceRep; import com.emc.storageos.model.auth.ACLAssignmentChanges; import com.emc.storageos.model.auth.ACLAssignments; import com.emc.storageos.model.auth.ACLEntry; import com.emc.storageos.model.auth.PrincipalsToValidate; import com.emc.storageos.model.project.ProjectBulkRep; import com.emc.storageos.model.project.ProjectRestRep; import com.emc.storageos.model.project.ProjectUpdateParam; import com.emc.storageos.model.project.ResourceList; import com.emc.storageos.model.project.VirtualNasParam; import com.emc.storageos.model.quota.QuotaInfo; import com.emc.storageos.model.quota.QuotaUpdateParam; 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.PermissionsKey; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.security.validator.StorageOSPrincipal; import com.emc.storageos.security.validator.Validator; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableBourneEvent; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager; import com.emc.storageos.volumecontroller.impl.monitoring.cim.enums.RecordType; /** * Project resource implementation */ @Path("/projects") @DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL }) public class ProjectService extends TaggedResource { private static final Logger _log = LoggerFactory.getLogger(ProjectService.class); private static String EXPECTED_GEO_VERSION = "2.4"; private static String FEATURE_NAME = "VNAS support in File Controller"; // Constants for Events private static final String EVENT_SERVICE_TYPE = "project"; private static final String EVENT_SERVICE_SOURCE = "ProjectService"; private CustomConfigHandler customConfigHandler; public void setCustomConfigHandler(CustomConfigHandler customConfigHandler) { this.customConfigHandler = customConfigHandler; } @Override public String getServiceType() { return EVENT_SERVICE_TYPE; } @Autowired private RecordableEventManager _evtMgr; /** * Get info for project including owner, parent project, and child projects * * @param id the URN of a ViPR Project * @prereq none * @brief Show project * @return Project details */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public ProjectRestRep getProject(@PathParam("id") URI id) { Project project = queryResource(id); return map(project); } @Override protected Project queryResource(URI id) { return getProjectById(id, false); } @Override protected URI getTenantOwner(URI id) { Project project = queryResource(id); return project.getTenantOrg().getURI(); } /** * Update info for project including project name and owner * * @param projectUpdate Project update parameters * @param id the URN of a ViPR Project * @prereq none * @brief Update project * @return No data returned in response body */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN }) public Response updateProject(@PathParam("id") URI id, ProjectUpdateParam projectUpdate) { Project project = getProjectById(id, true); if (null != projectUpdate.getName() && !projectUpdate.getName().isEmpty() && !project.getLabel().equalsIgnoreCase(projectUpdate.getName())) { checkForDuplicateName(projectUpdate.getName(), Project.class, project.getTenantOrg() .getURI(), "tenantOrg", _dbClient); project.setLabel(projectUpdate.getName()); NamedURI tenant = project.getTenantOrg(); if (tenant != null) { tenant.setName(projectUpdate.getName()); project.setTenantOrg(tenant); } } if (null != projectUpdate.getOwner() && !projectUpdate.getOwner().isEmpty() && !projectUpdate.getOwner().equalsIgnoreCase(project.getOwner())) { StringBuilder error = new StringBuilder(); if (!Validator.isValidPrincipal(new StorageOSPrincipal(projectUpdate.getOwner(), StorageOSPrincipal.Type.User), project.getTenantOrg().getURI(), error)) { throw APIException.forbidden .specifiedOwnerIsNotValidForProjectTenant(error.toString()); } // in GEO scenario, root can't be assigned as project owner boolean isRootInGeo = (projectUpdate.getOwner().equalsIgnoreCase("root") && !VdcUtil.isLocalVdcSingleSite()); if (isRootInGeo) { throw APIException.forbidden.specifiedOwnerIsNotValidForProjectTenant( "in GEO scenario, root can't be assigned as project owner" ); } // set owner acl project.removeAcl(new PermissionsKey(PermissionsKey.Type.SID, project.getOwner(), project.getTenantOrg().getURI()).toString(), ACL.OWN.toString()); project.setOwner(projectUpdate.getOwner()); // set owner acl project.addAcl(new PermissionsKey(PermissionsKey.Type.SID, project.getOwner(), project.getTenantOrg().getURI()).toString(), ACL.OWN.toString()); } _dbClient.updateAndReindexObject(project); recordOperation(OperationTypeEnum.UPDATE_PROJECT, true, project); return Response.ok().build(); } /** * List resources in project * * @param id the URN of a ViPR Project * @prereq none * @brief List project resources * @return List of resources */ @SuppressWarnings("unchecked") @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/resources") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public ResourceList getResourceList(@PathParam("id") URI id) { Project project = getProjectById(id, false); QueryResultList<TypedRelatedResourceRep> file = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.FILE); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.FILE); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> volume = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.VOLUME); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.VOLUME); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> bucket = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BUCKET); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BUCKET); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> exportgroup = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.EXPORT_GROUP); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.EXPORT_GROUP); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> blockSnapshot = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BLOCK_SNAPSHOT); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BLOCK_SNAPSHOT); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> fileSnapshot = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.FILE_SNAPSHOT); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.FILE_SNAPSHOT); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> protectionSet = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.PROTECTION_SET); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.PROTECTION_SET); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; QueryResultList<TypedRelatedResourceRep> blockConsistencySet = new QueryResultList<TypedRelatedResourceRep>() { @Override public TypedRelatedResourceRep createQueryHit(URI uri) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BLOCK_CONSISTENCY_GROUP); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, String name, UUID timestamp) { TypedRelatedResourceRep resource = new TypedRelatedResourceRep(); resource.setId(uri); resource.setType(ResourceTypeEnum.BLOCK_CONSISTENCY_GROUP); resource.setName(name); return resource; } @Override public TypedRelatedResourceRep createQueryHit(URI uri, Object entry) { return createQueryHit(uri); } }; _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectVolumeConstraint(project.getId()), volume); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectFileshareConstraint(project.getId()), file); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectBucketConstraint(project.getId()), bucket); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectExportGroupConstraint(project.getId()), exportgroup); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectBlockSnapshotConstraint(project.getId()), blockSnapshot); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectFileSnapshotConstraint(project.getId()), fileSnapshot); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectProtectionSetConstraint(project.getId()), protectionSet); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getProjectBlockConsistencyGroupConstraint(project.getId()), blockConsistencySet); ResourceList list = new ResourceList(); list.setResources(new ChainedList<TypedRelatedResourceRep>(file.iterator(), volume.iterator(), bucket.iterator(), exportgroup.iterator(), blockSnapshot.iterator(), fileSnapshot.iterator(), file.iterator(), volume.iterator(), exportgroup.iterator(), protectionSet.iterator(), blockConsistencySet.iterator())); return list; } /** * validates whether the project has active vnas servers * assigned to it or not. * * @param project - a ViPR Project */ private boolean isProjectAssignedWithVNasServers(Project project) { if (project.getAssignedVNasServers() != null && !project.getAssignedVNasServers().isEmpty()) { for (String vnasId : project.getAssignedVNasServers()) { VirtualNAS vnas = _permissionsHelper.getObjectById(URI.create(vnasId), VirtualNAS.class); if (vnas != null && !vnas.getInactive()) { _log.debug("project {} has been assigned with vnas server {}", project.getLabel(), vnas.getNasName()); return true; } } } _log.info("No active vnas servers assigned to project {}", project.getLabel()); return false; } /** * Deactivates the project. * When a project is deleted it will move to a "marked for deletion" state. Once in this state, * new resources or child projects may no longer be created in the project. * The project will be permanently deleted once all its references of type * ExportGroup, FileSystem, KeyPool, KeyPoolInfo, Volume are deleted. * * @prereq none * @param id the URN of a ViPR Project * @brief Deactivate project * @return No data returned in response body */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deactivate") @CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN }) public Response deactivateProject(@PathParam("id") URI id) { Project project = getProjectById(id, true); //check if any filepolicies are assigned to project if((project.getFilePolicies() != null) && !(project.getFilePolicies().isEmpty())){ throw APIException.badRequests.cannotDeleteProjectAssignedFilePolicy(project.getLabel()); } //for block service cinder if there is QuotaOfCinder entries //we need to remove before the project removal List<URI> quotas = _dbClient.queryByType(QuotaOfCinder.class, true); for (URI quota : quotas) { QuotaOfCinder quotaObj = _dbClient.queryObject(QuotaOfCinder.class, quota); if ((quotaObj.getProject() != null) && (quotaObj.getProject().toString().equalsIgnoreCase(project.getId().toString()))) { _log.debug("Deleting related Quota object {}.",quotaObj.getId()); _dbClient.removeObject(quotaObj); } } ArgValidator.checkReference(Project.class, id, checkForDelete(project)); // Check the project has been assigned with vNAS servers!!! if (isProjectAssignedWithVNasServers(project)) { _log.error("Delete porject failed due to, One or more vnas servers are assigned to project."); throw APIException.badRequests.failedToDeleteVNasAssignedProject(); } _dbClient.markForDeletion(project); recordOperation(OperationTypeEnum.DELETE_PROJECT, true, project); return Response.ok().build(); } /** * Filter class for validating input args for project ACLs */ public class ProjectACLFilter extends ACLInputFilter { private final TenantOrg _tenant; private List<String> _groups; private List<String> _users; public ProjectACLFilter(TenantOrg tenant) { _tenant = tenant; } @Override public PermissionsKey getPermissionKeyForEntry(ACLEntry entry) throws SecurityException { PermissionsKey key; StorageOSPrincipal principal = new StorageOSPrincipal(); if (entry.getGroup() != null) { String group = entry.getGroup(); key = new PermissionsKey(PermissionsKey.Type.GROUP, group, _tenant.getId()); principal.setName(group); principal.setType(StorageOSPrincipal.Type.Group); } else if (entry.getSubjectId() != null) { key = new PermissionsKey(PermissionsKey.Type.SID, entry.getSubjectId(), _tenant.getId()); principal.setName(entry.getSubjectId()); principal.setType(StorageOSPrincipal.Type.User); } else { throw APIException.badRequests.invalidEntryForProjectACL(); } return key; } @Override public boolean isValidACL(String ace) { return (_permissionsHelper.isProjectACL(ace) && !ace.equalsIgnoreCase(ACL.OWN.toString())); } @Override public void validate() { PrincipalsToValidate principalsToValidate = new PrincipalsToValidate(); principalsToValidate.setGroups(_groups); principalsToValidate.setUsers(_users); principalsToValidate.setTenantId(_tenant.getId().toString()); StringBuilder error = new StringBuilder(); if (!Validator.validatePrincipals(principalsToValidate, error)) { throw APIException.badRequests.invalidRoleAssignments(error.toString()); } } @Override protected void addPrincipalToList(PermissionsKey key) { switch (key.getType()) { case GROUP: _groups.add(key.getValue()); break; case SID: _users.add(key.getValue()); break; case TENANT: default: break; } } @Override protected void initLists() { _groups = new ArrayList<String>(); _users = new ArrayList<String>(); } } /** * Get project ACL * * @param id the URN of a ViPR Project * @prereq none * @brief Show project ACL * @return ACL Assignment details */ @GET @Path("/{id}/acl") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN }, acls = { ACL.OWN }) public ACLAssignments getRoleAssignments(@PathParam("id") URI id) { return getRoleAssignmentsResponse(id); } /** * Add or remove individual ACL entry(s) * * @param changes ACL assignment changes. Request body must include at least one add or remove operation * @param id the URN of a ViPR Project * @prereq none * @brief Add or remove project ACL entries * @return No data returned in response body */ @PUT @Path("/{id}/acl") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN }, acls = { ACL.OWN }, blockProxies = true) public ACLAssignments updateRoleAssignments(@PathParam("id") URI id, ACLAssignmentChanges changes) { Project project = getProjectById(id, true); TenantOrg tenant = _permissionsHelper.getObjectById(project.getTenantOrg().getURI(), TenantOrg.class); _permissionsHelper.updateACLs(project, changes, new ProjectACLFilter(tenant)); _dbClient.updateAndReindexObject(project); recordProjectEvent(project, OperationTypeEnum.MODIFY_PROJECT_ACL, true); auditOp(OperationTypeEnum.MODIFY_PROJECT_ACL, true, null, project.getId() .toString(), project.getLabel(), changes); return getRoleAssignmentsResponse(id); } private ACLAssignments getRoleAssignmentsResponse(URI id) { Project project = getProjectById(id, false); ACLAssignments response = new ACLAssignments(); response.setAssignments(_permissionsHelper.convertToACLEntries(project.getAcls())); return response; } /** * Retrieve resource representations based on input ids. * * @prereq none * @param param POST data containing the id list. * @brief List data of project resources * @return list of representations. * @throws DatabaseException When an error occurs querying the database. */ @POST @Path("/bulk") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public ProjectBulkRep getBulkResources(BulkIdParam param) { return (ProjectBulkRep) super.getBulkResources(param); } /** * Show quota and available capacity before quota is exhausted * * @prereq none * @param id the URN of a ViPR project. * @brief Show quota and available capacity * @return QuotaInfo Quota metrics. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }) @Path("/{id}/quota") public QuotaInfo getQuota(@PathParam("id") URI id) throws DatabaseException { Project project = getProjectById(id, true); return getQuota(project); } /** * Updates quota and available capacity before quota is exhausted * * @param id the URN of a ViPR Project. * @param param new values for the quota * @prereq none * @brief Updates quota and available capacity * @return QuotaInfo Quota metrics. */ @PUT @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("/{id}/quota") public QuotaInfo updateQuota(@PathParam("id") URI id, QuotaUpdateParam param) throws DatabaseException { Project project = getProjectById(id, true); project.setQuotaEnabled(param.getEnable()); if (param.getEnable()) { long quota_gb = (param.getQuotaInGb() != null) ? param.getQuotaInGb() : project.getQuota(); ArgValidator.checkFieldMinimum(quota_gb, 0, "quota_gb", "GB"); // Verify that the quota of this project does not exit quota for its tenant TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, project.getTenantOrg().getURI()); if (tenant.getQuotaEnabled()) { long totalProjects = CapacityUtils.totalProjectQuota(_dbClient, tenant.getId()) - project.getQuota() + quota_gb; if (totalProjects > tenant.getQuota()) { throw APIException.badRequests.invalidParameterProjectQuotaInvalidatesTenantQuota(tenant.getQuota()); } } project.setQuota(quota_gb); } _dbClient.persistObject(project); return getQuota(project); } private QuotaInfo getQuota(Project project) { QuotaInfo quotaInfo = new QuotaInfo(); double capacity = CapacityUtils.getProjectCapacity(_dbClient, project.getId()); quotaInfo.setQuotaInGb(project.getQuota()); quotaInfo.setEnabled(project.getQuotaEnabled()); quotaInfo.setCurrentCapacityInGb((long) Math.ceil(capacity / CapacityUtils.GB)); quotaInfo.setLimitedResource(DbObjectMapper.toNamedRelatedResource(project)); return quotaInfo; } /** * Get project object from id * * @param id the URN of a ViPR Project * @return */ private Project getProjectById(URI id, boolean checkInactive) { if (id == null) { return null; } Project ret = _permissionsHelper.getObjectById(id, Project.class); ArgValidator.checkEntity(ret, id, isIdEmbeddedInURL(id), checkInactive); return ret; } /** * Create an event based on the project * * @param project for which the event is about * @param opType Type of event such as ProjectCreated, ProjectDeleted * @param opStatus */ public void recordProjectEvent(Project project, OperationTypeEnum opType, Boolean opStatus) { String type = opType.getEvType(opStatus); String description = opType.getDescription(); _log.info("opType: {} detail: {}", opType.toString(), type + ':' + description); RecordableBourneEvent event = new RecordableBourneEvent( type, project.getTenantOrg().getURI(), URI.create(getUserFromContext().getName()), project.getId(), null, EVENT_SERVICE_TYPE, project.getId(), description, System.currentTimeMillis(), "", // extensions "", RecordType.Event.name(), EVENT_SERVICE_SOURCE, "", "" ); try { _evtMgr.recordEvents(event); } catch (Exception ex) { _log.error("Failed to record event. Event description: {}. Error: {}.", description, ex); } } public void recordOperation(OperationTypeEnum opType, boolean opStatus, Project project) { recordProjectEvent(project, opType, opStatus); switch (opType) { case UPDATE_PROJECT: auditOp(opType, opStatus, null, project.getId().toString(), project.getLabel(), project.getOwner()); break; case DELETE_PROJECT: auditOp(opType, opStatus, null, project.getId().toString(), project.getLabel()); break; case REASSIGN_PROJECT_ACL: auditOp(opType, opStatus, null, project.getId().toString(), project.getLabel()); break; default: _log.error("unrecognized project opType"); } } @SuppressWarnings("unchecked") @Override public Class<Project> getResourceClass() { return Project.class; } @Override public ProjectBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<Project> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); return new ProjectBulkRep(BulkList.wrapping(_dbIterator, MapProject.getInstance())); } @Override protected ProjectBulkRep queryFilteredBulkResourceReps( List<URI> ids) { Iterator<Project> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); BulkList.ResourceFilter filter = new BulkList.ProjectFilter(getUserFromContext(), _permissionsHelper); return new ProjectBulkRep(BulkList.wrapping(_dbIterator, MapProject.getInstance(), filter)); } /** * Project is not a zone level resource */ @Override protected boolean isZoneLevelResource() { return false; } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.PROJECT; } public static class ProjectResRepFilter<E extends RelatedResourceRep> extends ResRepFilter<E> { public ProjectResRepFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { super(user, permissionsHelper); } @Override public boolean isAccessible(E resrep) { boolean ret = false; URI id = resrep.getId(); Project project = _permissionsHelper.getObjectById(id, Project.class); if (project == null) { return false; } URI tenantUri = project.getTenantOrg().getURI(); ret = isTenantAccessible(tenantUri); if (!ret) { ret = isProjectAccessible(id); } return ret; } } /** * Get object specific permissions filter */ @Override public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { return new ProjectResRepFilter(user, permissionsHelper); } /** * Assign VNAS Servers to a project * * @param id the URN of a ViPR Project * @param vnasParam Assign virtual NAS server parameters * @prereq none * @brief Assign VNAS Servers to a project * @return No data returned in response body * @throws BadRequestException */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/assign-vnas-servers") @CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ALL, ACL.OWN }) public Response assignVNasServersToProject(@PathParam("id") URI id, VirtualNasParam vnasParam) { checkCompatibleVersion(); Project project = getProjectById(id, true); StringBuilder errorMsg = new StringBuilder(); StringSet validVNasServers = validateVNasServers(project, vnasParam, errorMsg); if (validVNasServers != null && !validVNasServers.isEmpty()) { for (String validNas : validVNasServers) { URI vnasURI = URI.create(validNas); VirtualNAS vnas = _permissionsHelper.getObjectById(vnasURI, VirtualNAS.class); vnas.associateProject(project.getId().toString()); _dbClient.persistObject(vnas); } project.setAssignedVNasServers(validVNasServers); _dbClient.persistObject(project); _log.info("Successfully assigned {} virtual NAS Servers to project : {} ", validVNasServers.size(), project.getLabel()); } // Report error, if there are any invalid vnas servers found!!! if (errorMsg != null && errorMsg.length() > 0) { _log.error("Failed to assign virtual NAS server(s) to project. Error: {} ", errorMsg.toString()); throw APIException.badRequests.oneOrMorevNasServersNotAssociatedToProject(); } return Response.ok().build(); } /** * Validate VNAS Servers before assign to a project * * @param project to which VANS servers will be assigned * @param param Assign virtual NAS server parameters * @param error message * @brief Validate VNAS Servers * @return List of valid NAS servers * @throws BadRequestException */ public StringSet validateVNasServers(Project project, VirtualNasParam param, StringBuilder errorMsg) { Set<String> vNasIds = param.getVnasServers(); StringSet validNas = new StringSet(); boolean shareVNASWithMultipleProjects = Boolean.valueOf(customConfigHandler.getComputedCustomConfigValue( CustomConfigConstants.SHARE_VNAS_WITH_MULTIPLE_PROJECTS, "global", null)); _log.info("Can vNAS be shared with multiple projects? : {}", shareVNASWithMultipleProjects); if (vNasIds != null && !vNasIds.isEmpty() && project != null) { // Get list of domains associated with the project Set<String> projectDomains = ProjectUtility.getDomainsOfProject(_permissionsHelper, project); for (String id : vNasIds) { if (project.getAssignedVNasServers().contains(id)) { continue; } URI vnasURI = URI.create(id); VirtualNAS vnas = _permissionsHelper.getObjectById(vnasURI, VirtualNAS.class); ArgValidator.checkEntity(vnas, vnasURI, isIdEmbeddedInURL(vnasURI)); // Validate the VNAS is assigned to project!!! if (!shareVNASWithMultipleProjects && !vnas.isNotAssignedToProject()) { errorMsg.append(" vNas: " + vnas.getNasName() + " is already associated to a project."); _log.error(errorMsg.toString()); continue; } // Validate the VNAS is in Discovery state -VISIBLE!!! if (!DiscoveryStatus.VISIBLE.name().equals(vnas.getDiscoveryStatus())) { errorMsg.append(" vNas " + vnas.getNasName() + " is not in Discovery-VISIBLE state "); _log.error(errorMsg.toString()); continue; } // Validate the VNAS state should be in loaded state !!! if (!vnas.getVNasState().equalsIgnoreCase(VirtualNasState.LOADED.getNasState())) { errorMsg.append(" vNas " + vnas.getNasName() + " is not in Loaded state"); _log.error(errorMsg.toString()); continue; } // Get list of domains associated with a VNAS server and validate with project's domain boolean domainMatched = ProjectUtility.doesProjectDomainMatchesWithVNASDomain(projectDomains, vnas); if (!domainMatched) { errorMsg.append(" vNas " + vnas.getNasName() + " domain is not matched with project domain"); _log.error(errorMsg.toString()); continue; } if (!shareVNASWithMultipleProjects) { /* * Get list of file systems and associated project of VNAS server and validate with Project * If the FS is on a vNAS associated with a project, then validation fails */ URIQueryResultList fsList = new URIQueryResultList(); boolean projectMatched = true; StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, vnas.getStorageDeviceURI()); for (String storagePort : vnas.getStoragePorts()) { _dbClient.queryByConstraint( ContainmentConstraint.Factory.getStoragePortFileshareConstraint(URI.create(storagePort)), fsList); Iterator<URI> fsItr = fsList.iterator(); while (fsItr.hasNext()) { FileShare fileShare = _dbClient.queryObject(FileShare.class, fsItr.next()); if (fileShare != null && !fileShare.getInactive()) { // if fs contain vNAS uri, then compare uri of vNAS assgined to project if (fileShare.getVirtualNAS() != null && 0 == fileShare.getVirtualNAS().compareTo(vnas.getId())) { _log.debug("Validation of assigned vNAS URI: {} and file system path : {} ", fileShare.getVirtualNAS(), fileShare.getPath()); if (!fileShare.getProject().getURI().toString().equals(project.getId().toString())) { projectMatched = false; break; } } else { // for isilon, if fs don't have vNAS uri, compare fspath with base path of AZ if (storageSystem.getSystemType().equals(StorageSystem.Type.isilon.name())) { if (fileShare.getPath().startsWith(vnas.getBaseDirPath() + "/") == false) { continue; } } _log.debug("Validation of assigned vNAS base path {} and file path : {} ", vnas.getBaseDirPath(), fileShare.getPath()); if (!fileShare.getProject().getURI().toString().equals(project.getId().toString())) { projectMatched = false; break; } } } } if (!projectMatched) { break; } } if (!projectMatched) { errorMsg.append(" vNas " + vnas.getNasName() + " has file systems belongs to other project"); _log.error(errorMsg.toString()); continue; } } validNas.add(id); } } else { throw APIException.badRequests.invalidEntryForProjectVNAS(); } return validNas; } /** * Unassigns VNAS server from project. * * @param id the URN of a ViPR Project * @param param Assign virtual NAS server parameters * @prereq none * @brief Unassigns VNAS servers from project * @return No data returned in response body * @throws BadRequestException */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/unassign-vnas-servers") @CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ALL, ACL.OWN }) public Response unassignVNasServersFromProject(@PathParam("id") URI id, VirtualNasParam param) { checkCompatibleVersion(); Project project = getProjectById(id, true); Set<String> vNasIds = param.getVnasServers(); if (vNasIds != null && !vNasIds.isEmpty()) { StringSet vnasServers = project.getAssignedVNasServers(); if (!vnasServers.containsAll(vNasIds)) { throw APIException.badRequests.vNasServersNotAssociatedToProject(); } if (vnasServers != null && !vnasServers.isEmpty()) { for (String vId : vNasIds) { URI vnasURI = URI.create(vId); VirtualNAS vnas = _permissionsHelper.getObjectById(vnasURI, VirtualNAS.class); ArgValidator.checkEntity(vnas, vnasURI, isIdEmbeddedInURL(vnasURI)); if (vnasServers.contains(vId)) { vnas.dissociateProject(id.toString()); _dbClient.updateObject(vnas); project.getAssignedVNasServers().remove(vId); } } _dbClient.updateObject(project); _log.info("Successfully unassigned the VNAS servers from project : {} ", project.getLabel()); } else { throw APIException.badRequests.noVNasServersAssociatedToProject(project.getLabel()); } } else { throw APIException.badRequests.invalidEntryForProjectVNAS(); } return Response.ok().build(); } /** * Check if all the VDCs in the federation are in the same expected * or minimum supported version for this API. * */ private void checkCompatibleVersion() { if (!_dbClient.checkGeoCompatible(EXPECTED_GEO_VERSION)) { throw APIException.badRequests.incompatibleGeoVersions(EXPECTED_GEO_VERSION, FEATURE_NAME); } } }