/**
* 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.controller.logging;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.PropertyProvider;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
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.state.ComponentInfo;
import org.apache.ambari.server.state.LogDefinition;
import org.apache.ambari.server.state.StackId;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
public class LoggingSearchPropertyProvider implements PropertyProvider {
private static final Logger LOG = LoggerFactory.getLogger(LoggingSearchPropertyProvider.class);
private static final String CLUSTERS_PATH = "/api/v1/clusters";
private static final String PATH_TO_SEARCH_ENGINE = "/logging/searchEngine";
/**
* 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 static AtomicInteger errorLogCounterForLogSearchConnectionExceptions = new AtomicInteger(0);
@Inject
private AmbariManagementController ambariManagementController;
@Inject
private LogSearchDataRetrievalService logSearchDataRetrievalService;
@Inject
private LoggingRequestHelperFactory loggingRequestHelperFactory;
@Override
public Set<Resource> populateResources(Set<Resource> resources, Request request, Predicate predicate) throws SystemException {
Map<String, Boolean> isLogSearchRunning = new HashMap<>();
for (Resource resource : resources) {
// obtain the required identifying properties on the host component resource
final String componentName = (String)resource.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "component_name"));
final String hostName = (String) resource.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "host_name"));
final String clusterName = (String) resource.getPropertyValue(PropertyHelper.getPropertyId("HostRoles", "cluster_name"));
// Test to see if the authenticated user is authorized to view this data... if not, skip it.
if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceID(clusterName), REQUIRED_AUTHORIZATIONS)) {
if(LOG.isDebugEnabled()) {
LOG.debug(String.format("The authenticated user (%s) is not authorized to access LogSearch data for the cluster named %s",
AuthorizationHelper.getAuthenticatedName(),
clusterName));
}
continue;
}
Boolean isLogSearchRunningForSpecifiedCluster = isLogSearchRunning.get(clusterName);
if (isLogSearchRunningForSpecifiedCluster == null) {
isLogSearchRunningForSpecifiedCluster = logSearchServerRunning(clusterName);
isLogSearchRunning.put(clusterName, isLogSearchRunningForSpecifiedCluster);
}
if (!isLogSearchRunningForSpecifiedCluster) {
continue;
}
// query the stack definitions to find the correct component name (stack name mapped to LogSearch-defined name)
final String mappedComponentNameForLogSearch =
getMappedComponentNameForSearch(clusterName, componentName, ambariManagementController);
if (mappedComponentNameForLogSearch != null) {
// send query to obtain logging metadata
Set<String> logFileNames =
logSearchDataRetrievalService.getLogFileNames(mappedComponentNameForLogSearch, hostName, clusterName);
if ((logFileNames != null) && (!logFileNames.isEmpty())) {
HostComponentLoggingInfo loggingInfo = new HostComponentLoggingInfo();
loggingInfo.setComponentName(mappedComponentNameForLogSearch);
List<LogFileDefinitionInfo> listOfFileDefinitions =
new LinkedList<>();
for (String fileName : logFileNames) {
// generate the URIs that can be used by clients to obtain search results/tail log results/etc
final String searchEngineURI = ambariManagementController.getAmbariServerURI(getFullPathToSearchEngine(clusterName));
final String logFileTailURI = logSearchDataRetrievalService.getLogFileTailURI(searchEngineURI, mappedComponentNameForLogSearch, hostName, clusterName);
if (logFileTailURI != null) {
// all log files are assumed to be service types for now
listOfFileDefinitions.add(new LogFileDefinitionInfo(fileName, LogFileType.SERVICE, searchEngineURI, logFileTailURI));
}
}
loggingInfo.setListOfLogFileDefinitions(listOfFileDefinitions);
LOG.debug("Adding logging info for component name = " + componentName + " on host name = " + hostName);
// add the logging metadata for this host component
resource.setProperty("logging", loggingInfo);
} else {
if (LOG.isDebugEnabled()) {
String debugMessage =
String.format("Error occurred while making request to LogSearch service, unable to populate logging properties on this resource, component = %s, host = %s",
componentName, hostName);
Utils.logDebugMessageWithCounter(LOG, errorLogCounterForLogSearchConnectionExceptions,
debugMessage);
}
}
}
}
return resources;
}
/**
* Get the relevant cluster's resource ID.
*
* @param clusterName a cluster name
* @return a resource ID or <code>null</code> if the cluster is not found
*/
private Long getClusterResourceID(String clusterName) {
Long clusterResourceId = null;
if(!StringUtils.isEmpty(clusterName)) {
try {
Cluster cluster = ambariManagementController.getClusters().getCluster(clusterName);
if(cluster == null) {
LOG.warn(String.format("No cluster found with the name %s, assuming null resource id", clusterName));
}
else {
clusterResourceId = cluster.getResourceId();
}
} catch (AmbariException e) {
LOG.warn(String.format("An exception occurred looking up the cluster named %s, assuming null resource id: %s",
clusterName, e.getLocalizedMessage()));
}
}
else {
LOG.debug("The cluster name is not set, assuming null resource id");
}
return clusterResourceId;
}
private boolean logSearchServerRunning(String clusterName) {
return loggingRequestHelperFactory.getHelper(ambariManagementController, clusterName) != null;
}
private String getMappedComponentNameForSearch(String clusterName, String componentName, AmbariManagementController controller) {
try {
AmbariMetaInfo metaInfo = controller.getAmbariMetaInfo();
StackId stackId =
controller.getClusters().getCluster(clusterName).getCurrentStackVersion();
final String stackName = stackId.getStackName();
final String stackVersion = stackId.getStackVersion();
final String serviceName =
metaInfo.getComponentToService(stackName, stackVersion, componentName);
ComponentInfo componentInfo =
metaInfo.getComponent(stackName, stackVersion, serviceName, componentName);
if (componentInfo != null) {
List<LogDefinition> listOfLogs =
componentInfo.getLogs();
// for now, the assumption is that there is only one log file associated with each
// component in LogSearch, but this may change in the future
if ((listOfLogs != null) && (!listOfLogs.isEmpty())) {
LogDefinition definition = listOfLogs.get(0);
// return the first log id we find
return definition.getLogId();
}
}
} catch (AmbariException e) {
LOG.error("Error occurred while attempting to locate the log component name for component = " + componentName, e);
}
return null;
}
private String getFullPathToSearchEngine(String clusterName) {
return CLUSTERS_PATH + "/" + clusterName + PATH_TO_SEARCH_ENGINE;
}
@Override
public Set<String> checkPropertyIds(Set<String> propertyIds) {
return Collections.emptySet();
}
protected void setAmbariManagementController(AmbariManagementController ambariManagementController) {
this.ambariManagementController = ambariManagementController;
}
void setLogSearchDataRetrievalService(LogSearchDataRetrievalService logSearchDataRetrievalService) {
this.logSearchDataRetrievalService = logSearchDataRetrievalService;
}
void setLoggingRequestHelperFactory(LoggingRequestHelperFactory loggingRequestHelperFactory) {
this.loggingRequestHelperFactory = loggingRequestHelperFactory;
}
}