/**
* 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.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.AmbariServer;
import org.apache.ambari.server.controller.metrics.MetricReportingAdapter;
import org.apache.ambari.server.controller.spi.PropertyProvider;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.TemporalInfo;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
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.hadoop.metrics2.sink.timeline.TimelineMetric;
/**
* Abstract property provider implementation.
*/
public abstract class AbstractPropertyProvider extends BaseProvider implements PropertyProvider {
/**
* The property/metric information for this provider keyed by component name / property id.
*/
private final Map<String, Map<String, PropertyInfo>> componentMetrics;
/**
* Regular expression for checking a property id for a metric argument with methods.
* (e.g. metrics/yarn/Queue$1.replaceAll(\",q(\\d+)=\",\"/\")/AppsRunning)
*/
private static final Pattern CHECK_FOR_METRIC_ARGUMENT_METHODS_REGEX = Pattern.compile("(\\$\\d\\.[^\\$]+\\))+");
/**
* Regular expression for extracting the methods from a metric argument.
* (e.g. $1.replaceAll(\",q(\\d+)=\",\"/\"))
*/
private static final Pattern FIND_ARGUMENT_METHOD_REGEX = Pattern.compile(".\\w+\\(.*?\\)");
/**
* Regular expression for extracting the arguments for methods from a metric argument.
* Only strings and integers are supported.
*/
private static final Pattern FIND_ARGUMENT_METHOD_ARGUMENTS_REGEX = Pattern.compile("\".*?\"|[0-9]+");
/**
* Supported any regex inside ()
*/
private static final String FIND_REGEX_IN_METRIC_REGEX = "\\([^)]+\\)";
private static final DecimalFormat decimalFormat = new DecimalFormat("#.00");
// ----- Constructors ------------------------------------------------------
/**
* Construct a provider.
*
* @param componentMetrics map of metrics for this provider
*/
public AbstractPropertyProvider(Map<String, Map<String, PropertyInfo>> componentMetrics) {
super(PropertyHelper.getPropertyIds(componentMetrics));
this.componentMetrics = componentMetrics;
}
// ----- accessors ---------------------------------------------------------
/**
* Get the map of metrics for this provider.
*
* @return the map of metric / property info.
*/
public Map<String, Map<String, PropertyInfo>> getComponentMetrics() {
return componentMetrics;
}
// ----- helper methods ----------------------------------------------------
/**
* Retrieves passed-in Resource's Type
*
* @param resources Set of Resources.
* @return Type of resource from the Set.
*/
protected String getResourceTypeFromResources(Set<Resource> resources) {
String resType = null;
if (resources != null) {
Iterator<Resource> itr = resources.iterator();
if (itr.hasNext()) {
// Pick the 1st resource, as the passed in resources will have same Type,
// in a given call.
Resource res = itr.next();
if (res != null) {
resType = res.getType().toString();
}
}
}
return resType;
}
/**
* Retrieves all the cluster names to which the passed-in Resource's belong.
*
* @param resources Set of Resources.
* @return Cluster's Name
*/
protected Set<String> getClustersNameFromResources(Set<Resource> resources, String clusterNamePropertyId) {
Set<String> clusNames = new HashSet<>();
if (resources != null) {
Iterator<Resource> itr = resources.iterator();
while (itr.hasNext()) {
Resource res = itr.next();
if (res != null) {
clusNames.add((String) res.getPropertyValue(clusterNamePropertyId));
}
}
}
return clusNames;
}
/**
* Retrieves all the 'Cluster's Resource Ids' from the passed-in Resources.
*
* @param resources Set of Resources.
* @param clusterNamePropertyId ClusterName PropertyId.
* @return cluster Id.
*/
protected Set<Long> getClustersResourceId(Set<Resource> resources, String clusterNamePropertyId) {
Set<Long> clusterResId = new HashSet<>();
if (clusterNamePropertyId != null) {
try {
AmbariManagementController amc = AmbariServer.getController();
Set<String> clusterNames = getClustersNameFromResources(resources, clusterNamePropertyId);
Iterator<String> clusNameItr = clusterNames.iterator();
while (clusNameItr.hasNext()) {
clusterResId.add(amc.getClusters().getCluster(clusNameItr.next()).getResourceId());
}
} catch (AmbariException e) {
LOG.error("Cluster Id couldn't be retrieved.");
} catch (Exception e) {
LOG.error("Cluster Id couldn't be retrieved");
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("Retrieved Cluster Ids = " + clusterResId.toString());
}
return clusterResId;
}
/**
* Check the User's authorization for retrieving the Metrics.
*
* @param resources Set of Resources.
* @param clusterNamePropertyId ClusterName PropertyId.
* @return boolean
* @throws AuthorizationException
*/
protected boolean checkAuthorizationForMetrics(Set<Resource> resources, String clusterNamePropertyId) throws AuthorizationException {
String resType = null;
// Get the Type
resType = getResourceTypeFromResources(resources);
if (resType == null) {
return false;
}
// Get the cluster Id.
Set<Long> clusterResIds = getClustersResourceId(resources, clusterNamePropertyId);
if (clusterResIds.size() == 0) {
return false;
}
if(LOG.isDebugEnabled()) {
LOG.debug("Retrieved cluster's Resource Id = " + clusterResIds + ", Resource Type = " + resType);
}
Iterator<Long> clusResIdsItr = clusterResIds.iterator();
while (clusResIdsItr.hasNext()) {
Long clusResId = clusResIdsItr.next();
Resource.InternalType resTypeVal = Resource.InternalType.valueOf(resType);
switch (resTypeVal) {
case Cluster:
if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, clusResId, EnumSet.of(RoleAuthorization.CLUSTER_VIEW_METRICS))) {
throw new AuthorizationException("The authenticated user does not have authorization to view cluster metrics");
}
break;
case Host:
if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, clusResId, EnumSet.of(RoleAuthorization.HOST_VIEW_METRICS))) {
throw new AuthorizationException("The authenticated user does not have authorization to view Host metrics");
}
break;
case Component :
if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, clusResId, EnumSet.of(RoleAuthorization.SERVICE_VIEW_METRICS))) {
throw new AuthorizationException("The authenticated user does not have authorization to view Service metrics");
}
break;
case HostComponent:
if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, clusResId, EnumSet.of(RoleAuthorization.SERVICE_VIEW_METRICS))) {
throw new AuthorizationException("The authenticated user does not have authorization to view Service metrics");
}
break;
default:
LOG.error("Unsuported Resource Type for Metrics");
return false;
}
}
return true;
}
/**
* Get a map of metric / property info based on the given component name and property id.
* Note that the property id may map to multiple metrics if the property id is a category.
*
* @param componentName the component name
* @param propertyId the property id; may be a category
*
* @return a map of metrics
*/
protected Map<String, PropertyInfo> getPropertyInfoMap(String componentName, String propertyId) {
Map<String, PropertyInfo> propertyInfoMap = new HashMap<>();
updatePropertyInfoMap(componentName, propertyId, propertyInfoMap);
return propertyInfoMap;
}
protected void updatePropertyInfoMap(String componentName, String propertyId, Map<String, PropertyInfo> propertyInfoMap) {
Map<String, PropertyInfo> componentMetricMap = getComponentMetrics().get(componentName);
propertyInfoMap.clear();
if (componentMetricMap == null) {
return;
}
PropertyInfo propertyInfo = componentMetricMap.get(propertyId);
if (propertyInfo != null) {
propertyInfoMap.put(propertyId, propertyInfo);
return;
}
Map.Entry<String, Pattern> regexEntry = getRegexEntry(propertyId);
if (regexEntry != null) {
String regexKey = regexEntry.getKey();
propertyInfo = componentMetricMap.get(regexKey);
if (propertyInfo != null) {
propertyInfoMap.put(regexKey, propertyInfo);
return;
}
}
if (!propertyId.endsWith("/")) {
propertyId += "/";
}
for (Map.Entry<String, PropertyInfo> entry : componentMetricMap.entrySet()) {
if (entry.getKey().startsWith(propertyId)) {
String key = entry.getKey();
propertyInfoMap.put(key, entry.getValue());
}
}
if (regexEntry != null) {
// in the event that a category is being requested, the pattern must
// match all child properties; append \S* for this
String regexPattern = regexEntry.getValue().pattern();
regexPattern += "(\\S*)";
for (Map.Entry<String, PropertyInfo> entry : componentMetricMap.entrySet()) {
if (entry.getKey().matches(regexPattern)) {
propertyInfoMap.put(entry.getKey(), entry.getValue());
}
}
}
return;
}
/**
* Substitute the given value into the argument in the given property id. If there are methods attached
* to the argument then execute them for the given value.
*
* @param propertyId the property id
* @param argName the argument name
* @param val the value to substitute
*
* @return the modified property id
*/
protected static String substituteArgument(String propertyId, String argName, String val) {
// find the argument in the property id
int argStart = propertyId.indexOf(argName);
String value = val == null ? "" : val;
if (argStart > -1) {
// get the string segment starting with the given argument
String argSegment = propertyId.substring(argStart);
// check to see if there are any methods attached to the argument
Matcher matcher = CHECK_FOR_METRIC_ARGUMENT_METHODS_REGEX.matcher(argSegment);
if (matcher.find()) {
// expand the argument name to include its methods
argName = argSegment.substring(matcher.start(), matcher.end());
// for each method attached to the argument ...
matcher = FIND_ARGUMENT_METHOD_REGEX.matcher(argName);
while (matcher.find()) {
// find the end of the method
int openParenIndex = argName.indexOf('(', matcher.start());
int closeParenIndex = indexOfClosingParenthesis(argName, openParenIndex);
String methodName = argName.substring(matcher.start() + 1, openParenIndex);
String args = argName.substring(openParenIndex + 1, closeParenIndex);
List<Object> argList = new LinkedList<>();
List<Class<?>> paramTypes = new LinkedList<>();
// for each argument of the method ...
Matcher argMatcher = FIND_ARGUMENT_METHOD_ARGUMENTS_REGEX.matcher(args);
while (argMatcher.find()) {
addArgument(args, argMatcher.start(), argMatcher.end(), argList, paramTypes);
}
try {
value = invokeArgumentMethod(value, methodName, argList, paramTypes);
} catch (Exception e) {
throw new IllegalArgumentException("Can't apply method " + methodName + " for argument " +
argName + " in " + propertyId, e);
}
}
if (value.equals(val)) {
return propertyId;
}
}
// Do the substitution
return propertyId.replace(argName, value);
}
throw new IllegalArgumentException("Can't substitute " + val + " for argument " + argName + " in " + propertyId);
}
/**
* Find the index of the closing parenthesis in the given string.
*/
private static int indexOfClosingParenthesis(String s, int index) {
int depth = 0;
int length = s.length();
while (index < length) {
char c = s.charAt(index++);
if (c == '(') {
++depth;
} else if (c == ')') {
if (--depth == 0) {
return index;
}
}
}
return -1;
}
/**
* Extract an argument from the given string and add it to the given arg and param type collections.
*/
private static void addArgument(String args, int start, int end, List<Object> argList, List<Class<?>> paramTypes) {
String arg = args.substring(start, end);
// only supports strings and integers
if (arg.contains("\"")) {
argList.add(arg.substring(1, arg.length() - 1));
paramTypes.add(String.class);
} else {
Integer number = Integer.parseInt(arg);
argList.add(number);
paramTypes.add(Integer.TYPE);
}
}
/**
* Invoke a method on the given argument string with the given parameters.
*/
private static String invokeArgumentMethod(String argValue, String methodName, List<Object> argList,
List<Class<?>> paramTypes)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// invoke the method through reflection
Method method = String.class.getMethod(methodName, paramTypes.toArray(new Class<?>[paramTypes.size()]));
return (String) method.invoke(argValue, argList.toArray(new Object[argList.size()]));
}
/**
* Adds to the componentMetricMap a specific(not regexp)
* metric for the propertyId
*
* @param componentMetricMap
* @param propertyId
*/
protected void updateComponentMetricMap(
Map<String, PropertyInfo> componentMetricMap, String propertyId) {
String regexKey = null;
Map.Entry<String, Pattern> regexEntry = getRegexEntry(propertyId);
if (null != regexEntry) {
regexKey = regexEntry.getKey();
}
if (!componentMetricMap.containsKey(propertyId) && regexKey != null
&& !regexKey.equals(propertyId)) {
PropertyInfo propertyInfo = componentMetricMap.get(regexKey);
if (propertyInfo != null) {
List<String> regexGroups = getRegexGroups(regexKey, propertyId);
String key = propertyInfo.getPropertyId();
for (String regexGroup : regexGroups) {
regexGroup = regexGroup.replace("/", ".");
key = key.replaceFirst(FIND_REGEX_IN_METRIC_REGEX, regexGroup);
}
PropertyInfo compPropertyInfo = new PropertyInfo(key,
propertyInfo.isTemporal(), propertyInfo.isPointInTime());
compPropertyInfo.setAmsHostMetric(propertyInfo.isAmsHostMetric());
compPropertyInfo.setAmsId(propertyInfo.getAmsId());
compPropertyInfo.setUnit(propertyInfo.getUnit());
componentMetricMap.put(propertyId, compPropertyInfo);
}
}
}
protected PropertyInfo updatePropertyInfo(String propertyKey, String id, PropertyInfo propertyInfo) {
List<String> regexGroups = getRegexGroups(propertyKey, id);
String propertyId = propertyInfo.getPropertyId();
if (propertyId != null) {
for (String regexGroup : regexGroups) {
regexGroup = regexGroup.replace("/", ".");
propertyId = propertyId.replaceFirst(FIND_REGEX_IN_METRIC_REGEX, regexGroup);
}
}
return new PropertyInfo(propertyId, propertyInfo.isTemporal(), propertyInfo.isPointInTime());
}
/**
* Verify that the component metrics contains the property id.
* @param componentName Name of the component
* @param propertyId Property Id
* @return true/false
*/
protected boolean isSupportedPropertyId(String componentName, String propertyId) {
Map<String, PropertyInfo> componentMetricMap = componentMetrics.get(componentName);
return componentMetricMap != null
&& (componentMetricMap.containsKey(propertyId) || checkPropertyCategory(propertyId));
}
/**
* Verify if the property category is supported
*/
protected boolean checkPropertyCategory(String propertyId) {
Set<String> categoryIds = getCategoryIds();
// Support query by category
if (categoryIds.contains(propertyId)) {
return true;
}
String category = PropertyHelper.getPropertyCategory(propertyId);
while (category != null) {
if (categoryIds.contains(category)) {
return true;
}
category = PropertyHelper.getPropertyCategory(category);
}
return false;
}
// Normalize percent values: Copied over from Ganglia Metric
private static Number[][] getGangliaLikeDatapoints(TimelineMetric metric, TemporalInfo temporalInfo) {
MetricReportingAdapter rpt = new MetricReportingAdapter(metric);
return rpt.reportMetricData(metric, temporalInfo);
}
/**
* Get value from the given metric.
*
* @param metric the metric
* @param temporalInfo indicates whether or not this a temporal metric
*
* @return a range of temporal data or a point in time value if not temporal
*/
protected static Object getValue(TimelineMetric metric, TemporalInfo temporalInfo) {
Number[][] dataPoints = getGangliaLikeDatapoints(metric, temporalInfo);
int length = dataPoints.length;
if (temporalInfo != null) {
return length > 0 ? dataPoints : null;
} else {
// return the value of the last data point
return length > 0 ? dataPoints[length - 1][0] : 0;
}
}
}