/** * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.metrics.system.impl; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.ambari.server.configuration.ComponentSSLConfiguration; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider; import org.apache.ambari.server.controller.internal.ServiceConfigVersionResourceProvider; import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.ResourceProvider; import org.apache.ambari.server.controller.utilities.PredicateBuilder; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.apache.ambari.server.metrics.system.MetricsSink; import org.apache.ambari.server.metrics.system.SingleMetric; import org.apache.ambari.server.security.authorization.internal.InternalAuthenticationToken; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; import org.apache.ambari.server.state.ConfigHelper; import org.apache.ambari.server.state.Service; import org.apache.ambari.server.state.ServiceComponent; import org.apache.ambari.server.state.ServiceComponentHost; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.metrics2.sink.timeline.AbstractTimelineMetricsSink; import org.apache.hadoop.metrics2.sink.timeline.TimelineMetric; import org.apache.hadoop.metrics2.sink.timeline.TimelineMetrics; import org.apache.hadoop.metrics2.sink.timeline.cache.TimelineMetricsCache; import org.springframework.security.core.context.SecurityContextHolder; /** * Ambari Server Metrics Sink implementation to push collected metrics to AMS. */ public class AmbariMetricSinkImpl extends AbstractTimelineMetricsSink implements MetricsSink { private static final String AMBARI_SERVER_APP_ID = "ambari_server"; private Collection<String> collectorHosts; private String collectorUri; private String port; private String protocol; private String hostName; private AmbariManagementController ambariManagementController; private TimelineMetricsCache timelineMetricsCache; private boolean isInitialized = false; private boolean setInstanceId = false; private String instanceId; public AmbariMetricSinkImpl(AmbariManagementController amc) { this.ambariManagementController = amc; } @Override public void init(MetricsConfiguration configuration) { if (ambariManagementController == null) { return; } InternalAuthenticationToken authenticationToken = new InternalAuthenticationToken("admin"); authenticationToken.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(authenticationToken); Clusters clusters = ambariManagementController.getClusters(); if (clusters == null || clusters.getClusters().isEmpty()) { LOG.info("No clusters configured."); return; } String ambariMetricsServiceName = "AMBARI_METRICS"; collectorHosts = new HashSet<>(); for (Map.Entry<String, Cluster> kv : clusters.getClusters().entrySet()) { String clusterName = kv.getKey(); instanceId = clusterName; Cluster c = kv.getValue(); Resource.Type type = Resource.Type.ServiceConfigVersion; //If Metrics Collector VIP settings are configured, use that. boolean externalHostConfigPresent = false; boolean externalPortConfigPresent = false; Config clusterEnv = c.getDesiredConfigByType(ConfigHelper.CLUSTER_ENV); if (clusterEnv != null) { Map<String, String> configs = clusterEnv.getProperties(); String metricsCollectorExternalHosts = configs.get("metrics_collector_external_hosts"); if (StringUtils.isNotEmpty(metricsCollectorExternalHosts)) { LOG.info("Setting Metrics Collector External Host : " + metricsCollectorExternalHosts); collectorHosts.addAll(Arrays.asList(metricsCollectorExternalHosts.split(","))); externalHostConfigPresent = true; setInstanceId = true; } String metricsCollectorExternalPort = configs.get("metrics_collector_external_port"); if (StringUtils.isNotEmpty(metricsCollectorExternalPort)) { LOG.info("Setting Metrics Collector External Port : " + metricsCollectorExternalPort); port = metricsCollectorExternalPort; externalPortConfigPresent = true; } } Set<String> propertyIds = new HashSet<>(); propertyIds.add(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_CONFIGURATIONS_PROPERTY_ID); Predicate predicate = new PredicateBuilder().property( ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_CLUSTER_NAME_PROPERTY_ID).equals(clusterName).and().property( ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_SERVICE_NAME_PROPERTY_ID).equals(ambariMetricsServiceName).and().property( ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_IS_CURRENT_PROPERTY_ID).equals("true").toPredicate(); Request request = PropertyHelper.getReadRequest(propertyIds); ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider( type, PropertyHelper.getPropertyIds(type), PropertyHelper.getKeyPropertyIds(type), ambariManagementController); try { if ( !externalHostConfigPresent ) { //get collector host(s) Service service = c.getService(ambariMetricsServiceName); if (service != null) { for (String component : service.getServiceComponents().keySet()) { ServiceComponent sc = service.getServiceComponents().get(component); for (ServiceComponentHost serviceComponentHost : sc.getServiceComponentHosts().values()) { if (serviceComponentHost.getServiceComponentName().equals("METRICS_COLLECTOR")) { collectorHosts.add(serviceComponentHost.getHostName()); } } } } } // get collector port and protocol Set<Resource> resources = provider.getResources(request, predicate); for (Resource resource : resources) { if (resource != null) { ArrayList<LinkedHashMap<Object, Object>> configs = (ArrayList<LinkedHashMap<Object, Object>>) resource.getPropertyValue(ServiceConfigVersionResourceProvider.SERVICE_CONFIG_VERSION_CONFIGURATIONS_PROPERTY_ID); for (LinkedHashMap<Object, Object> config : configs) { if (config != null && config.get("type").equals("ams-site")) { TreeMap<Object, Object> properties = (TreeMap<Object, Object>) config.get("properties"); String timelineWebappAddress = (String) properties.get("timeline.metrics.service.webapp.address"); if (!externalPortConfigPresent && StringUtils.isNotEmpty(timelineWebappAddress) && timelineWebappAddress.contains(":")) { port = timelineWebappAddress.split(":")[1]; } String httpPolicy = (String) properties.get("timeline.metrics.service.http.policy"); protocol = httpPolicy.equals("HTTP_ONLY") ? "http" : "https"; break; } } } } } catch (Exception e) { LOG.info("Exception caught when retrieving Collector URI", e); } } hostName = configuration.getProperty("ambariserver.hostname.override", getDefaultLocalHostName()); LOG.info("Hostname used for ambari server metrics : " + hostName); if (protocol.contains("https")) { ComponentSSLConfiguration sslConfiguration = ComponentSSLConfiguration.instance(); String trustStorePath = sslConfiguration.getTruststorePath(); String trustStoreType = sslConfiguration.getTruststoreType(); String trustStorePwd = sslConfiguration.getTruststorePassword(); loadTruststore(trustStorePath, trustStoreType, trustStorePwd); } collectorUri = getCollectorUri(findPreferredCollectHost()); int maxRowCacheSize = Integer.parseInt(configuration.getProperty(MAX_METRIC_ROW_CACHE_SIZE, String.valueOf(TimelineMetricsCache.MAX_RECS_PER_NAME_DEFAULT))); int metricsSendInterval = Integer.parseInt(configuration.getProperty(METRICS_SEND_INTERVAL, String.valueOf(TimelineMetricsCache.MAX_EVICTION_TIME_MILLIS))); timelineMetricsCache = new TimelineMetricsCache(maxRowCacheSize, metricsSendInterval); if (CollectionUtils.isNotEmpty(collectorHosts)) { isInitialized = true; } } private String getDefaultLocalHostName() { try { return InetAddress.getLocalHost().getCanonicalHostName(); } catch (UnknownHostException e) { LOG.info("Error getting host address"); } return null; } /** * Publish metrics to AMS. * @param metrics Set of metrics */ @Override public void publish(List<SingleMetric> metrics) { //If Sink not yet initialized, drop the metrics on the floor. if (isInitialized) { List<TimelineMetric> metricList = getFilteredMetricList(metrics); if (!metricList.isEmpty()) { TimelineMetrics timelineMetrics = new TimelineMetrics(); timelineMetrics.setMetrics(metricList); emitMetrics(timelineMetrics); } } } @Override public boolean isInitialized() { return isInitialized; } /** * Get a pre-formatted URI for the collector * * @param host */ @Override protected String getCollectorUri(String host) { return constructTimelineMetricUri(protocol, host, port); } @Override protected String getCollectorProtocol() { return protocol; } @Override protected String getCollectorPort() { return port; } @Override protected int getTimeoutSeconds() { return 10; } /** * Get the zookeeper quorum for the cluster used to find collector * * @return String "host1:port1,host2:port2" */ @Override protected String getZookeeperQuorum() { //Ignoring Zk Fallback. return null; } /** * Get pre-configured list of collectors available * * @return Collection<String> host1,host2 */ @Override protected Collection<String> getConfiguredCollectorHosts() { return collectorHosts; } /** * Get hostname used for calculating write shard. * * @return String "host1" */ @Override protected String getHostname() { return hostName; } @Override protected boolean isHostInMemoryAggregationEnabled() { return false; } @Override protected int getHostInMemoryAggregationPort() { return 0; } private List<TimelineMetric> getFilteredMetricList(List<SingleMetric> metrics) { final List<TimelineMetric> metricList = new ArrayList<>(); for (SingleMetric metric : metrics) { String metricName = metric.getMetricName(); Double value = metric.getValue(); TimelineMetric timelineMetric = createTimelineMetric(metric.getTimestamp(), AMBARI_SERVER_APP_ID, metricName, value); timelineMetricsCache.putTimelineMetric(timelineMetric, false); TimelineMetric cachedMetric = timelineMetricsCache.getTimelineMetric(metricName); if (cachedMetric != null) { metricList.add(cachedMetric); } } return metricList; } private TimelineMetric createTimelineMetric(long currentTimeMillis, String component, String attributeName, Number attributeValue) { TimelineMetric timelineMetric = new TimelineMetric(); timelineMetric.setMetricName(attributeName); timelineMetric.setHostName(hostName); if (setInstanceId) { timelineMetric.setInstanceId(instanceId); } timelineMetric.setAppId(component); timelineMetric.setStartTime(currentTimeMillis); timelineMetric.getMetricValues().put(currentTimeMillis, attributeValue.doubleValue()); return timelineMetric; } }