/*
* 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 static com.emc.storageos.api.mapper.DbObjectMapper.toLink;
import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.HostMapper.map;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.api.mapper.DbObjectMapper;
import com.emc.storageos.api.mapper.functions.MapTenant;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.resource.utils.CapacityUtils;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.Constraint;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.ContainmentPermissionsConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.AbstractChangeTrackingSet;
import com.emc.storageos.db.client.model.AuthnProvider;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DataObjectWithACLs;
import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.ObjectNamespace;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.SchedulePolicy;
import com.emc.storageos.db.client.model.SchedulePolicy.SchedulePolicyType;
import com.emc.storageos.db.client.model.SchedulePolicy.SnapshotExpireType;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.TenantResource;
import com.emc.storageos.db.client.model.UserGroup;
import com.emc.storageos.db.client.model.Vcenter;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.VolumeGroup;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
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.NamedRelatedResourceRep;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.application.VolumeGroupList;
import com.emc.storageos.model.auth.PrincipalsToValidate;
import com.emc.storageos.model.auth.RoleAssignmentChanges;
import com.emc.storageos.model.auth.RoleAssignmentEntry;
import com.emc.storageos.model.auth.RoleAssignments;
import com.emc.storageos.model.host.HostCreateParam;
import com.emc.storageos.model.host.HostList;
import com.emc.storageos.model.host.cluster.ClusterCreateParam;
import com.emc.storageos.model.host.cluster.ClusterList;
import com.emc.storageos.model.host.cluster.ClusterRestRep;
import com.emc.storageos.model.host.vcenter.VcenterCreateParam;
import com.emc.storageos.model.host.vcenter.VcenterList;
import com.emc.storageos.model.project.ProjectElement;
import com.emc.storageos.model.project.ProjectList;
import com.emc.storageos.model.project.ProjectParam;
import com.emc.storageos.model.quota.QuotaInfo;
import com.emc.storageos.model.quota.QuotaUpdateParam;
import com.emc.storageos.model.schedulepolicy.PolicyParam;
import com.emc.storageos.model.schedulepolicy.SchedulePolicyList;
import com.emc.storageos.model.schedulepolicy.SchedulePolicyResp;
import com.emc.storageos.model.tenant.TenantCreateParam;
import com.emc.storageos.model.tenant.TenantOrgBulkRep;
import com.emc.storageos.model.tenant.TenantOrgList;
import com.emc.storageos.model.tenant.TenantOrgRestRep;
import com.emc.storageos.model.tenant.TenantUpdateParam;
import com.emc.storageos.model.tenant.UserMappingAttributeParam;
import com.emc.storageos.model.tenant.UserMappingParam;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.ACL;
import com.emc.storageos.security.authorization.BasePermissionsHelper.UserMapping;
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.resource.UserInfoPage;
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.svcs.errorhandling.resources.BadRequestException;
import com.emc.storageos.svcs.errorhandling.resources.ForbiddenException;
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;
import com.google.common.collect.Lists;
/**
* API for creating and manipulating tenants
*/
@Path("/tenants")
@DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR },
writeRoles = { Role.TENANT_ADMIN })
public class TenantsService extends TaggedResource {
private static final String EVENT_SERVICE_TYPE = "tenant";
private static final String EVENT_SERVICE_SOURCE = "TenantManager";
private static final Logger _log = LoggerFactory.getLogger(TenantsService.class);
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
@Autowired
private RecordableEventManager _evtMgr;
@Autowired
private HostService _hostService;
@Autowired
private VcenterService _vcenterService;
@Autowired
private ClusterService _clusterService;
/**
* Get info for tenant or subtenant
*
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief Show tenant or subtenant
* @return Tenant details
*/
@GET
@Path("/{id}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN })
public TenantOrgRestRep getTenant(@PathParam("id") URI id) {
return map(getTenantById(id, false));
}
@Override
protected DataObject queryResource(URI id) {
return getTenantById(id, false);
}
@Override
protected URI getTenantOwner(URI id) {
return id;
}
/**
* Filter class for validating input args for role assignments
*/
private class TenantRoleInputFilter extends PermissionsHelper.RoleInputFilter {
protected List<String> _tenantAdminUsers;
public TenantRoleInputFilter(TenantOrg tenant) {
super(tenant);
}
@Override
public boolean isValidRole(String ace) {
return _permissionsHelper.isRoleTenantLevel(ace);
}
@Override
public StringSetMap convertFromRolesRemove(List<RoleAssignmentEntry> remove) {
return convertFromRoleAssignments(remove);
}
@Override
public void initLists() {
super.initLists();
_tenantAdminUsers = new ArrayList<String>();
}
@Override
protected void addPrincipalToList(PermissionsKey key,
RoleAssignmentEntry roleAssignment) {
switch (key.getType()) {
case GROUP:
_groups.add(key.getValue());
break;
case SID:
if (roleAssignment.getRoles().contains(Role.TENANT_ADMIN.toString())) {
_tenantAdminUsers.add(key.getValue());
} else {
_users.add(key.getValue());
}
break;
case TENANT:
default:
break;
}
}
@Override
public void validatePrincipals() {
StringBuilder error = new StringBuilder();
// we allow exceptions if the role we want to assign is
// TENANT_ADMIN, and either the user is root, or it belongs to the
// root tenant
_tenantAdminUsers.remove("root");
PrincipalsToValidate principalsToValidate = new PrincipalsToValidate();
principalsToValidate.setTenantId(_tenant.getId().toString());
principalsToValidate.setGroups(_groups);
principalsToValidate.setUsers(_users);
principalsToValidate.setAltTenantId(_tenant.getParentTenant().getURI()
.toString());
principalsToValidate.setAltTenantUsers(_tenantAdminUsers);
if (!Validator.validatePrincipals(principalsToValidate, error)) {
throw APIException.badRequests.invalidRoleAssignments(error.toString());
}
}
@Override
public StringSetMap convertFromRolesAdd(List<RoleAssignmentEntry> add,
boolean validate) {
StringSetMap returnedStringSetMap = convertFromRoleAssignments(add);
if (!CollectionUtils.isEmpty(_rootRoleAssignments)
&& !VdcUtil.isLocalVdcSingleSite()) {
throw APIException.badRequests
.invalidRoleAssignments("Tenant Roles can't be assigned to root in GEO scenario.");
}
if (!CollectionUtils.isEmpty(_rootRoleAssignments)
&& _rootRoleAssignments.size() == 1
&& _rootRoleAssignments.get(0).equalsIgnoreCase(
Role.TENANT_ADMIN.toString())) {
_localUsers.remove("root");
}
if (!CollectionUtils.isEmpty(_localUsers)) {
throw APIException.badRequests
.invalidEntryForRoleAssignmentSubjectIdsCannotBeALocalUsers(StringUtils
.join(_localUsers, ", "));
}
if (validate) {
validatePrincipals();
}
return returnedStringSetMap;
}
}
/**
* Update info for tenant or subtenant
*
* @param param Tenant update parameter
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq If modifying user mappings, an authentication provider needs to support the domain used in the mappings
* @brief Update tenant or subtenant
* @return the updated Tenant/Subtenant instance
*/
@PUT
@Path("/{id}")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN, Role.SECURITY_ADMIN })
public TenantOrgRestRep setTenant(@PathParam("id") URI id, TenantUpdateParam param) {
TenantOrg tenant = getTenantById(id, true);
ObjectNamespace namesp = null;
boolean namespModified = false;
ObjectNamespace oldNamesp = null;
boolean oldNamespModified = false;
if (param.getLabel() != null && !param.getLabel().isEmpty()) {
if (!tenant.getLabel().equalsIgnoreCase(param.getLabel())) {
checkForDuplicateName(param.getLabel(), TenantOrg.class, tenant.getParentTenant()
.getURI(), "parentTenant", _dbClient);
}
tenant.setLabel(param.getLabel());
NamedURI parent = tenant.getParentTenant();
if (parent != null) {
parent.setName(param.getLabel());
tenant.setParentTenant(parent);
}
}
if (param.getDescription() != null) {
tenant.setDescription(param.getDescription());
}
if (!StringUtils.isEmpty(param.getNamespace())) {
if (!param.getNamespace().equals(tenant.getNamespace())) {
checkForDuplicateNamespace(param.getNamespace());
}
if (!StringUtils.isEmpty(tenant.getNamespace()) && !"null".equals(tenant.getNamespace())) {
if (!tenant.getNamespace().equalsIgnoreCase(param.getNamespace())) {
List<Class<? extends DataObject>> excludeTypes = Lists.newArrayList();
excludeTypes.add(ObjectNamespace.class);
// Though we are not deleting need to check no dependencies on this tenant
ArgValidator.checkReference(TenantOrg.class, id, checkForDelete(tenant, excludeTypes));
}
}
String oldNamespace = tenant.getNamespace();
tenant.setNamespace(param.getNamespace());
// Update tenant info in respective namespace CF
List<URI> allNamespaceURI = _dbClient.queryByType(ObjectNamespace.class, true);
Iterator<ObjectNamespace> nsItr = _dbClient.queryIterativeObjects(ObjectNamespace.class, allNamespaceURI);
while (nsItr.hasNext()) {
namesp = nsItr.next();
if (namesp.getNativeId().equalsIgnoreCase(param.getNamespace())) {
namesp.setTenant(tenant.getId());
namesp.setMapped(true);
// There is a chance of exceptions ahead; hence updated db at the end
namespModified = true;
break;
}
}
// removing link between tenant and the old namespace
List<URI> namespaceURIs = _dbClient.queryByType(ObjectNamespace.class, true);
Iterator<ObjectNamespace> nsItrToUnMap = _dbClient.queryIterativeObjects(ObjectNamespace.class, namespaceURIs);
while (nsItrToUnMap.hasNext()) {
oldNamesp = nsItrToUnMap.next();
if (oldNamesp.getNativeId().equalsIgnoreCase(oldNamespace)) {
oldNamesp.setMapped(false);
oldNamespModified = true;
break;
}
}
}
if (param.getDetachNamespace()) {
List<Class<? extends DataObject>> excludeTypes = Lists.newArrayList();
excludeTypes.add(ObjectNamespace.class);
// Though we are not deleting need to check no dependencies on this tenant
ArgValidator.checkReference(TenantOrg.class, id, checkForDelete(tenant, excludeTypes));
String oldNamespace = tenant.getNamespace();
tenant.setNamespace(NullColumnValueGetter.getNullStr());
// Update tenant info in respective namespace CF
List<URI> allNamespaceURI = _dbClient.queryByType(ObjectNamespace.class, true);
Iterator<ObjectNamespace> nsItr = _dbClient.queryIterativeObjects(ObjectNamespace.class, allNamespaceURI);
while (nsItr.hasNext()) {
namesp = nsItr.next();
if (namesp.getNativeId().equalsIgnoreCase(oldNamespace)) {
namesp.setMapped(false);
// There is a chance of exceptions ahead; hence updated db at the end
namespModified = true;
break;
}
}
}
if (!isUserMappingEmpty(param)) {
// only SecurityAdmin can modify user-mapping
if (!_permissionsHelper.userHasGivenRole(
(StorageOSUser) sc.getUserPrincipal(),
null, Role.SECURITY_ADMIN)) {
throw ForbiddenException.forbidden.onlySecurityAdminsCanModifyUserMapping();
}
if (null != param.getUserMappingChanges().getRemove() && !param.getUserMappingChanges().getRemove().isEmpty()
&& null != tenant.getUserMappings()) {
checkUserMappingAttribute(param.getUserMappingChanges().getRemove());
List<UserMapping> remove = UserMapping.fromParamList(param.getUserMappingChanges().getRemove());
StringSetMap mappingsToRemove = new StringSetMap();
// Find the database entries to remove
for (UserMapping mappingToRemove : remove) {
StringSet domainMappings = tenant.getUserMappings().get(mappingToRemove.getDomain().trim());
trimGroupAndDomainNames(mappingToRemove);
if (null != domainMappings) {
for (String existingMapping : domainMappings) {
if (mappingToRemove.equals(UserMapping.fromString(existingMapping))) {
mappingsToRemove.put(mappingToRemove.getDomain(), existingMapping);
}
}
}
}
// Remove the items from the tenant database object
for (Entry<String, AbstractChangeTrackingSet<String>> mappingToRemoveSet : mappingsToRemove
.entrySet()) {
for (String mappingToRemove : mappingToRemoveSet.getValue()) {
tenant.removeUserMapping(mappingToRemoveSet.getKey(),
mappingToRemove);
}
}
}
if (null != param.getUserMappingChanges().getAdd() && !param.getUserMappingChanges().getAdd().isEmpty()) {
checkUserMappingAttribute(param.getUserMappingChanges().getAdd());
addUserMappings(tenant, param.getUserMappingChanges().getAdd(), getUserFromContext());
}
if (!TenantOrg.isRootTenant(tenant)) {
boolean bMappingsEmpty = true;
for (AbstractChangeTrackingSet<String> mapping : tenant
.getUserMappings().values()) {
if (!mapping.isEmpty()) {
bMappingsEmpty = false;
break;
}
}
if (bMappingsEmpty) {
throw APIException.badRequests
.requiredParameterMissingOrEmpty("user_mappings");
}
}
// request contains user-mapping change, perform the check.
mapOutProviderTenantCheck(tenant);
}
if (namespModified) {
_dbClient.updateObject(namesp);
}
if (oldNamespModified) {
_dbClient.updateObject(oldNamesp);
}
_dbClient.updateAndReindexObject(tenant);
recordOperation(OperationTypeEnum.UPDATE_TENANT, tenant.getId(), tenant);
return map(getTenantById(id, false));
}
/**
* Create subtenant
*
* @param param Subtenant create parameter
* @param id the URN of a ViPR Tenant
* @prereq An authentication provider needs to support the domain used in the mappings
* @brief Create subtenant
* @return Subtenant details
*/
@POST
@Path("/{id}/subtenants")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN })
public TenantOrgRestRep createSubTenant(@PathParam("id") URI id,
TenantCreateParam param) {
ObjectNamespace namesp = null;
boolean namespModified = false;
TenantOrg parent = getTenantById(id, true);
if (!TenantOrg.isRootTenant(parent)) {
throw APIException.badRequests.parentTenantIsNotRoot();
}
ArgValidator.checkFieldNotEmpty(param.getLabel(), "name");
checkForDuplicateName(param.getLabel(), TenantOrg.class, id, "parentTenant", _dbClient);
TenantOrg subtenant = new TenantOrg();
subtenant.setId(URIUtil.createId(TenantOrg.class));
subtenant.setParentTenant(new NamedURI(parent.getId(), param.getLabel()));
subtenant.setLabel(param.getLabel());
subtenant.setDescription(param.getDescription());
if (param.getNamespace() != null) {
checkForDuplicateNamespace(param.getNamespace());
subtenant.setNamespace(param.getNamespace());
// Update tenant info in respective namespace CF
List<URI> allNamespaceURI = _dbClient.queryByType(ObjectNamespace.class, true);
Iterator<ObjectNamespace> nsItr = _dbClient.queryIterativeObjects(ObjectNamespace.class, allNamespaceURI);
while (nsItr.hasNext()) {
namesp = nsItr.next();
if (subtenant.getNamespace().equalsIgnoreCase(namesp.getNativeId())) {
namesp.setTenant(subtenant.getId());
namesp.setMapped(true);
// There could be exceptions ahead; update the db at end
namespModified = true;
break;
}
}
}
if (null == param.getUserMappings() || param.getUserMappings().isEmpty()) {
throw APIException.badRequests
.requiredParameterMissingOrEmpty("user_mappings");
} else {
checkUserMappingAttribute(param.getUserMappings());
addUserMappings(subtenant, param.getUserMappings(), getUserFromContext());
}
// add creator as tenant admin
subtenant.addRole(new PermissionsKey(PermissionsKey.Type.SID,
getUserFromContext().getName()).toString(), Role.TENANT_ADMIN.toString());
// perform user tenant check before persistent
mapOutProviderTenantCheck(subtenant);
if (namespModified) {
_dbClient.updateObject(namesp);
}
_dbClient.createObject(subtenant);
// To Do - add attributes to the set of attributes to pull from AD/LDAP
recordOperation(OperationTypeEnum.CREATE_TENANT, parent.getId(), subtenant);
return map(subtenant);
}
/**
* List subtenants
*
* @param id the URN of a ViPR Tenant
* @prereq none
* @brief List subtenants
* @return List of subtenants
*/
@GET
@Path("/{id}/subtenants")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public TenantOrgList listSubTenants(@PathParam("id") URI id) {
StorageOSUser user = getUserFromContext();
TenantOrg tenant = getTenantById(id, false);
TenantOrgList list = new TenantOrgList();
if (!TenantOrg.isRootTenant(tenant)) {
// no subtenants if not root tenant
throw APIException.methodNotAllowed.notSupportedForSubtenants();
}
NamedElementQueryResultList subtenants = new NamedElementQueryResultList();
if (_permissionsHelper.userHasGivenRole(user, tenant.getId(),
Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN)) {
_dbClient.queryByConstraint(ContainmentConstraint.Factory
.getTenantOrgSubTenantConstraint(tenant.getId()), subtenants);
} else {
// we will most likely not need indexing for tenants
// given the number of tenants is not going to be that many
Set<String> roles = new HashSet<String>();
roles.add(Role.TENANT_ADMIN.toString());
Map<URI, Set<String>> allTenantPermissions =
_permissionsHelper.getAllPermissionsForUser(user, tenant.getId(),
roles, true);
if (!allTenantPermissions.keySet().isEmpty()) {
List<TenantOrg> tenants =
_dbClient.queryObjectField(TenantOrg.class, "label",
new ArrayList<URI>(allTenantPermissions.keySet()));
List<NamedElementQueryResultList.NamedElement> elements =
new ArrayList<NamedElementQueryResultList.NamedElement>(
tenants.size());
for (TenantOrg t : tenants) {
elements.add(NamedElementQueryResultList.NamedElement.createElement(
t.getId(), t.getLabel()));
}
subtenants.setResult(elements.iterator());
} else {
throw APIException.forbidden.insufficientPermissionsForUser(user
.getName());
}
}
for (NamedElementQueryResultList.NamedElement el : subtenants) {
list.getSubtenants().add(
toNamedRelatedResource(ResourceTypeEnum.TENANT, el.getId(), el.getName()));
}
return list;
}
/**
* Deactivate the subtenant.
* When a subtenant is deleted it will move to a "marked for deletion" state.
* Once in this state, new projects may no longer be created in the subtenant.
* The subtenant will be permanently deleted once all Projects and UserSecretKeys
* referencing this tenant are deleted.
*
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief Delete subtenant
* @return No data returned in response body
*/
@POST
@Path("/{id}/deactivate")
@CheckPermission(roles = { Role.SECURITY_ADMIN })
public Response deactivateTenant(@PathParam("id") URI id) {
TenantOrg tenant = getTenantById(id, true);
if (TenantOrg.isRootTenant(tenant)) {
// root can not be deleted
throw APIException.badRequests.resourceCannotBeDeleted("Root tenant");
}
ArgValidator.checkReference(TenantOrg.class, id, checkForDelete(tenant));
_dbClient.markForDeletion(tenant);
recordOperation(OperationTypeEnum.DELETE_TENANT, tenant.getParentTenant()
.getURI(), tenant);
// clear references to this deleted sub-tenant from the USE ACL lists of vpools and varrays
clearTenantACLs(VirtualPool.class, tenant.getId(), VirtualPool.Type.block.toString());
clearTenantACLs(VirtualPool.class, tenant.getId(), VirtualPool.Type.file.toString());
clearTenantACLs(VirtualArray.class, tenant.getId(), null);
return Response.ok().build();
}
/**
* Get tenant or subtenant role assignments
*
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief List tenant or subtenant role assignments
* @return Role assignment details
*/
@GET
@Path("/{id}/role-assignments")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN })
public RoleAssignments getRoleAssignments(@PathParam("id") URI id) {
TenantOrg tenant = getTenantById(id, false);
return getRoleAssignmentsResponse(tenant);
}
/**
* Add or remove individual role assignments
*
* @param changes Role Assignment changes
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief Add or remove role assignments
* @return No data returned in response body
*/
@PUT
@Path("/{id}/role-assignments")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.TENANT_ADMIN },
blockProxies = true)
public RoleAssignments updateRoleAssignments(@PathParam("id") URI id,
RoleAssignmentChanges changes) {
TenantOrg tenant = getTenantById(id, true);
_permissionsHelper.updateRoleAssignments(tenant, changes,
new TenantRoleInputFilter(tenant));
_dbClient.updateAndReindexObject(tenant);
recordTenantEvent(OperationTypeEnum.MODIFY_TENANT_ROLES, tenant.getId(),
tenant.getId());
auditOp(OperationTypeEnum.MODIFY_TENANT_ROLES, true, null, tenant.getId()
.toString(), tenant.getLabel(), changes);
return getRoleAssignmentsResponse(tenant);
}
/**
* Create a project
*
* @param param Project parameters
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief Create project
* @return Project details
*/
@POST
@Path("/{id}/projects")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN, Role.PROJECT_ADMIN })
public ProjectElement createProject(@PathParam("id") URI id, ProjectParam param) {
ProjectElement projectElement = createProject(id, param, getUserFromContext().getName(), getUserFromContext().getTenantId()
.toString());
auditOp(OperationTypeEnum.CREATE_PROJECT, true, null, param.getName(),
id.toString(), projectElement.getId().toString());
return projectElement;
}
/**
* Worker method for create project. Allows external requests (REST) as well as
* internal requests that may not have a security context.
*
* @param id tenant id
* @param param project params
* @param owner name of owner of the request
* @param ownerTenantId tenant id of the owner
* @return project details
*/
public ProjectElement createProject(URI id, ProjectParam param, String owner, String ownerTenantId) {
TenantOrg tenant = getTenantById(id, true);
if (param.getName() != null && !param.getName().isEmpty()) {
checkForDuplicateName(param.getName(), Project.class, id, "tenantOrg", _dbClient);
}
Project project = new Project();
project.setId(URIUtil.createId(Project.class));
project.setLabel(param.getName());
project.setTenantOrg(new NamedURI(tenant.getId(), project.getLabel()));
project.setOwner(owner);
// set owner acl
project.addAcl(
new PermissionsKey(PermissionsKey.Type.SID, owner, ownerTenantId).toString(),
ACL.OWN.toString());
_dbClient.createObject(project);
recordTenantEvent(OperationTypeEnum.CREATE_PROJECT, tenant.getId(),
project.getId());
return new ProjectElement(project.getId(), toLink(ResourceTypeEnum.PROJECT,
project.getId()), project.getLabel());
}
/**
* List projects the user is authorized to see
*
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief List projects
* @return List of projects
*/
@GET
@Path("/{id}/projects")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public ProjectList listProjects(@PathParam("id") URI id) {
TenantOrg tenant = getTenantById(id, false);
StorageOSUser user = getUserFromContext();
NamedElementQueryResultList projects = new NamedElementQueryResultList();
if (_permissionsHelper.userHasGivenRole(user, tenant.getId(),
Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN)) {
// list all
_dbClient.queryByConstraint(ContainmentConstraint.Factory
.getTenantOrgProjectConstraint(tenant.getId()), projects);
} else {
// list only projects that the user has access to
if (!id.equals(URI.create(user.getTenantId()))) {
throw APIException.forbidden.insufficientPermissionsForUser(user
.getName());
}
Map<URI, Set<String>> allMyProjects =
_permissionsHelper.getAllPermissionsForUser(user, tenant.getId(),
null, false);
if (!allMyProjects.keySet().isEmpty()) {
List<Project> project_list =
_dbClient.queryObjectField(Project.class, "label",
new ArrayList<URI>(allMyProjects.keySet()));
List<NamedElementQueryResultList.NamedElement> elements =
new ArrayList<NamedElementQueryResultList.NamedElement>(
project_list.size());
for (Project p : project_list) {
elements.add(NamedElementQueryResultList.NamedElement.createElement(
p.getId(), p.getLabel()));
}
projects.setResult(elements.iterator());
} else {
// empty list
projects.setResult(new ArrayList<NamedElementQueryResultList.NamedElement>()
.iterator());
}
}
ProjectList list = new ProjectList();
for (NamedElementQueryResultList.NamedElement el : projects) {
list.getProjects().add(
toNamedRelatedResource(ResourceTypeEnum.PROJECT, el.getId(), el.getName()));
}
return list;
}
/**
* List volume groups the user is authorized to see
*
* @param id the URN of a ViPR Tenant/Subtenant
* @prereq none
* @brief List volume groups
* @return List of volume groups
*/
@GET
@Path("/{id}/volume-groups")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public VolumeGroupList listVolumeGroups(@PathParam("id") URI id) {
// tenant id and user permission will get validated in listProjects()
ProjectList projectList = listProjects(id);
Set<URI> projects = new HashSet<URI>();
for (NamedRelatedResourceRep projectRep : projectList.getProjects()) {
projects.add(projectRep.getId());
}
// for each project, get all volumes. Collect volume group ids for all volumes
StringSet volumeGroups = new StringSet();
for (URI project : projects) {
URIQueryResultList list = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getProjectVolumeConstraint(project), list);
Iterator<Volume> resultsIt = _dbClient.queryIterativeObjects(Volume.class, list);
while (resultsIt.hasNext()) {
volumeGroups.addAll(resultsIt.next().getVolumeGroupIds());
}
}
// form Volume Group list response
VolumeGroupList volumeGroupList = new VolumeGroupList();
for (String vg : volumeGroups) {
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, URI.create(vg));
volumeGroupList.getVolumeGroups().add(toNamedRelatedResource(volumeGroup));
}
return volumeGroupList;
}
/**
* Retrieve resource representations based on input ids.
*
* @param param
* POST data containing the id list.
* @prereq none
* @brief List data of tenant 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 TenantOrgBulkRep getBulkResources(BulkIdParam param) {
return (TenantOrgBulkRep) super.getBulkResources(param);
}
/**
* Add the list of user mappings to the tenant. Check for conflicting mappings that
* already exist.
*
* @param tenant
* @param userMappingParams
*/
private void addUserMappings(TenantOrg tenant,
List<UserMappingParam> userMappingParams, StorageOSUser user) {
List<UserMapping> userMappings = UserMapping.fromParamList(userMappingParams);
Map<String, Map<URI, List<UserMapping>>> domainUserMappingMap =
new HashMap<String, Map<URI, List<UserMapping>>>();
for (UserMapping userMapping : userMappings) {
if (null == userMapping.getDomain() || userMapping.getDomain().isEmpty()) {
throw APIException.badRequests.requiredParameterMissingOrEmpty("domain");
}
trimGroupAndDomainNames(userMapping);
if (!authNProviderExistsForDomain(userMapping.getDomain())) {
throw APIException.badRequests.invalidParameter("domain",
userMapping.getDomain());
}
String domain = userMapping.getDomain();
Map<URI, List<UserMapping>> domainMappings = domainUserMappingMap.get(domain);
if (null == domainMappings) {
domainMappings = _permissionsHelper.getAllUserMappingsForDomain(domain);
domainUserMappingMap.put(domain, domainMappings);
}
for (Entry<URI, List<UserMapping>> existingMappingEntry : domainMappings
.entrySet()) {
if (!tenant.getId().equals(existingMappingEntry.getKey())) {
for (UserMapping existingMapping : existingMappingEntry.getValue()) {
if (userMapping.isMatch(existingMapping)) {
URI dupTenantURI = existingMappingEntry.getKey();
throw _permissionsHelper.userHasGivenRole(user, dupTenantURI,
Role.TENANT_ADMIN) ? APIException.badRequests
.userMappingDuplicatedInAnotherTenantExtended(
userMapping.toString(),
dupTenantURI.toString())
: APIException.badRequests
.userMappingDuplicatedInAnotherTenant(userMapping
.toString());
}
}
}
}
// Now validate the user mapping. Now the user mapping is done
// from the tenants service itself in order to differenticate
// user group and groups in authentication provider.
if (!isValidMapping(userMapping)) {
throw APIException.badRequests.invalidParameter("user_mapping",
userMapping.toString());
}
tenant.addUserMapping(userMapping.getDomain(), userMapping.toString());
}
}
/**
* @param userMapping
* object for the tenant containing the domain, group and attribute
* information The method will trim spaces at the beginning and at the end
* of the domain and groups in the provided UserMapping object.
*
* the method also trim domain name from groups, if it matches domain.
*/
private void trimGroupAndDomainNames(UserMapping userMapping) {
if (null != userMapping) {
userMapping.setDomain(userMapping.getDomain().trim());
List<String> groupsIn = userMapping.getGroups();
List<String> groupsOut = new ArrayList<String>();
for (String igroup : groupsIn) {
String groupName = igroup.trim();
// if group contains @, potentially, the string after @ is its domain name,
// if it matches authentication server, trim it.
// if not match, then keep it, as it is part of the group name.
String[] groupItems = igroup.split("@");
if (groupItems.length > 1) {
String inputDomain = groupItems[groupItems.length - 1];
if (inputDomain.equalsIgnoreCase(userMapping.getDomain())) {
groupName = groupName.substring(0, groupName.lastIndexOf("@"));
}
}
groupsOut.add(groupName);
}
if (!groupsOut.isEmpty()) {
userMapping.setGroups(groupsOut);
}
}
}
/**
* Get tenant object from id
*
* @param id the URN of a ViPR tenant
* @return
*/
private TenantOrg getTenantById(URI id, boolean checkInactive) {
if (id == null) {
return null;
}
TenantOrg org = _permissionsHelper.getObjectById(id, TenantOrg.class);
ArgValidator.checkEntity(org, id, isIdEmbeddedInURL(id), checkInactive);
return org;
}
/**
* Check if a provider exists for the given domain
*
* @param domain
* @return
*/
private boolean authNProviderExistsForDomain(String domain) {
URIQueryResultList providers = new URIQueryResultList();
try {
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getAuthnProviderDomainConstraint(domain), providers);
} catch (DatabaseException ex) {
_log.error(
"Could not query for authn providers to check for existing domain {}",
domain, ex.getStackTrace());
throw ex;
}
// check if there is an AuthnProvider contains the given domain and not in disabled state
boolean bExist = false;
Iterator<URI> it = providers.iterator();
while (it.hasNext()) {
URI providerURI = it.next();
AuthnProvider provider = _dbClient.queryObject(AuthnProvider.class, providerURI);
if (provider != null && provider.getDisable() == false) {
bExist = true;
break;
}
}
return bExist;
}
/**
* Record Bourne Event for the completed operations
*
* @param tenantId
* @param targetId
*/
private void recordTenantEvent(OperationTypeEnum opType, URI tenantId, URI targetId) {
String type = opType.getEvType(true);
String description = opType.getDescription();
RecordableBourneEvent event = new RecordableBourneEvent(
/* String */type,
/* tenant id */tenantId,
/* user id ?? */URI.create("ViPR-User"),
/* project ID */null,
/* CoS */null,
/* service */EVENT_SERVICE_TYPE,
/* resource id */targetId,
/* description */description,
/* timestamp */System.currentTimeMillis(),
/* extensions */"",
/* native guid */null,
/* record type */RecordType.Event.name(),
/* Event Source */EVENT_SERVICE_SOURCE,
/* Operational Status codes */"",
/* Operational Status Descriptions */"");
try {
_evtMgr.recordEvents(event);
} catch (Exception ex) {
if (ex instanceof RuntimeException) {
// CQ604367 --
// print full stack trace of error. Problem is intermittent
// and can not be reproduced easily. A NullPointerException
// is getting thrown. This will code will help identify in
// apisvc.log exactly where that occurs when it does.
_log.error("Failed to record event. Event description: " + description
+ ". Error: .", ex);
} else {
_log.error("Failed to record event. Event description: {}. Error: {}.",
description, ex);
}
}
_log.info("opType: {} detail: {}", opType.toString(), type + ':' + description);
}
/**
* Creates a new host for the tenant organization. Discovery is initiated
* after the host is created.
* <p>
* This method is deprecated. Use /compute/hosts instead
*
* @param tid
* the tenant organization id
* @param createParam
* the parameter that has the type and attribute of the host to be created.
* @prereq none
* @deprecated use {@link HostService#createHost(HostCreateParam,Boolean)}
* @brief Create tenant host
* @return the host discovery async task representation.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/hosts")
@Deprecated
public TaskResourceRep createHost(@PathParam("id") URI tid,
HostCreateParam createParam,
@QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection) {
// This is mostly to validate the tenant
TenantOrg tenant = getTenantById(tid, true);
HostService hostService = _hostService;
hostService.validateHostData(createParam, tid, null, validateConnection);
// Create the host
return hostService.createNewHost(tenant, createParam);
}
/**
* Lists the id and name for all the hosts that belong to the given tenant organization.
* <p>
* This method is deprecated. Use /compute/hosts instead
*
* @param id the URN of a ViPR tenant organization
* @prereq none
* @deprecated use {@link HostService#listHosts()}
* @brief List tenant hosts
* @return a list of hosts that belong to the tenant organization.
* @throws DatabaseException when a DB error occurs
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/hosts")
@Deprecated
public HostList listHosts(@PathParam("id") URI id) throws DatabaseException {
// this call validates the tenant id
getTenantById(id, false);
// check the user permissions for this tenant org
verifyAuthorizedInTenantOrg(id, getUserFromContext());
// get all host children
HostList list = new HostList();
list.setHosts(map(ResourceTypeEnum.HOST,
listChildren(id, Host.class, "label", "tenant")));
return list;
}
/**
* Creates a new vCenter for the tenant organization. Discovery is initiated
* after the vCenter is created.
*
* @param tid
* the tenant organization id
* @param createParam
* the parameter that has the attributes of the vCenter to be created.
* @prereq none
* @brief Create tenant vCenter
* @return the vCenter discovery async task.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/vcenters")
public TaskResourceRep createVcenter(@PathParam("id") URI tid,
VcenterCreateParam createParam,
@QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection) {
// This validates the tenant
TenantOrg tenant = getTenantById(tid, true);
VcenterService service = _vcenterService;
// validates the create param and validation is successful then creates and persist the vcenter
Vcenter vcenter = service.createNewTenantVcenter(tenant, createParam, validateConnection);
vcenter.setRegistrationStatus(RegistrationStatus.REGISTERED.toString());
_dbClient.createObject(vcenter);
recordTenantResourceOperation(OperationTypeEnum.CREATE_VCENTER, tid, vcenter);
return service.doDiscoverVcenter(vcenter);
}
/**
* Lists the id and name for all the vCenters that belong to the given tenant organization.
*
* @param id the URN of a ViPR tenant organization
* @prereq none
* @brief List tenant vCenters
* @return a list of hosts that belong to the tenant organization.
* @throws DatabaseException when a DB error occurs
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/vcenters")
public VcenterList listVcenters(@PathParam("id") URI id) throws DatabaseException {
// this call is for validating the tenant Id
getTenantById(id, false);
// check the user permissions for this tenant org
verifyAuthorizedInTenantOrg(id, getUserFromContext());
// get all children vcenters
VcenterList list = new VcenterList();
list.setVcenters(map(ResourceTypeEnum.VCENTER, listChildrenWithAcls(id, Vcenter.class, "label")));
return list;
}
/**
* Creates a new host cluster for the tenant organization.
*
* @param tid
* the tenant organization id
* @param createParam
* the parameter that has the type and attribute of the host to be created.
* @prereq none
* @brief Create host cluster for tenant
* @return the host discovery async task representation.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/clusters")
public ClusterRestRep createCluster(@PathParam("id") URI tid,
ClusterCreateParam createParam) {
// This is mostly to validate the tenant
TenantOrg tenant = getTenantById(tid, true);
ClusterService clusterService = _clusterService;
clusterService.validateClusterData(createParam, tid, null, _dbClient);
Cluster cluster = clusterService.createNewCluster(tenant, createParam);
_dbClient.createObject(cluster);
recordTenantResourceOperation(OperationTypeEnum.CREATE_CLUSTER, tid, cluster);
return map(cluster);
}
/**
* Lists the id and name for all the clusters that belong to the
* tenant organization.
*
* @param id the URN of a ViPR tenant organization
* @prereq none
* @brief List tenant clusters
* @return a list of hosts that belong to the tenant organization.
* @throws DatabaseException when a DB error occurs
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/clusters")
public ClusterList listClusters(@PathParam("id") URI id) throws DatabaseException {
// this called just for validation
getTenantById(id, false);
// check the user permissions for this tenant org
verifyAuthorizedInTenantOrg(id, getUserFromContext());
// get the children
ClusterList list = new ClusterList();
list.setClusters(map(ResourceTypeEnum.CLUSTER,
listChildren(id, Cluster.class, "label", "tenant")));
return list;
}
/**
* Show quota and available capacity before quota is exhausted
*
* @param id the URN of a ViPR Tenant.
* @prereq none
* @brief Show quota and available capacity
* @return QuotaInfo Quota metrics.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SECURITY_ADMIN })
@Path("/{id}/quota")
public QuotaInfo getQuota(@PathParam("id") URI id) throws DatabaseException {
TenantOrg tenant = getTenantById(id, true);
return getQuota(tenant);
}
/**
* Updates quota and available capacity before quota is exhausted
*
* @param id the URN of a ViPR Tenant.
* @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.SYSTEM_ADMIN, Role.SECURITY_ADMIN })
@Path("/{id}/quota")
public QuotaInfo updateQuota(@PathParam("id") URI id, QuotaUpdateParam param)
throws DatabaseException {
TenantOrg tenant = getTenantById(id, true);
tenant.setQuotaEnabled(param.getEnable());
if (param.getEnable()) {
long quota_gb =
(param.getQuotaInGb() != null) ? param.getQuotaInGb() : tenant
.getQuota();
ArgValidator.checkFieldMinimum(quota_gb, 0, "quota_gb", "GB");
// Verify that the quota of this "sub-tenant" does exceed the new quota for
// its parent;
if (!TenantOrg.isRootTenant(tenant)) {
TenantOrg parent =
_dbClient.queryObject(TenantOrg.class, tenant.getParentTenant()
.getURI());
if (parent.getQuotaEnabled()) {
long totalSubtenants =
CapacityUtils.totalSubtenantQuota(_dbClient, parent.getId());
totalSubtenants = totalSubtenants - tenant.getQuota() + quota_gb;
if (totalSubtenants > parent.getQuota()) {
throw APIException.badRequests
.invalidParameterTenantsQuotaInvalidatesParent(parent
.getQuota());
}
}
}
// Verify that the quota is consistent with quotas for subtenants.
long totalSubtenants = CapacityUtils.totalSubtenantQuota(_dbClient, id);
if (totalSubtenants > quota_gb) {
throw APIException.badRequests
.invalidParameterTenantsQuotaExceedsSubtenants(quota_gb,
totalSubtenants);
}
// verify that the quota is consistentn with quotas for all projects.
long totalProjects = CapacityUtils.totalProjectQuota(_dbClient, id);
if (totalProjects > quota_gb) {
throw APIException.badRequests
.invalidParameterTenantsQuotaExceedsProject(quota_gb,
totalProjects);
}
tenant.setQuota(quota_gb);
}
_dbClient.persistObject(tenant);
return getQuota(tenant);
}
/**
* Create schedule policy and persist into CoprHD DB.
*
* @param id the URN of a CoprHD Tenant/Subtenant
* @param param schedule policy parameters
* @brief Create schedule policy
* @return No data returned in response body
* @throws BadRequestException
*/
@POST
@Path("/{id}/schedule-policies")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
public SchedulePolicyResp createSchedulePolicy(@PathParam("id") URI id, PolicyParam param) {
SchedulePolicyResp schedulePolicyResp = createPolicy(id, param);
auditOp(OperationTypeEnum.CREATE_SCHEDULE_POLICY, true, null, param.getPolicyName(),
id.toString(), schedulePolicyResp.getId().toString());
return schedulePolicyResp;
}
/**
* Worker method for create schedule policy. Allows external requests (REST) as well as
* internal requests that may not have a security context.
*
* @param id the URN of a CoprHD Tenant/Subtenant
* @param param schedule policy parameters
* @brief Create schedule policy
* @return No data returned in response body
* @throws BadRequestException
*/
public SchedulePolicyResp createPolicy(URI id, PolicyParam param) {
TenantOrg tenant = getTenantById(id, true);
// Make policy name as mandatory field
ArgValidator.checkFieldNotNull(param.getPolicyName(), "policyName");
// Check for duplicate policy name
if (param.getPolicyName() != null && !param.getPolicyName().isEmpty()) {
checkForDuplicateName(param.getPolicyName(), SchedulePolicy.class);
}
// check schedule policy type is valid or not
if (!ArgValidator.isValidEnum(param.getPolicyType(), SchedulePolicyType.class)) {
throw APIException.badRequests.invalidSchedulePolicyType(param.getPolicyType());
}
_log.info("Schedule policy creation started -- ");
SchedulePolicy schedulePolicy = new SchedulePolicy();
StringBuilder errorMsg = new StringBuilder();
// Validate Schedule policy parameters
boolean isValidSchedule = SchedulePolicyService.validateSchedulePolicyParam(param.getPolicySchedule(), schedulePolicy, errorMsg);
if (!isValidSchedule && errorMsg != null && errorMsg.length() > 0) {
_log.error("Failed to create schedule policy due to {} ", errorMsg.toString());
throw APIException.badRequests.invalidSchedulePolicyParam(param.getPolicyName(), errorMsg.toString());
}
// Validate snapshot expire parameters
boolean isValidSnapshotExpire = false;
if (param.getSnapshotExpire() != null) {
// check snapshot expire type is valid or not
String expireType = param.getSnapshotExpire().getExpireType();
if (!ArgValidator.isValidEnum(expireType, SnapshotExpireType.class)) {
_log.error(
"Invalid schedule snapshot expire type {}. Valid Snapshot expire types are hours, days, weeks, months and never",
expireType);
throw APIException.badRequests.invalidScheduleSnapshotExpireType(expireType);
}
isValidSnapshotExpire = SchedulePolicyService.validateSnapshotExpireParam(param.getSnapshotExpire());
if (!isValidSnapshotExpire) {
int expireTime = param.getSnapshotExpire().getExpireValue();
int minExpireTime = 2;
int maxExpireTime = 10;
_log.error("Invalid schedule snapshot expire time {}. Try an expire time between {} hours to {} years",
expireTime, minExpireTime, maxExpireTime);
throw APIException.badRequests.invalidScheduleSnapshotExpireValue(expireTime, minExpireTime, maxExpireTime);
}
} else {
if (param.getPolicyType().equalsIgnoreCase(SchedulePolicyType.file_snapshot.toString())) {
errorMsg.append("Required parameter snapshot_expire was missing or empty");
_log.error("Failed to create schedule policy due to {} ", errorMsg.toString());
throw APIException.badRequests.invalidSchedulePolicyParam(param.getPolicyName(), errorMsg.toString());
}
}
if (isValidSchedule) {
schedulePolicy.setId(URIUtil.createId(SchedulePolicy.class));
schedulePolicy.setPolicyType(param.getPolicyType());
schedulePolicy.setLabel(param.getPolicyName());
schedulePolicy.setPolicyName(param.getPolicyName());
schedulePolicy.setScheduleFrequency(param.getPolicySchedule().getScheduleFrequency().toLowerCase());
if (isValidSnapshotExpire) {
schedulePolicy.setSnapshotExpireType(param.getSnapshotExpire().getExpireType().toLowerCase());
if (!param.getSnapshotExpire().getExpireType().equalsIgnoreCase(SnapshotExpireType.NEVER.toString())) {
schedulePolicy.setSnapshotExpireTime((long) param.getSnapshotExpire().getExpireValue());
}
}
schedulePolicy.setTenantOrg(new NamedURI(tenant.getId(), schedulePolicy.getLabel()));
_dbClient.createObject(schedulePolicy);
_log.info("Schedule policy {} created successfully", schedulePolicy);
}
recordTenantEvent(OperationTypeEnum.CREATE_SCHEDULE_POLICY, tenant.getId(),
schedulePolicy.getId());
return new SchedulePolicyResp(schedulePolicy.getId(), toLink(ResourceTypeEnum.SCHEDULE_POLICY,
schedulePolicy.getId()), schedulePolicy.getLabel());
}
/**
* Gets the policyIds, policyNames and self links for all schedule policies.
*
* @param id the URN of a CoprHD Tenant/Subtenant
* @brief List schedule policies
* @return policyList - A SchedulePolicyList reference specifying the policyIds, name and self links for
* the schedule policies.
*/
@GET
@Path("/{id}/schedule-policies")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN, Role.PROJECT_ADMIN })
public SchedulePolicyList getSchedulePolicies(@PathParam("id") URI id) {
TenantOrg tenant = getTenantById(id, false);
StorageOSUser user = getUserFromContext();
NamedElementQueryResultList schedulePolicies = new NamedElementQueryResultList();
if (_permissionsHelper.userHasGivenRole(user, tenant.getId(),
Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN)) {
// list all schedule policies
_dbClient.queryByConstraint(ContainmentConstraint.Factory
.getTenantOrgSchedulePolicyConstraint(tenant.getId()), schedulePolicies);
} else {
// list only schedule policies that the user has access to
if (!id.equals(URI.create(user.getTenantId()))) {
throw APIException.forbidden.insufficientPermissionsForUser(user
.getName());
}
Map<URI, Set<String>> allMySchedulePolicies =
_permissionsHelper.getAllPermissionsForUser(user, tenant.getId(),
null, false);
if (!allMySchedulePolicies.keySet().isEmpty()) {
List<SchedulePolicy> policyList =
_dbClient.queryObjectField(SchedulePolicy.class, "label",
new ArrayList<URI>(allMySchedulePolicies.keySet()));
List<NamedElementQueryResultList.NamedElement> elements =
new ArrayList<NamedElementQueryResultList.NamedElement>(
policyList.size());
for (SchedulePolicy policy : policyList) {
elements.add(NamedElementQueryResultList.NamedElement.createElement(
policy.getId(), policy.getLabel()));
}
schedulePolicies.setResult(elements.iterator());
} else {
// empty list
schedulePolicies.setResult(new ArrayList<NamedElementQueryResultList.NamedElement>()
.iterator());
}
}
SchedulePolicyList policyList = new SchedulePolicyList();
for (NamedElementQueryResultList.NamedElement el : schedulePolicies) {
policyList.getSchdulePolicies().add(
toNamedRelatedResource(ResourceTypeEnum.SCHEDULE_POLICY, el.getId(), el.getName()));
}
return policyList;
}
private QuotaInfo getQuota(TenantOrg tenant) {
QuotaInfo quotaInfo = new QuotaInfo();
double capacity =
CapacityUtils.getTenantCapacity(_dbClient, tenant.getId());
quotaInfo.setQuotaInGb(tenant.getQuota());
quotaInfo.setEnabled(tenant.getQuotaEnabled());
quotaInfo.setCurrentCapacityInGb((long) Math.ceil(capacity / CapacityUtils.GB));
quotaInfo.setLimitedResource(DbObjectMapper.toNamedRelatedResource(tenant));
return quotaInfo;
}
public void recordOperation(OperationTypeEnum opType, URI srctenantURI,
TenantOrg tgttenant) {
recordTenantEvent(opType, srctenantURI, tgttenant.getId());
switch (opType) {
case CREATE_TENANT:
auditOp(opType, true, null, tgttenant.getLabel(), srctenantURI, tgttenant
.getId().toString());
break;
case UPDATE_TENANT:
auditOp(opType, true, null, srctenantURI.toString(), tgttenant.getLabel());
break;
case DELETE_TENANT:
auditOp(opType, true, null, tgttenant.getId().toString(),
tgttenant.getLabel());
break;
case REASSIGN_TENANT_ROLES:
auditOp(opType, true, null, tgttenant.getId().toString(),
tgttenant.getLabel());
break;
default:
_log.error("unrecognized tenant operation type");
}
}
private void recordTenantResourceOperation(OperationTypeEnum opType, URI tenantUri,
TenantResource tenantResource) {
// TODO - Is an event required?
auditOp(opType, true, null, tenantResource.auditParameters());
}
@SuppressWarnings("unchecked")
@Override
public Class<TenantOrg> getResourceClass() {
return TenantOrg.class;
}
@Override
public TenantOrgBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<TenantOrg> _dbIterator =
_dbClient.queryIterativeObjects(getResourceClass(), ids);
return new TenantOrgBulkRep(BulkList.wrapping(_dbIterator,
MapTenant.getInstance()));
}
@Override
protected TenantOrgBulkRep queryFilteredBulkResourceReps(List<URI> ids) {
Iterator<TenantOrg> _dbIterator =
_dbClient.queryIterativeObjects(getResourceClass(), ids);
BulkList.ResourceFilter filter =
new BulkList.TenantFilter(getUserFromContext(), _permissionsHelper);
return new TenantOrgBulkRep(BulkList.wrapping(_dbIterator,
MapTenant.getInstance(), filter));
}
/**
* Tenant is not a zone level resource
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.TENANT;
}
public static class TenantResRepFilter<E extends RelatedResourceRep> extends
ResRepFilter<E> {
public TenantResRepFilter(StorageOSUser user, PermissionsHelper permissionsHelper) {
super(user, permissionsHelper);
}
@Override
public boolean isAccessible(E resrep) {
return isTenantAccessible(resrep.getId());
}
}
/**
* Get object specific permissions filter
*/
@Override
public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(
StorageOSUser user, PermissionsHelper permissionsHelper) {
return new TenantResRepFilter(user, permissionsHelper);
}
/**
* convert a tenants role assignments into a rest response
*
* @param tenant
* @return
*/
private RoleAssignments getRoleAssignmentsResponse(TenantOrg tenant) {
RoleAssignments roleAssignmentsResponse = new RoleAssignments();
roleAssignmentsResponse.setAssignments(_permissionsHelper
.convertToRoleAssignments(tenant.getRoleAssignments(), false));
roleAssignmentsResponse.setSelfLink(getCurrentSelfLink());
return roleAssignmentsResponse;
}
/**
* Clear any tenant USE ACLs associated with the provided tenant id from the indicated CF
*
* @param clazz CF type to clear of tenant ACLs
* @param tenantId the tenant id
* @param specifier optional specifier (e.g. block or file for VirtualPools)
*/
private void clearTenantACLs(Class<? extends DataObjectWithACLs> clazz, URI tenantId, String specifier) {
PermissionsKey permissionKey;
if (StringUtils.isNotBlank(specifier)) {
permissionKey = new PermissionsKey(PermissionsKey.Type.TENANT, tenantId.toString(), specifier);
} else {
permissionKey = new PermissionsKey(PermissionsKey.Type.TENANT, tenantId.toString());
}
URIQueryResultList resultURIs = new URIQueryResultList();
Constraint aclConstraint = ContainmentPermissionsConstraint.Factory.getObjsWithPermissionsConstraint(
permissionKey.toString(), clazz);
_dbClient.queryByConstraint(aclConstraint, resultURIs);
List<URI> ids = new ArrayList<URI>();
for (URI result : resultURIs) {
ids.add(result);
}
Iterator<? extends DataObjectWithACLs> objectIter = _dbClient.queryIterativeObjects(clazz, ids);
if ((objectIter != null) && (objectIter.hasNext())) {
List<DataObjectWithACLs> objectList = new ArrayList<DataObjectWithACLs>();
while (objectIter.hasNext()) {
objectList.add(objectIter.next());
}
for (DataObjectWithACLs object : objectList) {
_log.info("Removing USE ACL for deleted subtenant {} from object {}", tenantId, object.getId());
object.removeAcl(permissionKey.toString(), ACL.USE.toString());
}
_dbClient.updateAndReindexObject(objectList);
}
}
/**
* check if UserMapping is empty.
*
* @param param
* @return
*/
private boolean isUserMappingEmpty(TenantUpdateParam param) {
if (param == null || param.getUserMappingChanges() == null) {
return true;
}
List<UserMappingParam> list = param.getUserMappingChanges().getAdd();
if (list != null && !list.isEmpty()) {
return false;
}
list = param.getUserMappingChanges().getRemove();
if (list != null && !list.isEmpty()) {
return false;
}
return true;
}
/**
* Validate an attribute string
*
* @param userMapping
* attribute string to validate
* @return True if the attribute string is valid
*/
public boolean isValidMapping(UserMapping userMapping) {
if (CollectionUtils.isEmpty(userMapping.getGroups())) {
return true;
}
for (String group : userMapping.getGroups()) {
if (StringUtils.isBlank(group)) {
_log.warn("Invalid group in the user mapping groups list");
continue;
}
StorageOSPrincipal groupPrincipal = new StorageOSPrincipal();
groupPrincipal.setType(StorageOSPrincipal.Type.Group);
// First add the group with "@domain" suffix and after that
// if we find that the group is a userGroup reset the
// name to just the group name without "@domain" suffix.
groupPrincipal.setName(group + "@" + userMapping.getDomain());
List<UserGroup> userGroupList = _permissionsHelper.getAllUserGroupByLabel(group);
if (!CollectionUtils.isEmpty(userGroupList)) {
for (UserGroup userGroup : userGroupList) {
// If the returned userGroup's domain matches the userMapping
// domain, reset the groupPrincipal name to just group (without domain) name
// in order to treat the group as UserGroup.
if (userGroup != null &&
userGroup.getDomain().equalsIgnoreCase(userMapping.getDomain())) {
_log.debug("Group {} is considered as user group", group);
groupPrincipal.setName(group);
}
}
}
if (!Validator.isValidPrincipal(groupPrincipal, null)) {
return false;
}
}
return true;
}
/**
* make sure user-mapping's attributes are valid. all keys and values are not empty.
*
* @param params
*/
private void checkUserMappingAttribute(List<UserMappingParam> params) {
if (params == null) {
return;
}
for (UserMappingParam param : params) {
List<UserMappingAttributeParam> attrs = param.getAttributes();
if (attrs != null) {
for (UserMappingAttributeParam attr : attrs) {
if (StringUtils.isEmpty(attr.getKey()) || CollectionUtils.isEmpty(attr.getValues())) {
throw BadRequestException.badRequests.userMappingAttributeIsEmpty();
}
for (String value : attr.getValues()) {
if (StringUtils.isEmpty(value)) {
throw BadRequestException.badRequests.userMappingAttributeIsEmpty();
}
}
}
}
}
}
/**
* SecurityAdmin has permission to create subtenant, or change a tenant's user mapping.
*
* before persisting user-mapping change to database, this check makes sure current user, if he is SecurityAdmin,
* will not be mapped out of Provider Tenant, and lose its security admin role.
*
* @param tenant tenant, which is about to be created or modified
*/
private void mapOutProviderTenantCheck(TenantOrg tenant) {
// if the user don't have SecurityAdmin role, skip further check
if (!_permissionsHelper.userHasGivenRole(
(StorageOSUser) sc.getUserPrincipal(),
null, Role.SECURITY_ADMIN)) {
return;
}
String user = getUserFromContext().getName();
UserInfoPage.UserTenantList userAndTenants = Validator.getUserTenants(user, tenant);
if (null != userAndTenants) {
List<UserInfoPage.UserTenant> userTenants = userAndTenants._userTenantList;
if (CollectionUtils.isEmpty(userTenants)) {
_log.error("User {} will not match any tenant after this user mapping change", user);
throw APIException.badRequests.userMappingNotAllowed(user);
} else if (userTenants.size() > 1) {
_log.error("User {} will map to multiple tenants {} after this user mapping change", user, userTenants.toArray());
throw APIException.badRequests.userMappingNotAllowed(user);
} else {
String tenantUri = userTenants.get(0)._id.toString();
String providerTenantId = _permissionsHelper.getRootTenant().getId().toString();
_log.debug("user will map to tenant: " + tenantUri);
_log.debug("provider tenant ID: " + providerTenantId);
if (!providerTenantId.equalsIgnoreCase(tenantUri)) {
_log.error("User {} will map to tenant {}, which is not provider tenant", user, tenant.getLabel());
throw APIException.badRequests.userMappingNotAllowed(user);
}
}
}
}
}