/* * Copyright (c) 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.HostMapper.map; import static com.emc.storageos.api.mapper.TaskMapper.toTask; import java.net.URI; import java.util.UUID; import java.util.Set; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.Iterator; 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 com.emc.storageos.db.client.model.*; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.model.*; 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.host.vcenter.*; import com.emc.storageos.security.authorization.*; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.storageos.api.mapper.functions.MapVcenter; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.resource.utils.AsyncTaskExecutorIntf; import com.emc.storageos.api.service.impl.resource.utils.DiscoveredObjectTaskScheduler; import com.emc.storageos.api.service.impl.resource.utils.VCenterConnectionValidator; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.api.service.impl.response.ResRepFilter; import com.emc.storageos.computesystemcontroller.ComputeSystemController; import com.emc.storageos.computesystemcontroller.impl.ComputeSystemHelper; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.NamedElementQueryResultList; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.model.host.HostList; import com.emc.storageos.model.host.cluster.ClusterList; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.volumecontroller.AsyncTask; import com.emc.storageos.volumecontroller.ControllerException; import org.springframework.util.CollectionUtils; /** * A service that provides APIs for viewing, updating and deleting vcenters and their * data centers. * */ @DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN }, readAcls = { ACL.USE }, writeRoles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) @Path("/compute/vcenters") public class VcenterService extends TaskResourceService { // Logger protected final static Logger _log = LoggerFactory.getLogger(VcenterService.class); private static final String EVENT_SERVICE_TYPE = "vcenter"; public String getServiceType() { return EVENT_SERVICE_TYPE; } @Autowired private HostService _hostService; @Autowired private VcenterDataCenterService _vcenterDataCenterService; private static class DiscoverJobExec implements AsyncTaskExecutorIntf { private final ComputeSystemController _controller; DiscoverJobExec(ComputeSystemController controller) { _controller = controller; } @Override public void executeTasks(AsyncTask[] tasks) throws ControllerException { _controller.discover(tasks); } @Override public ResourceOperationTypeEnum getOperation() { return ResourceOperationTypeEnum.DISCOVER_VCENTER; } } /** * Updates one or more of the vCenter attributes. Discovery is initiated * after the vCenter is updated. * * @param id the URN of a ViPR vCenter * @param updateParam the parameter that has the attributes to be updated. * @prereq none * @brief Update vCenter * @return the vCenter discovery async task representation. */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) @Path("/{id}") public TaskResourceRep updateVcenter(@PathParam("id") URI id, VcenterUpdateParam updateParam, @QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection, @QueryParam("discover_vcenter") @DefaultValue("true") final Boolean discoverVcenter) { // update the host Vcenter vcenter = queryObject(Vcenter.class, id, true); validateVcenter(updateParam, vcenter, validateConnection); // check the user permissions for this tenant org verifyAuthorizedSystemAdminOrTenantOrgUser(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); populateVcenterData(vcenter, updateParam); validateVcenterLastDiscoveryJobStatus(vcenter); _dbClient.persistObject(vcenter); auditOp(OperationTypeEnum.UPDATE_VCENTER, true, null, vcenter.auditParameters()); if (discoverVcenter) { return doDiscoverVcenter(vcenter); } else { return createManualReadyTask(vcenter); } } /** * Discovers (refreshes) a vCenter. This is an asynchronous call. * * @param id The URI of the vCenter. * @prereq none * @brief Discover vCenter * @return TaskResourceRep (asynchronous call) */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/discover") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) public TaskResourceRep discoverVcenter(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Vcenter.class, "id"); Vcenter vcenter = queryObject(Vcenter.class, id, true); return doDiscoverVcenter(vcenter); } /** * Vcenter Discovery * * @param vcenter the Vcenter to be discovered. * provided, a new taskId is generated. * @return the task used to track the discovery job */ protected TaskResourceRep doDiscoverVcenter(Vcenter vcenter) { ComputeSystemController controller = getController(ComputeSystemController.class, "vcenter"); DiscoveredObjectTaskScheduler scheduler = new DiscoveredObjectTaskScheduler( _dbClient, new DiscoverJobExec(controller)); String taskId = UUID.randomUUID().toString(); ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>(1); tasks.add(new AsyncTask(Vcenter.class, vcenter.getId(), taskId)); TaskList taskList = scheduler.scheduleAsyncTasks(tasks); TaskResourceRep taskResourceRep = taskList.getTaskList().iterator().next(); updateTaskTenant(taskResourceRep); return taskResourceRep; } /** * Validates the create/update vCenter input data * * @param param the input parameter * @param vcenter the vcenter being updated in case of update operation. * This parameter must be null for create operations. */ protected void validateVcenterCommon(VcenterParam param, Vcenter vcenter, Boolean validateConnection) { if (vcenter == null || (param.findIpAddress() != null && !param.findIpAddress().equals(vcenter.getIpAddress()))) { checkDuplicateAltId(Vcenter.class, "ipAddress", param.findIpAddress(), "vcenter"); } if (vcenter == null || (param.getName() != null && !param.getName().equals(vcenter.getLabel()))) { checkDuplicateLabel(Vcenter.class, param.getName()); } validateVcenterCredentials(param, vcenter); if (validateConnection != null && validateConnection == true) { String errorMessage = VCenterConnectionValidator.isVCenterConnectionValid(param); if (StringUtils.isNotBlank(errorMessage)) { throw APIException.badRequests.invalidVCenterConnection(errorMessage); } } } /** * Shows the information for one vCenter server. * * @param id the URN of a ViPR vCenter * @prereq none * @brief Show vCenter * @return All non-null attributes of the vCenter. * @throws DatabaseException when a DB error occurs. */ @GET @Path("/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public VcenterRestRep getVcenter(@PathParam("id") URI id) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, false); // check the user permissions for this tenant org verifyAuthorizedSystemOrTenantOrgUser(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); return map(vcenter); } /** * List the hosts of a vCenter. * * @param id the URN of a ViPR vCenter * @prereq none * @brief List vCenter hosts * @return The list of hosts of the vCenter. * @throws DatabaseException when a DB error occurs. */ @GET @Path("/{id}/hosts") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public HostList getVcenterHosts(@PathParam("id") URI id) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, false); ArgValidator.checkEntity(vcenter, id, isIdEmbeddedInURL(id)); // check the user permissions for this tenant org verifyAuthorizedInTenantOrg(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); URI tenantId = URI.create(getUserFromContext().getTenantId()); List<NamedElementQueryResultList.NamedElement> vCentersDataCenters = filterTenantResourcesByTenant(tenantId, VcenterDataCenter.class, listChildren(id, VcenterDataCenter.class, DATAOBJECT_NAME_FIELD, "vcenter")); HostList list = new HostList(); Iterator<NamedElementQueryResultList.NamedElement> dataCentersIterator = vCentersDataCenters.iterator(); while (dataCentersIterator.hasNext()) { NamedElementQueryResultList.NamedElement dataCenterElement = dataCentersIterator.next(); list.getHosts().addAll(map(ResourceTypeEnum.HOST, listChildren(dataCenterElement.getId(), Host.class, DATAOBJECT_NAME_FIELD, "vcenterDataCenter"))); } return list; } /** * List the clusters in a vCenter * * @param id the URN of a ViPR vCenter * @prereq none * @brief List vCenter clusters * @return The list of clusters of the vCenter. * @throws DatabaseException when a DB error occurs. */ @GET @Path("/{id}/clusters") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public ClusterList getVcenterClusters(@PathParam("id") URI id) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, false); ArgValidator.checkEntity(vcenter, id, isIdEmbeddedInURL(id)); // check the user permissions for this tenant org verifyAuthorizedInTenantOrg(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); URI tenantId = URI.create(getUserFromContext().getTenantId()); List<NamedElementQueryResultList.NamedElement> vCentersDataCenters = filterTenantResourcesByTenant(tenantId, VcenterDataCenter.class, listChildren(id, VcenterDataCenter.class, DATAOBJECT_NAME_FIELD, "vcenter")); ClusterList list = new ClusterList(); Iterator<NamedElementQueryResultList.NamedElement> dataCentersIterator = vCentersDataCenters.iterator(); while (dataCentersIterator.hasNext()) { NamedElementQueryResultList.NamedElement dataCenterElement = dataCentersIterator.next(); list.getClusters().addAll(map(ResourceTypeEnum.CLUSTER, listChildren(dataCenterElement.getId(), Cluster.class, DATAOBJECT_NAME_FIELD, "vcenterDataCenter"))); } return list; } /** * Deactivates the vCenter, its vCenter data centers, clusters and hosts. * * @param id the URN of a ViPR vCenter to be deactivated * @prereq none * @brief Delete vCenter * @return OK if deactivation completed successfully * @throws DatabaseException when a DB error occurs */ @POST @Path("/{id}/deactivate") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) public TaskResourceRep deactivateVcenter(@PathParam("id") URI id, @DefaultValue("false") @QueryParam("detach-storage") boolean detachStorage) throws DatabaseException { if (ComputeSystemHelper.isVcenterInUse(_dbClient, id) && !detachStorage) { throw APIException.badRequests.resourceHasActiveReferences(Vcenter.class.getSimpleName(), id); } else { Vcenter vcenter = queryObject(Vcenter.class, id, true); // check the user permissions for this tenant org verifyAuthorizedSystemAdminOrTenantOrgUser(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); checkIfOtherTenantsUsingTheVcenter(vcenter); String taskId = UUID.randomUUID().toString(); Operation op = _dbClient.createTaskOpStatus(Vcenter.class, vcenter.getId(), taskId, ResourceOperationTypeEnum.DELETE_VCENTER); ComputeSystemController controller = getController(ComputeSystemController.class, null); controller.detachVcenterStorage(vcenter.getId(), true, taskId); auditOp(OperationTypeEnum.DELETE_VCENTER, true, null, vcenter.auditParameters()); TaskResourceRep taskResourceRep = toTask(vcenter, taskId, op); updateTaskTenant(taskResourceRep); return taskResourceRep; } } /** * Detaches storage from the vcenter. * * @param id the URN of a ViPR vcenter * @brief Detach storage from vcenter * @return task * @throws DatabaseException when a DB error occurs */ @POST @Path("/{id}/detach-storage") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) public TaskResourceRep detachStorage(@PathParam("id") URI id) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, true); ArgValidator.checkEntity(vcenter, id, true); checkIfOtherTenantsUsingTheVcenter(vcenter); String taskId = UUID.randomUUID().toString(); Operation op = _dbClient.createTaskOpStatus(Vcenter.class, vcenter.getId(), taskId, ResourceOperationTypeEnum.DETACH_VCENTER_DATACENTER_STORAGE); ComputeSystemController controller = getController(ComputeSystemController.class, null); controller.detachVcenterStorage(vcenter.getId(), false, taskId); return toTask(vcenter, taskId, op); } /** * Creates a new vCenter data center. * * @param id the URN of the parent vCenter * @param createParam the details of the data center * @prereq none * @brief Create vCenter data center * @return the details of the vCenter data center, including its id and link, * when creation completes successfully. * @throws DatabaseException when a database error occurs. */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) @Path("/{id}/vcenter-data-centers") public VcenterDataCenterRestRep createVcenterDataCenter(@PathParam("id") URI id, VcenterDataCenterCreate createParam) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, false); checkDuplicateChildName(id, VcenterDataCenter.class, DATAOBJECT_NAME_FIELD, "vcenter", createParam.getName(), _dbClient); VcenterDataCenter datacenter = new VcenterDataCenter(); datacenter.setId(URIUtil.createId(VcenterDataCenter.class)); datacenter.setLabel(createParam.getName()); datacenter.setVcenter(id); if (vcenter.getCascadeTenancy()) { datacenter.setTenant(_permissionsHelper.getTenant(vcenter.getAcls())); } else { datacenter.setTenant(NullColumnValueGetter.getNullURI()); } _dbClient.createObject(datacenter); auditOp(OperationTypeEnum.CREATE_VCENTER_DATACENTER, true, null, datacenter.auditParameters()); return map(datacenter); } /** * List the vCenter data centers of the vCenter. * * @param id the URN of a ViPR vCenter * @param tid tenant to filter the vCenter data centers. * "No-Filter" or "null" indicates, listing all the vCenters in the system. * "Not-Assigned" indicates, list all the vCenters with no tenants assigned to it. * @prereq none * @brief List vCenter data centers * * @return All the list of vCenter data centers. * @throws DatabaseException when a DB error occurs. */ @GET @Path("/{id}/vcenter-data-centers") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public VcenterDataCenterList getVcenterDataCenters(@PathParam("id") URI id, @QueryParam("tenant") URI tid) throws DatabaseException { Vcenter vcenter = queryObject(Vcenter.class, id, false); ArgValidator.checkEntity(vcenter, id, isIdEmbeddedInURL(id)); // check the user permissions for this tenant org verifyAuthorizedSystemOrTenantOrgUser(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); URI tenantId; if (isSecurityAdmin() || isSystemAdmin()) { tenantId = tid; } else { if (shouldTenantAdminUseTenantParam(tid)){ tenantId = tid; } else { tenantId = URI.create(getUserFromContext().getTenantId()); } } _log.debug("Fetching the vCenterDataCenters for the tenant {}", tenantId); // get the vcenters VcenterDataCenterList list = new VcenterDataCenterList(); List<NamedElementQueryResultList.NamedElement> elements = listChildren(id, VcenterDataCenter.class, DATAOBJECT_NAME_FIELD, "vcenter"); //Filter the vCenterDataCenters based on the tenant. list.setDataCenters(map(ResourceTypeEnum.VCENTERDATACENTER, id, filterTenantResourcesByTenant(tenantId, VcenterDataCenter.class, elements))); return list; } /** * Creates a new instance of vCenter. Before creating the vCenter * it validates the create param and if the validation is successful * it creates the vCenter object. * * @param param the input parameter containing the vcenter attributes * @return an instance of {@link Vcenter} */ protected Vcenter createNewSystemVcenter(VcenterParam param, Boolean validateConnection) { validateSystemVcenter(param, null, validateConnection); Vcenter vcenter = new Vcenter(); vcenter.setId(URIUtil.createId(Vcenter.class)); //Always set the deprecated tenant field of a vCenter to null. vcenter.setTenant(NullColumnValueGetter.getNullURI()); populateVcenterData(vcenter, param); return vcenter; } /** * Populate an instance of vCenter with the provided vcenter parameter * * @param vcenter the vCenter to be populated * @param param the parameter that contains the attributes. */ protected void populateVcenterData(Vcenter vcenter, VcenterParam param) { vcenter.setLabel(param.getName()); vcenter.setOsVersion(param.getOsVersion()); vcenter.setUsername(param.getUserName()); vcenter.setPassword(param.getPassword()); vcenter.setIpAddress(param.findIpAddress()); vcenter.setPortNumber(param.getPortNumber()); vcenter.setUseSSL(param.getUseSsl()); vcenter.setCascadeTenancy(param.getCascadeTenancy()); } /** * List data of specified vCenters. * * @param param POST data containing the id list. * @prereq none * @brief List data of vCenters * @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 VcenterBulkRep getBulkResources(BulkIdParam param) { return (VcenterBulkRep) super.getBulkResources(param); } @Override protected DataObject queryResource(URI id) { return queryObject(Vcenter.class, id, false); } @Override protected URI getTenantOwner(URI id) { Vcenter vcenter = queryObject(Vcenter.class, id, false); return vcenter.getTenant(); } @SuppressWarnings("unchecked") @Override public Class<Vcenter> getResourceClass() { return Vcenter.class; } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.VCENTER; } @Override public VcenterBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<Vcenter> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); return new VcenterBulkRep(BulkList.wrapping(_dbIterator, MapVcenter.getInstance())); } @Override public VcenterBulkRep queryFilteredBulkResourceReps(List<URI> ids) { Iterator<Vcenter> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); BulkList.ResourceFilter filter = new BulkList.VcenterFilter(getUserFromContext(), _permissionsHelper); return new VcenterBulkRep(BulkList.wrapping(_dbIterator, MapVcenter.getInstance(), filter)); } @Override protected boolean isZoneLevelResource() { return false; } @Override protected boolean isSysAdminReadableResource() { return true; } public static class VcenterResRepFilter<E extends RelatedResourceRep> extends ResRepFilter<E> { public VcenterResRepFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { super(user, permissionsHelper); } @Override public boolean isAccessible(E resrep) { boolean ret = false; URI id = resrep.getId(); Vcenter obj = _permissionsHelper.getObjectById(id, Vcenter.class); if (obj == null) { return false; } if (obj.getTenant().toString().equals(_user.getTenantId()) || isSystemAdmin() || isSecurityAdmin()) { return true; } ret = isTenantAccessible(obj.getTenant()); return ret; } } /** * Get object specific permissions filter */ @Override public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { return new VcenterResRepFilter(user, permissionsHelper); } /** * Creates a new vCenter. Discovery is initiated after the vCenter is created. * * @brief Creates a vCenter. * @param createParam the parameter that has the attributes of the vCenter * to be created. * @param validateConnection specifies if the connection to the vCenter to be * validated before creating the vCenter or not. * Default value is "false", so connection to the * vCenter will not be validated if it is not specified. * @return the vCenter discovery async task. */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN }) public TaskResourceRep createVcenter(VcenterCreateParam createParam, @QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection, @QueryParam("discover_vcenter") @DefaultValue("true") final Boolean discoverVcenter) { Vcenter vcenter = createVcenter(createParam, validateConnection); vcenter.setRegistrationStatus(DiscoveredDataObject.RegistrationStatus.REGISTERED.toString()); _dbClient.createObject(vcenter); auditOp(OperationTypeEnum.CREATE_VCENTER, true, null, vcenter.auditParameters()); if (discoverVcenter) { return doDiscoverVcenter(queryObject(Vcenter.class, vcenter.getId(), true)); } else { return createManualReadyTask(vcenter); } } /** * Lists the id and name of all the vCenters that belong to the given * tenant organization if the requesting user is a SysAdmin or * SecAdmin. If the requested user is a TenantAdmin and user is * a tenant of the "tenant" given in the query param then all the * vCenters of the tenant query param will be returned otherwise * returned only the vCenters that the user's tenant shares. * * @brief Lists vCenters. * @param tid Tenant to filter the vCenters. * "No-Filter" or "null" indicates, listing all the vCenters in the system. * "Not-Assigned" indicates, list all the vCenters with no tenants assigned to it. * * @return a list of vCenters that belong to the tenant organization. * @throws DatabaseException when a DB error occurs */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public VcenterList listVcenters(@QueryParam("tenant") final URI tid) throws DatabaseException { VcenterList list = new VcenterList(); List<Vcenter> vcenters = null; if (isSecurityAdmin() || isSystemAdmin()) { _log.debug("Fetching vCenters for {}", tid); if ( NullColumnValueGetter.isNullURI(tid)|| Vcenter.NO_TENANT_SELECTOR.equalsIgnoreCase(tid.toString())) { vcenters = getDataObjects(Vcenter.class); list.setVcenters(map(ResourceTypeEnum.VCENTER, getNamedElementsList(Vcenter.class, DATAOBJECT_NAME_FIELD, vcenters))); } else if (Vcenter.TENANT_SELECTOR_FOR_UNASSIGNED.equalsIgnoreCase(tid.toString())) { vcenters = getDataObjects(Vcenter.class); list.setVcenters(map(ResourceTypeEnum.VCENTER, getNamedElementsWithNoAcls(Vcenter.class, DATAOBJECT_NAME_FIELD, vcenters))); } else { ArgValidator.checkEntity(_dbClient.queryObject(tid), tid, isIdEmbeddedInURL(tid)); list.setVcenters(map(ResourceTypeEnum.VCENTER, listChildrenWithAcls(tid, Vcenter.class, DATAOBJECT_NAME_FIELD))); } return list; } vcenters = getDataObjects(Vcenter.class); if (!CollectionUtils.isEmpty(vcenters)) { List<Vcenter> tenantVcenterList = null; if (shouldTenantAdminUseTenantParam(tid)) { //If the tenant admin can use the tid query param, then the filtering should //happen based on the tenant query param. tenantVcenterList = filterVcentersByTenant(vcenters, tid); } else { //Get the vCenters based on the User's tenant org. If the user is not a tenant admin, insufficient //permission exception will be thrown. tenantVcenterList = filterVcentersByTenant(vcenters, NullColumnValueGetter.getNullURI()); } list.setVcenters(map(ResourceTypeEnum.VCENTER, getNamedElementsList(Vcenter.class, DATAOBJECT_NAME_FIELD, tenantVcenterList))); } return list; } /** * Get vCenter Access Control List (ACLs) * * @brief Shows vCenter Access Control List * @param id the URN of a ViPR vCenter * @prereq none * * @return Access Control List Assignment details */ @GET @Path("/{id}/acl") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public ACLAssignments getAclAssignments(@PathParam("id") URI id) { return getAclAssignmentsResponse(id); } /** * Add or remove individual Access Control List entry(s). When the vCenter is created * with no shared access (Vcenter.shared = Boolean.FALSE), there cannot * be multiple Access Control List Entries associated with this vCenter. * * @brief Updates vCenter Access Control List * @param changes Access Control List assignment changes. Request body must include * at least one add or remove operation * @param id the URN of a ViPR Project. * * @return the vCenter discovery async task. */ @PUT @Path("/{id}/acl") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN }) public TaskResourceRep updateAclAssignments(@PathParam("id") URI id, ACLAssignmentChanges changes) { //Make sure the vCenter is a valid one. Vcenter vcenter = queryObject(Vcenter.class, id, true); ArgValidator.checkEntity(vcenter, id, isIdEmbeddedInURL(id)); //Validate the acl assignment changes. It is not valid when an //acl entry contains more than one privilege or privileges //other than USE. validateAclAssignments(changes); //Make sure that the vCenter with respect to the tenants //that we are removing is not in use (means the datacenters //and its clusters and hosts with the removing tenant do not //have any exports). checkVcenterUsage(vcenter, changes); validateVcenterLastDiscoveryJobStatus(vcenter); _permissionsHelper.updateACLs(vcenter, changes, new PermissionsHelper.UsageACLFilter(_permissionsHelper)); verifyVcenterCascadeTenancy(vcenter); _dbClient.updateAndReindexObject(vcenter); auditOp(OperationTypeEnum.UPDATE_VCENTER, true, null, vcenter.getId() .toString(), vcenter.getLabel(), changes); //Rediscover the vCenter, this will update the updated //list of tenants based its latest acls to its datacenters //and hosts and clusters. return doDiscoverVcenter(queryObject(Vcenter.class, vcenter.getId(), true)); } /** * Filters the vCenters by the tenant. If the provided tenant is * null or the tenant does not share the vCenter than the vCenters * are filtered with the user's tenant. * * @param vcenters to be filtered by the tenant. * @param tenantId to be used for filtering the vCenter. * @return the list of vCenters that belong to the tenantId or the user's tenant org. */ private List<Vcenter> filterVcentersByTenant(List<Vcenter> vcenters, URI tenantId) { List<Vcenter> tenantVcenterList = new ArrayList<Vcenter>(); Iterator<Vcenter> vcenterIt = vcenters.iterator(); while (vcenterIt.hasNext()) { Vcenter vcenter = vcenterIt.next(); if (vcenter == null) { continue; } Set<URI> tenantUris = _permissionsHelper.getUsageURIsFromAcls(vcenter.getAcls()); if (CollectionUtils.isEmpty(tenantUris)) { continue; } if (!NullColumnValueGetter.isNullURI(tenantId) && !tenantUris.contains(tenantId)) { //The tenantId is not a null URI and it is not available in the vCenter acls, //so, dont add to the filtered list. continue; } Iterator<URI> tenantUriIt = tenantUris.iterator(); while (tenantUriIt.hasNext()) { if(verifyAuthorizedInTenantOrg(tenantUriIt.next())) { tenantVcenterList.add(vcenter); } } } return tenantVcenterList; } /** * Adds the tenant to the vCenter acls if the tenant admin is * creating it. This always sets the vCenter tenant (the old * deprecated filed to null). * * @param tenant a valid tenant org if the tenant admin is * creating it. * @param vcenter the vCenter being created. */ private void addVcenterAclIfTenantAdmin(TenantOrg tenant, Vcenter vcenter) { //Always set the deprecated tenant field of a vCenter to null. vcenter.setTenant(NullColumnValueGetter.getNullURI()); URI tenantId; if (tenant != null) { tenantId = tenant.getId(); } else { //If the tenant org is not valid, try to use the //user's tenant org. tenantId = URI.create(getUserFromContext().getTenantId()); } //If the User is an admin in the tenant org, allow the //operation otherwise, report the insufficient permission //exception. if (_permissionsHelper.userHasGivenRole(getUserFromContext(), tenantId, Role.TENANT_ADMIN)) { //Generate the acl entry and add to the vCenters acls. String aclKey = _permissionsHelper.getTenantUsePermissionKey(tenantId.toString()); vcenter.addAcl(aclKey, ACL.USE.name()); _log.debug("Adding {} to the vCenter {} acls", aclKey, vcenter.getLabel()); } } /** * Check if the vCenter being updated is used by any of its vCenterDataCenters * or clusters or hosts or not. This validates only with respect to the tenant * that is being removed from the vCenter acls. If the tenant that is getting * removed teh vCenter has any exports with the vCenter's vCenterDataCenter or * its clusters or hosts. * * @param vcenter the vCenter being updated. * @param changes new acl assignment changes for the vCenter. */ private void checkVcenterUsage(Vcenter vcenter, ACLAssignmentChanges changes) { //Make a copy of the vCenter's existing tenant list. List<ACLEntry> existingAclEntries = _permissionsHelper.convertToACLEntries(vcenter.getAcls()); if (CollectionUtils.isEmpty(existingAclEntries)) { //If there no existing acl entries for the vCenter //there is nothing to validate if it is in user or not. _log.debug("vCenter {} does not have any existing acls", vcenter.getLabel()); return; } //If there are no tenants to be removed from the vCenter acls, //there is nothing to check for usage. if (CollectionUtils.isEmpty(changes.getRemove())) { _log.debug("There are not acls to remove from vCenter {}", vcenter.getLabel()); return; } Set<String> tenantsInUse = new HashSet<String>(); Set<URI> removingTenants = _permissionsHelper.getUsageURIsFromAclEntries(changes.getRemove()); Set<URI> existingTenants = _permissionsHelper.getUsageURIsFromAclEntries(existingAclEntries); Iterator<URI> removingTenantsIterator = removingTenants.iterator(); while (removingTenantsIterator.hasNext()) { URI removingTenant = removingTenantsIterator.next(); if (!existingTenants.contains(removingTenant)) { continue; } //Check if vCenter is in use for the removing tenant or not. //This checks for all the datacenters of this vcenter that belong to the //removing tenant and finds if the datacenter or it clusters or hosts //use the exports from the removing tenant or not. if (ComputeSystemHelper.isVcenterInUseForTheTenant(_dbClient, vcenter.getId(), removingTenant)) { TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, removingTenant); tenantsInUse.add(tenant.getLabel()); } } if(!CollectionUtils.isEmpty(tenantsInUse)) { throw APIException.badRequests.cannotRemoveTenant("vCener", vcenter.getLabel(), tenantsInUse); } } /** * Validates the acl assignment changes. * It is not valid acl assignment change, when an * acl entry contains more than one privilege or privileges * other than USE if the tenant provided in the acl entry * is not a valid tenant org. * * @param changes acl assignment changes to validated. */ private void validateAclAssignments(ACLAssignmentChanges changes) { if(changes == null) { throw APIException.badRequests.requiredParameterMissingOrEmpty("ACLAssignmentChanges"); } //Make sure at least one acl entry either in the add or remove //list. if (CollectionUtils.isEmpty(changes.getAdd()) && CollectionUtils.isEmpty(changes.getRemove())) { throw APIException.badRequests.requiredParameterMissingOrEmpty("ACLAssignmentChanges"); } validateAclEntries(changes.getAdd()); validateAclEntries(changes.getRemove()); } /** * Validates the individual list of acl entries. * It is not valid acl entries list, when an * acl entry contains more than one privilege or privileges * other than USE and if the tenant provided in the acl entry * is not a valid tenant org. * * @param aclEntries acl entries to be validated. */ private void validateAclEntries(List<ACLEntry> aclEntries) { if (CollectionUtils.isEmpty(aclEntries)) { return; } Iterator<ACLEntry> aclEntryIterator = aclEntries.iterator(); while (aclEntryIterator.hasNext()) { ACLEntry aclEntry = aclEntryIterator.next(); //If more than one privileges provided the ACL Entry, it is not supported //for vCenter ACL. Only USE ACL can be provided. if (aclEntry.getAces().size() != 1) { throw APIException.badRequests.unsupportedNumberOfPrivileges(URI.create(aclEntry.getTenant()), aclEntry.getAces()); } if (!aclEntry.getAces().get(0).equalsIgnoreCase(ACL.USE.name())) { throw APIException.badRequests.unsupportedPrivilege(URI.create(aclEntry.getTenant()), aclEntry.getAces().get(0)); } //Validate if the provided tenant is a valid tenant or not. URI tenantId = URI.create(aclEntry.getTenant()); TenantOrg tenant = queryObject(TenantOrg.class, tenantId, true); ArgValidator.checkEntity(tenant, tenantId, isIdEmbeddedInURL(tenantId)); } } /** * Gets the current acl assignments of the requested vCenter. * * @param vcenterId * @return the list of acl assignments of the requested vCenter. */ private ACLAssignments getAclAssignmentsResponse(URI vcenterId) { Vcenter vcenter = queryObject(Vcenter.class, vcenterId, true); ArgValidator.checkEntity(vcenter, vcenterId, isIdEmbeddedInURL(vcenterId)); ACLAssignments response = new ACLAssignments(); response.setAssignments(_permissionsHelper.convertToACLEntries(vcenter.getAcls())); return response; } /** * Checks if the user is authorized to view the vCenter. * Authorized if, * The user has SysAdmin or SecAdmin role. * The user is a TenantAdmin of one of the that shares the vCenter. * * @param aclEntries the tenants list that shares the vCenter. */ protected void verifyAuthorizedSystemOrTenantOrgUser(List<ACLEntry> aclEntries) { if (isSystemAdmin() || isSecurityAdmin()) { return; } verifyAuthorizedInTenantOrg(aclEntries); } /** * Checks if the user is authorized to view the vCenter. * Authorized if, * The user has SysAdmin role. * The user is a TenantAdmin of one of the that shares the vCenter. * * @param aclEntries the tenants list that shares the vCenter. */ protected void verifyAuthorizedSystemAdminOrTenantOrgUser(List<ACLEntry> aclEntries) { if (isSystemAdmin()) { return; } verifyAuthorizedInTenantOrg(aclEntries); } /** * Checks if the user is authorized to view the vCenter. * Authorized if, * The user a TenantOrg user of one the tenant that shares the vCenter. * The user is a TenantAdmin of one of the tenant that shares the vCenter. * * @param aclEntries the tenants list that shares the vCenter. */ private void verifyAuthorizedInTenantOrg(List<ACLEntry> aclEntries) { boolean isUserAuthorized = false; StorageOSUser user = getUserFromContext(); Iterator<ACLEntry> aclEntriesIterator = aclEntries.iterator(); while (aclEntriesIterator.hasNext()) { ACLEntry aclEntry = aclEntriesIterator.next(); if (aclEntry == null) { continue; } if (user.getTenantId().toString().equals(aclEntry.getTenant()) || isSystemAdminOrMonitorUser() || _permissionsHelper.userHasGivenRole(user, URI.create(aclEntry.getTenant()), Role.TENANT_ADMIN)) { isUserAuthorized = true; break; } } if (!isUserAuthorized) { throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } } /** * Checks if the user is authorized in the tenant org or not. * Authorized if, * The user is in the tenant org. * The user who is not in the tenant org, but is a TenantAdmin of the tenant org. * * @param tenantId the tenants list that shares the vCenter. * @return true if the user is authorized for the tenant org, false otherwise. */ protected boolean verifyAuthorizedInTenantOrg(URI tenantId) { StorageOSUser user = getUserFromContext(); if (tenantId.toString().equals(user.getTenantId()) || _permissionsHelper.userHasGivenRole(user, tenantId, Role.TENANT_ADMIN)) { return true; } return false; } /** * Check if the other tenants using the vCenter before deleting it. * SysAdmin deleting the vCenter is always allowed whereas, if the * vCenter is shared with multiple tenants then it cannot be deleted by * the Tenant Admin. * * @param vcenter to be deleted. */ private void checkIfOtherTenantsUsingTheVcenter(Vcenter vcenter) { if (!isSystemAdmin() && !CollectionUtils.isEmpty(vcenter.getAcls()) && vcenter.getAcls().size() > 1) { throw APIException.forbidden.tenantAdminCannotDeleteVcenter(getUserFromContext().getName(), vcenter.getLabel()); } } /** * Creates a manual (fake) vcenter discover task, so that * there wont be any vcenter discovery happening because of * this task. * * @param vcenter vcenter to create its manual/fake discovery task. * * @return returns fake/manual vcenter discovery task. */ private TaskResourceRep createManualReadyTask(Vcenter vcenter) { // if not discoverable, manually create a ready task Operation op = new Operation(); op.setResourceType(ResourceOperationTypeEnum.DISCOVER_VCENTER); op.ready("Vcenter not discoverable."); String taskId = UUID.randomUUID().toString(); _dbClient.createTaskOpStatus(Host.class, vcenter.getId(), taskId, op); return toTask(vcenter, taskId, op); } /** * Updates the tenant information in the Task data object and * TaskResourceRep (the response object to the API request). * Both Task and TaskResourceRep is updated with the user's * tenant information if it they don't contain any tenant information * already. * * @param taskResourceRep api response to be updated. */ private void updateTaskTenant(TaskResourceRep taskResourceRep) { Task task = _dbClient.queryObject(Task.class, taskResourceRep.getId()); if (areEqual(task.getTenant(), NullColumnValueGetter.getNullURI())) { StorageOSUser user = getUserFromContext(); URI userTenantUri = URI.create(user.getTenantId()); task.setTenant(userTenantUri); RelatedResourceRep tenant = new RelatedResourceRep(); tenant.setId(userTenantUri); tenant.setLink(new RestLinkRep("self", URI.create("/tenants/" + userTenantUri.toString()))); taskResourceRep.setTenant(tenant); _dbClient.persistObject(task); List<String> traceParams = new ArrayList<String>(); traceParams.add(task.getId().toString()); traceParams.add(user.getName()); traceParams.add(user.getTenantId()); _log.info("Update the task {} with the user's {} tenant {}", traceParams); } } /** * Checks if the tenant admin can use the "tenant" query param while * listing the vCenter and vCenter data centers. * Tenant admin can list if the "tenant" query param is not equals to * "No-Filter" or "Not-Assigned" or the tenant admin is a tenant admin of * the tenant represented by the "tenant" query param. * * @param tid "tenant" query param to be validated if the tenant * admin can use that or not * * @return true if tenant admin can use the "tenant" query param to filter the * vCenters or vCenter data centers and false otherwise. */ private boolean shouldTenantAdminUseTenantParam (URI tid) { if (!NullColumnValueGetter.isNullURI(tid) && !TenantResource.TENANT_SELECTOR_FOR_UNASSIGNED.equalsIgnoreCase(tid.toString()) && !TenantResource.NO_TENANT_SELECTOR.equalsIgnoreCase(tid.toString()) && _permissionsHelper.userHasGivenRole(getUserFromContext(), tid, Role.TENANT_ADMIN)) { return true; } return false; } /** * Validates vCenter user credentials from create or update parameters. * * @param param either vCenter create or update param. * @param vcenter vCenter object. */ private void validateVcenterCredentials(VcenterParam param, Vcenter vcenter) { if (StringUtils.isBlank(param.getPassword()) && vcenter != null) { param.setPassword(StringUtils.trimToNull(vcenter.getPassword())); } if (StringUtils.isBlank(param.getUserName()) && vcenter != null) { param.setUserName(StringUtils.trimToNull(vcenter.getUsername())); } ArgValidator.checkFieldNotNull(param.getUserName(), "username"); ArgValidator.checkFieldNotNull(param.getPassword(), "password"); } /** * Verifies the number acls with vCenter's cascade tenancy. If the * vCenter is configured with cascade tenancy option and it contains * more than one acl is not allowed. * * @param vcenter to be validated against the cascade tenancy and * number of acls. */ private void verifyVcenterCascadeTenancy(Vcenter vcenter) { if (vcenter.getCascadeTenancy()) { Set<URI> uris = _permissionsHelper.getUsageURIsFromAcls(vcenter.getAcls()); if (uris.size() > 1) { throw APIException.badRequests.cannotShareVcenterWithMultipleTenants(vcenter.getLabel()); } } } /** * Gets the configured refresh interval for the compute discovery from the coordinator. * * @return the value of the compute discovery refresh interval. . */ private long getRefreshInterval() { long refreshInterval = 60; String prop = _coordinator.getPropertyInfo().getProperty("controller_cs_discovery_refresh_interval"); if (prop != null) { refreshInterval = Long.parseLong(prop); } return refreshInterval; } /** * Validates the vCenter's last discovery job. If discovery job is in progress or it * just completed within the configured refresh interval, dont allow to update * the vCenter acl. This is because, updating the vCenter acl completely depending * on the vCenter discovery job. If we try to run two vCenter discovery job with in * the configured refresh interval, the second job will not run at all. So, this will * cause vCenter acls may not be updated to its DataCenters, Clusters and Hosts. * * @param vcenter vCenter to find its last discovery status. */ private void validateVcenterLastDiscoveryJobStatus(Vcenter vcenter) { long tolerance = getRefreshInterval(); long lastDiscoveryTime = vcenter.getLastDiscoveryRunTime(); long currentSystemTime = System.currentTimeMillis(); if (DiscoveredDataObject.DataCollectionJobStatus.IN_PROGRESS.toString().equalsIgnoreCase(vcenter.getDiscoveryStatus()) || currentSystemTime - lastDiscoveryTime < tolerance * 1000) { throw APIException.badRequests.cannotEditVcenterOrUpdateACL(vcenter.getLabel(), tolerance); } } /** * Validates the vCenter update parameter. * * @param param vCenter update parameter to validate. * @param vcenter vCenter data object. * @param validateConnection flag to validate the vCenter connection. */ private void validateVcenter(VcenterUpdateParam param, Vcenter vcenter, Boolean validateConnection) { if (isSystemAdmin()) { validateSystemVcenter(param, vcenter, validateConnection); } else { validateTenantVcenter(param, vcenter, validateConnection); } } /** * Validates the create/update vCenter input data for System Admin. * * @param param the input parameter * @param vcenter the vCenter being updated in case of update operation. * This parameter must be null for create operations. */ protected void validateSystemVcenter(VcenterParam param, Vcenter vcenter, Boolean validateConnection) { validateVcenterCommon(param, vcenter, validateConnection); updateSystemVcenterCascadeTenancy(param, vcenter); } /** * Validates the create/update vCenter input data for Tenant Admin. * * @param param the input parameter * @param vcenter the vCenter being updated in case of update operation. * This parameter must be null for create operations. */ protected void validateTenantVcenter(VcenterParam param, Vcenter vcenter, Boolean validateConnection) { validateVcenterCommon(param, vcenter, validateConnection); updateTenantVcenterCascadeTenancy(param, vcenter); if (vcenter != null && vcenter.getCascadeTenancy().booleanValue() != param.getCascadeTenancy().booleanValue()) { throw APIException.forbidden.tenantAdminCannotModifyCascadeTenancy(getUserFromContext().getName(), vcenter.getLabel()); } } /** * Creates a new instance of vCenter that belongs to only one tenant. * Before creating the vCenter, it validates the crate param to make sure * all the input parameters are valid. * * @param tenant the vCenter parent tenant organization * @param param the input parameter containing the vCenter attributes * @return an instance of {@link Vcenter} */ protected Vcenter createNewTenantVcenter(TenantOrg tenant, VcenterParam param, Boolean validateConnection) { validateTenantVcenter(param, null, validateConnection); Vcenter vcenter = new Vcenter(); vcenter.setId(URIUtil.createId(Vcenter.class)); addVcenterAclIfTenantAdmin(tenant, vcenter); populateVcenterData(vcenter, param); return vcenter; } /** * Update the cascade tenancy to the vCenter create or update param. * While creating the vCenter by default, the cascade tenancy of the vCenter * is set to false. * While editing the vCenter, if the cascade tenancy is not provided * use the existing one from the vCenter. * * @param param vCenter create or update param. * @param vcenter vCenter to be updated, null while creation. */ private void updateSystemVcenterCascadeTenancy(VcenterParam param, Vcenter vcenter) { if (vcenter == null ) { //Since this is SysAdmin creating the vCenter, if cascade tenancy option //was not provided assume no cascade tenancy is required. if (param.getCascadeTenancy() == null) { param.setCascadeTenancy(Boolean.FALSE); } } if (vcenter != null && param.getCascadeTenancy() == null) { param.setCascadeTenancy(vcenter.getCascadeTenancy()); } } /** * Update the cascade tenancy to the vCenter create or update param. * While creating the vCenter by default, the cascade tenancy of the vCenter * is set to true. * While editing the vCenter, if the cascade tenancy is not provided * use the existing one from the vCenter. * * @param param vCenter create or update param. * @param vcenter vCenter to be updated, null while creation. */ private void updateTenantVcenterCascadeTenancy(VcenterParam param, Vcenter vcenter) { if (vcenter == null ) { //Since this is Tenant creating the vCenter, if cascade tenancy option //was not provided set the cascade tenancy to true. if (param.getCascadeTenancy() == null) { param.setCascadeTenancy(Boolean.TRUE); } } if (vcenter != null && param.getCascadeTenancy() == null) { param.setCascadeTenancy(vcenter.getCascadeTenancy()); } } /** * Creates the vCenter based on the User roles. * If the User is a System Admin user, the system level vCenter is created. * If the User is a Tenant Admin user, the tenant level vCenter is created. * * @param createParam param to create the vCenter. * @param validateConnection flag to validate the vCenter connection before creating it. * * @return vCenter data object. */ private Vcenter createVcenter(VcenterCreateParam createParam, Boolean validateConnection) { Vcenter vcenter; if (isSystemAdmin()) { // validates the create param and validation is successful then creates and persist the vcenter vcenter = createNewSystemVcenter(createParam, validateConnection); } else { TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, URI.create(getUserFromContext().getTenantId())); vcenter = createNewTenantVcenter(tenant, createParam, validateConnection); } return vcenter; } }