/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; 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.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.storageos.api.mapper.HostMapper; import com.emc.storageos.api.mapper.functions.MapInitiator; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.resource.utils.ExportUtils; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.api.service.impl.response.FilterIterator; import com.emc.storageos.api.service.impl.response.ResRepFilter; import com.emc.storageos.api.service.impl.response.SearchedResRepList; import com.emc.storageos.computesystemcontroller.ComputeSystemController; import com.emc.storageos.computesystemcontroller.impl.ComputeSystemHelper; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.model.Cluster; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.Host; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.util.EndpointUtility; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.model.BulkIdParam; import com.emc.storageos.model.RelatedResourceRep; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.export.ITLRestRepList; import com.emc.storageos.model.host.InitiatorAliasRestRep; import com.emc.storageos.model.host.InitiatorAliasSetParam; import com.emc.storageos.model.host.InitiatorBulkRep; import com.emc.storageos.model.host.InitiatorRestRep; import com.emc.storageos.model.host.InitiatorUpdateParam; import com.emc.storageos.model.search.SearchResultResourceRep; import com.emc.storageos.model.search.SearchResults; import com.emc.storageos.model.valid.Endpoint.EndpointType; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.CheckPermission; import com.emc.storageos.security.authorization.DefaultPermissions; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.volumecontroller.BlockController; import com.emc.storageos.volumecontroller.impl.smis.vmax.VmaxExportOperations; import com.emc.storageos.vplexcontroller.VPlexController; /** * Service providing APIs for host initiators. */ @Path("/compute/initiators") @DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN }, writeRoles = { Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }) public class InitiatorService extends TaskResourceService { // Logger protected final static Logger _log = LoggerFactory.getLogger(InitiatorService.class); private static final String EVENT_SERVICE_TYPE = "initiator"; private static final String ALIAS = "Alias-Operations"; private static final String EMPTY_INITIATOR_ALIAS = "/"; private static final int ALIAS_MAX_LIMIT = 32; private static final String ALIAS_ILLEGAL_CHARACTERS = ".*[@#^:?%&+=(){}*].*"; @Override public String getServiceType() { return EVENT_SERVICE_TYPE; } @Autowired private HostService _hostService; /** * List the exports of the initiator. * * @prereq none * @brief List host initiator exports * @return A list of ITLs (Initiator/Target/Lun) for this initiator. * * @throws DatabaseException When an error occurs querying the database. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/exports") public ITLRestRepList getInitiatorExports(@PathParam("id") URI id) { Initiator initiator = queryObject(Initiator.class, id, false); // get the initiators' ITLs - permissions are also checked in ExportUtils return ExportUtils.getItlsForInitiator(initiator, _dbClient, _permissionsHelper, getUserFromContext()); } /** * Show the information for an initiator. * * @param id the URN of a ViPR initiator * * @prereq none * @brief Show host initiator * @return A reference to an InitiatorRestRep specifying the initiator data. * * @throws DatabaseException When an error occurs querying the database. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") public InitiatorRestRep getInitiator(@PathParam("id") URI id) { Initiator initiator = queryResource(id); // check the user has permissions verifyUserPermisions(initiator); return HostMapper.map(initiator); } /** * Update a host initiator. * * @param id the URN of a ViPR initiator * @param updateParam the parameter containing the new attributes * @prereq none * @brief Update initiator. * @return the details of the updated host initiator. * @throws DatabaseException when a DB error occurs */ @PUT @Path("/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) public InitiatorRestRep updateInitiator(@PathParam("id") URI id, InitiatorUpdateParam updateParam) throws DatabaseException { Initiator initiator = queryObject(Initiator.class, id, true); _hostService.validateInitiatorData(updateParam, initiator); _hostService.populateInitiator(initiator, updateParam); _dbClient.persistObject(initiator); auditOp(OperationTypeEnum.UPDATE_HOST_INITIATOR, true, null, initiator.auditParameters()); return map(queryObject(Initiator.class, id, false)); } /** * Deactivate an initiator. * * @param id the URN of a ViPR initiator * @prereq The initiator must not have active exports * @brief Delete host initiator * @return A Response indicating success or failure. * * @throws DatabaseException When an error occurs querying the database. */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deactivate") @CheckPermission(roles = { Role.TENANT_ADMIN }) public TaskResourceRep deleteInitiator(@PathParam("id") URI id) { _log.info("Delete initiator {}", id); Initiator initiator = queryObject(Initiator.class, id, isIdEmbeddedInURL(id)); if (!initiator.getIsManualCreation()) { throw APIException.badRequests.initiatorNotCreatedManuallyAndCannotBeDeleted(); } ArgValidator.checkReference(Initiator.class, id, checkForDelete(initiator)); String taskId = UUID.randomUUID().toString(); Operation op = _dbClient.createTaskOpStatus(Initiator.class, initiator.getId(), taskId, ResourceOperationTypeEnum.DELETE_INITIATOR); if (ComputeSystemHelper.isInitiatorInUse(_dbClient, id.toString())) { ComputeSystemController controller = getController(ComputeSystemController.class, null); controller.removeInitiatorFromExport(initiator.getHost(), initiator.getId(), taskId); } else { _dbClient.ready(Initiator.class, initiator.getId(), taskId); _dbClient.markForDeletion(initiator); } auditOp(OperationTypeEnum.DELETE_HOST_INITIATOR, true, null, initiator.auditParameters()); return toTask(initiator, taskId, op); } /** * List data of specified initiators. * * @param param POST data containing the id list. * @prereq none * @brief List data of specified initiators * @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 InitiatorBulkRep getBulkResources(BulkIdParam param) { return (InitiatorBulkRep) super.getBulkResources(param); } /** * Allows the user to deregister a registered initiator so that it is no * longer used by the system. This simply sets the registration_status of * the initiator to UNREGISTERED. * * @param id the URN of a ViPR initiator * * @brief Unregister initiator * @return Status response indicating success or failure */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deregister") @CheckPermission(roles = { Role.TENANT_ADMIN }) public InitiatorRestRep deregisterInitiator(@PathParam("id") URI id) { Initiator initiator = queryResource(id); ArgValidator.checkEntity(initiator, id, isIdEmbeddedInURL(id)); if (ComputeSystemHelper.isInitiatorInUse(_dbClient, id.toString())) { throw APIException.badRequests.resourceHasActiveReferencesWithType(Initiator.class.getSimpleName(), initiator.getId(), ExportGroup.class.getSimpleName()); } if (RegistrationStatus.REGISTERED.toString().equalsIgnoreCase( initiator.getRegistrationStatus())) { initiator.setRegistrationStatus(RegistrationStatus.UNREGISTERED.toString()); _dbClient.persistObject(initiator); auditOp(OperationTypeEnum.DEREGISTER_INITIATOR, true, null, initiator.getLabel(), initiator.getId().toString()); } return map(initiator); } /** * Manually register the initiator with the passed id. * * @param id the URN of a ViPR initiator * * @brief Register initiator * @return A reference to an InitiatorRestRep specifying the data for the * initiator. */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN }) @Path("/{id}/register") public InitiatorRestRep registerInitiator(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Initiator.class, "id"); Initiator initiator = _dbClient.queryObject(Initiator.class, id); ArgValidator.checkEntity(initiator, id, isIdEmbeddedInURL(id)); if (RegistrationStatus.UNREGISTERED.toString().equalsIgnoreCase( initiator.getRegistrationStatus())) { initiator.setRegistrationStatus(RegistrationStatus.REGISTERED.toString()); _dbClient.persistObject(initiator); auditOp(OperationTypeEnum.REGISTER_INITIATOR, true, null, initiator.getId().toString()); } return map(initiator); } @Override public InitiatorBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<Initiator> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); return new InitiatorBulkRep(BulkList.wrapping(_dbIterator, MapInitiator.getInstance())); } @Override public InitiatorBulkRep queryFilteredBulkResourceReps(List<URI> ids) { Iterator<Initiator> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids); BulkList.ResourceFilter filter = new BulkList.HostInterfaceFilter(getUserFromContext(), _permissionsHelper); return new InitiatorBulkRep(BulkList.wrapping(_dbIterator, MapInitiator.getInstance(), filter)); } /** * Shows the alias/initiator name for an initiator * if set on the Storage System * * @param id the URN of a ViPR initiator * @param sid the pstorage system uri * @prereq none * @return A reference to an InitiatorRestRep representing the Initiator Alias if Set.. * @throws Exception When an error occurs querying the VMAX Storage System. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/alias/{sid}") public InitiatorAliasRestRep getInitiatorAlias(@PathParam("id") URI id, @PathParam("sid") URI systemURI) { // Basic Checks Initiator initiator = queryResource(id); verifyUserPermisions(initiator); ArgValidator.checkFieldUriType(systemURI, StorageSystem.class, "id"); StorageSystem system = _permissionsHelper.getObjectById(systemURI, StorageSystem.class); ArgValidator.checkEntity(system, systemURI, isIdEmbeddedInURL(systemURI)); _log.info("Retrieving alias for initiator {} on system {}", id, systemURI); String initiatorAlias = null; if (system != null && StorageSystem.Type.vmax.toString().equalsIgnoreCase(system.getSystemType())) { BlockController controller = getController(BlockController.class, system.getSystemType()); //Actual Control try { initiatorAlias = controller.getInitiatorAlias(systemURI, id); } catch (Exception e) { _log.error("Unexpected error: Getting alias failed.", e); throw APIException.badRequests.unableToProcessRequest(e.getMessage()); } } else { throw APIException.badRequests.operationNotSupportedForSystemType(ALIAS, system.getSystemType()); } // If the Alias is empty, set it to "/". if (NullColumnValueGetter.isNullValue(initiatorAlias)) { initiatorAlias = EMPTY_INITIATOR_ALIAS; } // Update the initiator initiator.mapInitiatorName(system.getSerialNumber(), initiatorAlias); _dbClient.updateObject(initiator); return new InitiatorAliasRestRep(system.getSerialNumber(), initiatorAlias); } /** * Sets the alias/initiator name for an initiator * on the Storage System * * @param id the URN of a ViPR initiator * @param aliasSetParam the parameter containing the storage system and alias attributes * @prereq none * @return A reference to an InitiatorRestRep representing the Initiator Alias after Set.. * @throws Exception When an error occurs setting the alias on a VMAX Storage System. */ @PUT @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/alias") public InitiatorAliasRestRep setInitiatorAlias(@PathParam("id") URI id, InitiatorAliasSetParam aliasSetParam) { //Basic Checks Initiator initiator = queryResource(id); verifyUserPermisions(initiator); URI systemURI = aliasSetParam.getSystemURI(); ArgValidator.checkFieldUriType(systemURI, StorageSystem.class, "id"); StorageSystem system = _permissionsHelper.getObjectById(systemURI, StorageSystem.class); ArgValidator.checkEntity(system, systemURI, isIdEmbeddedInURL(systemURI)); String initiatorAlias = aliasSetParam.getInitiatorAlias(); ArgValidator.checkFieldNotNull(initiatorAlias, "alias"); if (!initiatorAlias.contains(EMPTY_INITIATOR_ALIAS)) { ArgValidator.checkFieldLengthMaximum(initiatorAlias, ALIAS_MAX_LIMIT, "alias"); } else { ArgValidator.checkFieldLengthMaximum(initiatorAlias.split(EMPTY_INITIATOR_ALIAS)[0], ALIAS_MAX_LIMIT, "alias node name"); ArgValidator.checkFieldLengthMaximum(initiatorAlias.split(EMPTY_INITIATOR_ALIAS)[1], ALIAS_MAX_LIMIT, "alias port name"); } if (initiatorAlias.matches(ALIAS_ILLEGAL_CHARACTERS)) { String errMsg = String.format( "Supplied Alias: %s has invalid characters", initiatorAlias); _log.error(errMsg); throw DeviceControllerException.exceptions.couldNotPerformAliasOperation(errMsg); } _log.info("Setting alias- {} for initiator {} on system {}", initiatorAlias, id, systemURI); if (system != null && StorageSystem.Type.vmax.toString().equalsIgnoreCase(system.getSystemType())) { BlockController controller = getController(BlockController.class, system.getSystemType()); try { //Actual Control controller.setInitiatorAlias(systemURI, id, initiatorAlias); } catch (Exception e) { _log.error("Unexpected error: Setting alias failed.", e); throw APIException.badRequests.unableToProcessRequest(e.getMessage()); } } else { throw APIException.badRequests.operationNotSupportedForSystemType(ALIAS, system.getSystemType()); } //Update the Initiator here.. if (initiatorAlias.contains(EMPTY_INITIATOR_ALIAS)) {// If the Initiator Alias contains the "/" character, the user has supplied // different node and port names. initiator.mapInitiatorName(system.getSerialNumber(), initiatorAlias); } else {// The user has set the same node and port names. initiatorAlias = String.format("%s%s%s", initiatorAlias, EMPTY_INITIATOR_ALIAS, initiatorAlias); initiator.mapInitiatorName(system.getSerialNumber(), initiatorAlias); } _dbClient.updateObject(initiator); return new InitiatorAliasRestRep(system.getSerialNumber(), initiatorAlias); } @Override protected Initiator queryResource(URI id) { return queryObject(Initiator.class, id, false); } @Override protected URI getTenantOwner(URI id) { return null; } @SuppressWarnings("unchecked") @Override public Class<Initiator> getResourceClass() { return Initiator.class; } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.INITIATOR; } public static class InitiatorResRepFilter<E extends RelatedResourceRep> extends ResRepFilter<E> { public InitiatorResRepFilter(StorageOSUser user, PermissionsHelper permissionsHelper) { super(user, permissionsHelper); } @Override public boolean isAccessible(E resrep) { boolean ret = false; URI id = resrep.getId(); Initiator ini = _permissionsHelper.getObjectById(id, Initiator.class); if (ini == null || ini.getHost() == null) { return false; } Host obj = _permissionsHelper.getObjectById(ini.getHost(), Host.class); if (obj == null) { return false; } if (obj.getTenant().toString().equals(_user.getTenantId())) { 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 InitiatorResRepFilter(user, permissionsHelper); } @Override protected boolean isZoneLevelResource() { return false; } @Override protected boolean isSysAdminReadableResource() { return true; } /** * Verify the user has read permissions to the the initiator * * @param initiator the initiator to be verified */ private void verifyUserPermisions(Initiator initiator) { // check the user has permissions if (initiator.getHost() == null) { // this is a system-created initiator - should only be viewed by system admin or monitor verifySystemAdminOrMonitorUser(); } else { // otherwise, check the user permissions for the tenant org Host host = queryObject(Host.class, initiator.getHost(), false); verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext()); } } private Cluster getInitiatorCluster(Initiator initiator) { URI hostId = initiator.getHost(); Cluster cluster = null; URI clusterId = null; if (!NullColumnValueGetter.isNullURI(hostId)) { Host host = _dbClient.queryObject(Host.class, hostId); clusterId = host.getCluster(); } if (!NullColumnValueGetter.isNullURI(clusterId)) { cluster = _dbClient.queryObject(Cluster.class, clusterId); } return cluster; } /** * * parameter: 'initiator_port' The identifier of the initiator port. * * @return Return a list of initiator that containts the initiator port specified * or an empty list if no match was found. */ @Override protected SearchResults getOtherSearchResults(Map<String, List<String>> parameters, boolean authorized) { SearchResults result = new SearchResults(); if (!parameters.containsKey("initiator_port")) { throw APIException.badRequests.invalidParameterSearchMissingParameter(getResourceClass().getName(), "initiator_port"); } for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { if (!entry.getKey().equals("initiator_port")) { throw APIException.badRequests.parameterForSearchCouldNotBeCombinedWithAnyOtherParameter(getResourceClass().getName(), "initiator_port", entry.getKey()); } } String port = parameters.get("initiator_port").get(0); // Validate that the initiator_port value is not empty ArgValidator.checkFieldNotEmpty(port, "initiator_port"); // Validate the format of the initiator port. if (!EndpointUtility.isValidEndpoint(port, EndpointType.SAN)) { throw APIException.badRequests.initiatorPortNotValid(); } SearchedResRepList resRepList = new SearchedResRepList(getResourceType()); // Finds the Initiator that includes the initiator port specified, if any. _dbClient.queryByConstraint(AlternateIdConstraint.Factory.getInitiatorPortInitiatorConstraint(port), resRepList); // Filter list based on permission if (!authorized) { Iterator<SearchResultResourceRep> _queryResultIterator = resRepList.iterator(); ResRepFilter<SearchResultResourceRep> resRepFilter = (ResRepFilter<SearchResultResourceRep>) getPermissionFilter(getUserFromContext(), _permissionsHelper); SearchedResRepList filteredResRepList = new SearchedResRepList(); filteredResRepList.setResult( new FilterIterator<SearchResultResourceRep>(_queryResultIterator, resRepFilter)); result.setResource(filteredResRepList); } else { result.setResource(resRepList); } return result; } }