/** * 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; } } }