/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource; import static com.emc.storageos.api.mapper.VirtualPoolMapper.toObjectVirtualPool; import java.net.URI; import java.util.Iterator; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; 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.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.VirtualPoolMapper; import com.emc.storageos.api.mapper.functions.MapObjectVirtualPool; import com.emc.storageos.api.service.impl.placement.VirtualPoolUtil; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.Bucket; import com.emc.storageos.db.client.model.StoragePool; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.VirtualPool.Type; 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.ResourceTypeEnum; import com.emc.storageos.model.auth.ACLAssignmentChanges; import com.emc.storageos.model.auth.ACLAssignments; import com.emc.storageos.model.pools.StoragePoolList; import com.emc.storageos.model.quota.QuotaInfo; import com.emc.storageos.model.quota.QuotaUpdateParam; import com.emc.storageos.model.vpool.CapacityResponse; import com.emc.storageos.model.vpool.ObjectVirtualPoolBulkRep; import com.emc.storageos.model.vpool.ObjectVirtualPoolParam; import com.emc.storageos.model.vpool.ObjectVirtualPoolRestRep; import com.emc.storageos.model.vpool.ObjectVirtualPoolUpdateParam; import com.emc.storageos.model.vpool.VirtualPoolList; import com.emc.storageos.model.vpool.VirtualPoolPoolUpdateParam; 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.security.geo.GeoServiceClient; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.volumecontroller.AttributeMatcher; import com.emc.storageos.volumecontroller.impl.utils.ImplicitPoolMatcher; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.google.common.base.Function; @Path("/object/vpools") @DefaultPermissions(readRoles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }, readAcls = { ACL.USE }, writeRoles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public class ObjectVirtualPoolService extends VirtualPoolService { private static final Logger _log = LoggerFactory.getLogger(ObjectVirtualPoolService.class); /** * Create Object Store VirtualPool * * @param param VirtualPool parameters * @brief Create VirtualPool for a Object store * @return VirtualPool details */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public ObjectVirtualPoolRestRep createObjectVirtualPool(ObjectVirtualPoolParam param) { ArgValidator.checkFieldNotEmpty(param.getName(), VPOOL_NAME); checkForDuplicateName(param.getName(), VirtualPool.class); ArgValidator.checkFieldNotEmpty(param.getDescription(), VPOOL_DESCRIPTION); VirtualPoolUtil.validateObjectVirtualPoolCreateParams(param, _dbClient); VirtualPool cos = prepareVirtualPool(param); StringBuffer errorMessage = new StringBuffer(); // update the implicit pools matching with this VirtualPool. ImplicitPoolMatcher.matchVirtualPoolWithAllStoragePools(cos, _dbClient, _coordinator, errorMessage); _dbClient.createObject(cos); recordOperation(OperationTypeEnum.CREATE_VPOOL, VPOOL_CREATED_DESCRIPTION, cos); return toObjectVirtualPool(cos); } /** * List VirtualPool for Object Store * * @brief List classes of service for a Object store * @return Returns the VirtualPool user is authorized to see */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public VirtualPoolList listObjectVirtualPool( @DefaultValue("") @QueryParam(TENANT_ID_QUERY_PARAM) String tenantId, @DefaultValue("") @QueryParam(VDC_ID_QUERY_PARAM) String shortVdcId) { _geoHelper.verifyVdcId(shortVdcId); return getVirtualPoolList(VirtualPool.Type.object, shortVdcId, tenantId); } /** * Get info for Object Store VirtualPool * * @param id the URN of a ViPR VirtualPool * @brief Show Object store VirtualPool * @return VirtualPool details */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }, acls = { ACL.USE }) public ObjectVirtualPoolRestRep getObjectVirtualPool(@PathParam("id") URI id) { VirtualPool vpool = getVirtualPool(VirtualPool.Type.object, id); ObjectVirtualPoolRestRep restRep = toObjectVirtualPool(vpool); restRep.setNumResources(getNumResources(vpool, _dbClient)); if (null != vpool.getMaxRetention()) { restRep.setMaxRetention(vpool.getMaxRetention()); } if (null != vpool.getMinDataCenters()) { restRep.setMinDataCenters(vpool.getMinDataCenters()); } return restRep; } /** * Deactivate Object Store VirtualPool, this will move the Cos to a "marked-for-deletion" state, * and no more resource may be created using it. * The VirtualPool will be deleted when all references to this VirtualPool of type ObjectShare are deleted * * @param id the URN of a ViPR VirtualPool * @brief Delete Object store VirtualPool * @return VirtualPool details */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deactivate") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public Response deleteObjectVirtualPool(@PathParam("id") URI id) { return deleteVirtualPool(VirtualPool.Type.object, id); } /** * Return the matching pools for a given set of VirtualPool attributes. * This API is useful for user to find the matching pools before creating a VirtualPool. * * @param param : VirtualPoolAttributeParam * @brief List pools matching specified properties in Object store VirtualPool * @return : matching pools. */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/matching-pools") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public StoragePoolList getMatchingPoolsForVirtualPoolAttributes(ObjectVirtualPoolParam param) { StoragePoolList poolList = new StoragePoolList(); VirtualPool vpool = prepareVirtualPool(param); List<URI> poolURIs = _dbClient.queryByType(StoragePool.class, true); List<StoragePool> allPools = _dbClient.queryObject(StoragePool.class, poolURIs); StringBuffer errorMessage = new StringBuffer(); List<StoragePool> matchedPools = ImplicitPoolMatcher.getMatchedPoolWithStoragePools(vpool, allPools, null, null, null, _dbClient, _coordinator, AttributeMatcher.VPOOL_MATCHERS, errorMessage); for (StoragePool pool : matchedPools) { poolList.getPools().add(toNamedRelatedResource(pool, pool.getNativeGuid())); } return poolList; } /** * Get Object Store VirtualPool ACL * * @param id the URN of a ViPR VirtualPool * @brief Show ACL entries for Object store VirtualPool * @return ACL Assignment details */ @GET @Path("/{id}/acl") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }) public ACLAssignments getAcls(@PathParam("id") URI id) { return getAclsOnVirtualPool(VirtualPool.Type.object, id); } /** * Add or remove individual Object Store VirtualPool ACL entry(s). Request body must include at least one add or remove operation. * * @param id the URN of a ViPR VirtualPool * @param changes ACL assignment changes * @brief Add or remove ACL entries from Object store VirtualPool * @return No data returned in response body */ @PUT @Path("/{id}/acl") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN, Role.RESTRICTED_SECURITY_ADMIN }, blockProxies = true) public ACLAssignments updateAcls(@PathParam("id") URI id, ACLAssignmentChanges changes) { return updateAclsOnVirtualPool(VirtualPool.Type.object, id, changes); } /** * Returns list of computed id's for all storage pools matching with the VirtualPool. * This list of pools will be used to do create Object. * * @param id the URN of a ViPR VirtualPool. * * @brief List storage pools in Object store VirtualPool * @return The ids for all storage pools that satisfy the VirtualPool. */ @GET @Path("/{id}/storage-pools") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }) public StoragePoolList getStoragePools(@PathParam("id") URI id) { return getStoragePoolsForVirtualPool(id); } /** * This method re-computes the matched pools for this VirtualPool and returns this information. * * Where as getStoragePools {id}/storage-pools returns whatever is already computed, for matched pools. * * @param id : the URN of a ViPR Block VirtualPool. * @brief Refresh list of storage pools in Object store VirtualPool * @return : List of Pool Ids matching with this VirtualPool. */ @GET @Path("/{id}/refresh-matched-pools") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public StoragePoolList refreshMatchedStoragePools(@PathParam("id") URI id) { return refreshMatchedPools(VirtualPool.Type.object, id); } /** * Update Object VirtualPool only allows if there are no resources associated and * list of attributes changed not changed. * * List of attributes can updated if it satisfies above constraint: * assignedStoragePools & useMatchedStoragePools flag. * * @param param * VirtualPool parameters * @brief Update description of Object store VirtualPool * @return VirtualPool details */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public ObjectVirtualPoolRestRep updateObjectVirtualPool(@PathParam("id") URI id, ObjectVirtualPoolUpdateParam param) { VirtualPool cos = null; ArgValidator.checkFieldUriType(id, VirtualPool.class, "id"); cos = _dbClient.queryObject(VirtualPool.class, id); ArgValidator.checkEntity(cos, id, isIdEmbeddedInURL(id)); if (!cos.getType().equals(VirtualPool.Type.object.name())) { throw APIException.badRequests.unexpectedValueForProperty("VPool type", VirtualPool.Type.object.name(), cos.getType()); } VirtualPoolUtil.validateObjectVirtualPoolUpdateParams(cos, param, _dbClient); // Validate the attributes that could be change if resource is created. if (getNumResources(cos, _dbClient) > 0 && checkAttributeValuesChanged(param, cos)) { throw APIException.badRequests.vPoolUpdateNotAllowed("Bucket"); } // set common update VirtualPool Params here. populateCommonVirtualPoolUpdateParams(cos, param); if (null != param.getMaxRetention()) { cos.setMaxRetention(param.getMaxRetention()); } if (null != param.getMinDataCenters()) { if (!cos.getMinDataCenters().equals(param.getMinDataCenters())) { ArgValidator.checkReference(VirtualPool.class, id, checkForDelete(cos)); } cos.setMinDataCenters(param.getMinDataCenters()); } if (null != param.getSystemType()) { if (cos.getArrayInfo() != null && cos.getArrayInfo().containsKey(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE)) { for (String systemType : cos.getArrayInfo().get( VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE)) { cos.getArrayInfo().remove(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE, systemType); } } if (!(VirtualPool.SystemType.NONE.name().equalsIgnoreCase(param.getSystemType()) || VirtualPool.SystemType.isObjectTypeSystem(param.getSystemType()))) { throw APIException.badRequests.invalidSystemType("Object"); } if (cos.getArrayInfo() == null) { cos.setArrayInfo(new StringSetMap()); } cos.getArrayInfo().put(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE, param.getSystemType()); } StringBuffer errorMessage = new StringBuffer(); // invokes implicit pool matching algorithm. ImplicitPoolMatcher.matchVirtualPoolWithAllStoragePools(cos, _dbClient, _coordinator, errorMessage); _dbClient.updateAndReindexObject(cos); recordOperation(OperationTypeEnum.UPDATE_VPOOL, VPOOL_UPDATED_DESCRIPTION, cos); return toObjectVirtualPool(cos); } /** * Update Object VirtualPool only allows user to assign matching storage pools. * * @param param * VirtualPool parameters * @brief Update storage pools in Object store VirtualPool * @return VirtualPool details */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/assign-matched-pools") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public ObjectVirtualPoolRestRep updateObjectVirtualPoolWithAssignedPools(@PathParam("id") URI id, VirtualPoolPoolUpdateParam param) { return toObjectVirtualPool(updateVirtualPoolWithAssignedStoragePools(id, param)); } /** * Gets storage capacity information for specified VirtualPool and Neighborhood instances. * * The method returns set of metrics for capacity available for Object storage provisioning: * - usable_gb : total storage capacity * - free_gb : free storage capacity * - used_gb : used storage capacity * - percent_used : percent of usable capacity which is used * * @param id the URN of a ViPR VirtualPool. * @param varrayId The id of varray. * @brief Show storage capacity for a VirtualPool and varray * @return Capacity metrics in GB and percent indicator for used capacity. */ @GET @Path("/{id}/varrays/{varrayId}/capacity") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }, acls = { ACL.USE }) public CapacityResponse getCapacity(@PathParam("id") URI id, @PathParam("varrayId") URI varrayId) { return getCapacityForVirtualPoolAndVirtualArray(getVirtualPool(Type.object, id), varrayId); } @Override protected URI getTenantOwner(URI id) { return null; } @Override protected Type getVirtualPoolType() { return Type.object; } /** * @brief List all instances of Object VirtualPools * */ @POST @Path("/bulk") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public ObjectVirtualPoolBulkRep getBulkResources(BulkIdParam param) { return (ObjectVirtualPoolBulkRep) super.getBulkResources(param); } /** * Gets Quota information. * * @param id the URN of a ViPR VirtualPool. * @brief Show quota and available capacity before quota is exhausted * @return QuotaInfo Quota metrics. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN }) @Path("/{id}/quota") public QuotaInfo getQuota(@PathParam("id") URI id) throws DatabaseException { return getQuota(getVirtualPool(Type.object, id)); } /** * Update Quota information. * * @param id the URN of a ViPR VirtualPool. * @param param new values for the quota * @brief Updates quota and available capacity before quota is exhausted * @return QuotaInfo Quota metrics. */ @PUT @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) @Path("/{id}/quota") public QuotaInfo updateQuota(@PathParam("id") URI id, QuotaUpdateParam param) throws DatabaseException { return updateQuota(getVirtualPool(Type.object, id), param); } private class mapObjectVirtualPoolWithResources implements Function<VirtualPool, ObjectVirtualPoolRestRep> { @Override public ObjectVirtualPoolRestRep apply(VirtualPool vpool) { ObjectVirtualPoolRestRep resp = VirtualPoolMapper.toObjectVirtualPool(vpool); resp.setNumResources(getNumResources(vpool, _dbClient)); return resp; } } /** * Gets list of all Object Virtual pool IDs */ @Override public ObjectVirtualPoolBulkRep queryBulkResourceReps(List<URI> ids) { if (!ids.iterator().hasNext()) { return new ObjectVirtualPoolBulkRep(); } // get vdc id from the first id; assume all id's are from the same vdc String shortVdcId = VdcUtil.getVdcId(VirtualArray.class, ids.iterator().next()).toString(); Iterator<VirtualPool> dbIterator; if (shortVdcId.equals(VdcUtil.getLocalShortVdcId())) { dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); } else { GeoServiceClient geoClient = _geoHelper.getClient(shortVdcId); try { dbIterator = geoClient.queryObjects(getResourceClass(), ids); } catch (Exception ex) { // TODO: revisit this exception _log.error("error retrieving bulk virtual pools from vdc " + shortVdcId, ex); throw APIException.internalServerErrors.genericApisvcError("error retrieving remote virtual pool", ex); } } return new ObjectVirtualPoolBulkRep(BulkList.wrapping(dbIterator, new mapObjectVirtualPoolWithResources(), new BulkList.VirtualPoolFilter(Type.object))); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected ObjectVirtualPoolBulkRep queryFilteredBulkResourceReps( List<URI> ids) { if (isSystemAdmin()) { return queryBulkResourceReps(ids); } if (!ids.iterator().hasNext()) { return new ObjectVirtualPoolBulkRep(); } // get vdc id from the first id; assume all id's are from the same vdc String shortVdcId = VdcUtil.getVdcId(VirtualArray.class, ids.iterator().next()).toString(); Iterator<VirtualPool> dbIterator; if (shortVdcId.equals(VdcUtil.getLocalShortVdcId())) { dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); } else { GeoServiceClient geoClient = _geoHelper.getClient(shortVdcId); try { dbIterator = geoClient.queryObjects(getResourceClass(), ids); } catch (Exception ex) { // TODO: revisit this exception _log.error("error retrieving bulk virtual pools from vdc " + shortVdcId, ex); throw APIException.internalServerErrors.genericApisvcError("error retrieving remote virtual pool", ex); } } BulkList.ResourceFilter filter = new BulkList.VirtualPoolFilter(Type.object, getUserFromContext(), _permissionsHelper); return new ObjectVirtualPoolBulkRep(BulkList.wrapping(dbIterator, MapObjectVirtualPool.getInstance(), filter)); } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.OBJECT_VPOOL; } // this method must not persist anything to the DB. private VirtualPool prepareVirtualPool(ObjectVirtualPoolParam param) { VirtualPool vPool = new VirtualPool(); vPool.setType(VirtualPool.Type.object.name()); // set common VirtualPool parameters. populateCommonVirtualPoolCreateParams(vPool, param); StringSetMap arrayInfo = new StringSetMap(); if (null != param.getSystemType()) { if (!VirtualPool.SystemType.NONE.toString().equals(param.getSystemType()) && !VirtualPool.SystemType.isObjectTypeSystem(param.getSystemType())) { throw APIException.badRequests.invalidParameter("system_type", param.getSystemType()); } arrayInfo.put(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE, param.getSystemType()); vPool.addArrayInfoDetails(arrayInfo); } if (null != param.getMaxRetention()) { vPool.setMaxRetention(param.getMaxRetention()); } if (null != param.getMinDataCenters()) { vPool.setMinDataCenters(param.getMinDataCenters()); } return vPool; } private static Integer getNumResources(VirtualPool vpool, DbClient dbClient) { return dbClient.countObjects(Bucket.class, "virtualPool", vpool.getId()); } }