/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.api.services; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.api.services.serializers.ResultSerializer; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.AmbariServer; import org.apache.ambari.server.controller.internal.ResourceImpl; import org.apache.ambari.server.controller.logging.LogQueryResponse; import org.apache.ambari.server.controller.logging.LoggingRequestHelper; import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.security.authorization.AuthorizationException; import org.apache.ambari.server.security.authorization.AuthorizationHelper; import org.apache.ambari.server.security.authorization.ResourceType; import org.apache.ambari.server.security.authorization.RoleAuthorization; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.utils.RetryHelper; import org.apache.commons.lang.StringUtils; import com.google.inject.Inject; /** * This Service provides access to the LogSearch query services, including: * - Access to all service log files in a given cluster * - Search query capability across the service logs * - Metrics data regarding logging (log level counts, etc) * */ public class LoggingService extends BaseService { /** * The user of authorizations for which a user must have one of in order to access LogSearch data */ private static final Set<RoleAuthorization> REQUIRED_AUTHORIZATIONS = EnumSet.of(RoleAuthorization.SERVICE_VIEW_OPERATIONAL_LOGS); private final ControllerFactory controllerFactory; @Inject private LoggingRequestHelperFactory helperFactory; private final String clusterName; public LoggingService(String clusterName) { this(clusterName, new DefaultControllerFactory()); } public LoggingService(String clusterName, ControllerFactory controllerFactory) { this.clusterName = clusterName; this.controllerFactory = controllerFactory; } @GET @Path("searchEngine") @Produces("text/plain") public Response getSearchEngine(String body, @Context HttpHeaders headers, @Context UriInfo uri) throws AuthorizationException { if(AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(), REQUIRED_AUTHORIZATIONS)) { return handleDirectRequest(uri, MediaType.TEXT_PLAIN_TYPE); } else { Response.ResponseBuilder responseBuilder = Response.status(new ResultStatus(ResultStatus.STATUS.FORBIDDEN).getStatusCode()); responseBuilder.entity("The authenticated user is not authorized to perform this operation."); return responseBuilder.build(); } } /** * Get the relevant cluster's resource ID. * * @return a resource ID or <code>null</code> if the cluster is not found */ private Long getClusterResourceId() { Long clusterResourceId = null; if(!StringUtils.isEmpty(clusterName)) { try { Cluster cluster = controllerFactory.getController().getClusters().getCluster(clusterName); if(cluster == null) { LOG.warn("No cluster found with the name {}, assuming null resource id", clusterName); } else { clusterResourceId = cluster.getResourceId(); } } catch (AmbariException e) { LOG.warn("An exception occurred looking up the cluster named {}, assuming null resource id: {}", clusterName, e.getLocalizedMessage()); } } else { LOG.debug("The cluster name is not set, assuming null resource id"); } return clusterResourceId; } /** * Handling method for REST services that don't require the QueryParameter and * partial-response syntax support provided by the Ambari REST framework. * * In the case of the LoggingService, the query parameters passed to the search engine must * be preserved, since they are passed to the LogSearch REST API. * * @param uriInfo URI information for this request * @param mediaType media type for this request * * @return a Response for the request associated with this UriInfo */ protected Response handleDirectRequest(UriInfo uriInfo, MediaType mediaType) { MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(); Map<String, String> enumeratedQueryParameters = new HashMap<>(); for (String queryName : queryParameters.keySet()) { List<String> queryValue = queryParameters.get(queryName); for (String value : queryValue) { enumeratedQueryParameters.put(queryName, value); } } AmbariManagementController controller = controllerFactory.getController(); LoggingRequestHelper requestHelper = helperFactory.getHelper(controller, clusterName); if (requestHelper != null) { LogQueryResponse response = requestHelper.sendQueryRequest(enumeratedQueryParameters); if (response != null) { ResultSerializer serializer = mediaType == null ? getResultSerializer() : getResultSerializer(mediaType); Result result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.OK)); Resource loggingResource = new ResourceImpl(Resource.Type.LoggingQuery); // include the top-level query result properties loggingResource.setProperty("startIndex", response.getStartIndex()); loggingResource.setProperty("pageSize", response.getPageSize()); loggingResource.setProperty("resultSize", response.getResultSize()); loggingResource.setProperty("queryTimeMMS", response.getQueryTimeMS()); loggingResource.setProperty("totalCount", response.getTotalCount()); // include the individual responses loggingResource.setProperty("logList", response.getListOfResults()); result.getResultTree().addChild(loggingResource, "logging"); Response.ResponseBuilder builder = Response.status(result.getStatus().getStatusCode()).entity( serializer.serialize(result)); if (mediaType != null) { builder.type(mediaType); } RetryHelper.clearAffectedClusters(); return builder.build(); } } else { LOG.debug("LogSearch is not currently available, an empty response will be returned"); } // return NOT_FOUND, with an error message describing that LogSearch is not running final Response.ResponseBuilder responseBuilder = Response.status(new ResultStatus(ResultStatus.STATUS.NOT_FOUND).getStatusCode()); responseBuilder.entity("LogSearch is not currently available. If LogSearch is deployed in this cluster, please verify that the LogSearch services are running."); return responseBuilder.build(); } /** * Package-level setter that facilitates simpler unit testing * * @param helperFactory */ void setLoggingRequestHelperFactory(LoggingRequestHelperFactory helperFactory) { this.helperFactory = helperFactory; } /** * Internal interface that defines an access factory for the * AmbariManagementController. This facilitates simpler unit testing. * */ interface ControllerFactory { AmbariManagementController getController(); } /** * Default implementation of the internal ControllerFactory interface, * which uses the AmbariServer static method to obtain the controller. */ private static class DefaultControllerFactory implements ControllerFactory { @Override public AmbariManagementController getController() { return AmbariServer.getController(); } } }