Repository: ambari
Updated Branches:
  refs/heads/branch-feature-AMBARI-22008 c8a34e51a -> b99b5ecb2


AMBARI-22236. Expression parser support for JMXServerSide alerts


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/b99b5ecb
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/b99b5ecb
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/b99b5ecb

Branch: refs/heads/branch-feature-AMBARI-22008
Commit: b99b5ecb229acd1a7271817ff7c4f9e462d2db48
Parents: c8a34e5
Author: Attila Magyar <amag...@hortonworks.com>
Authored: Tue Oct 17 16:15:15 2017 +0200
Committer: Attila Magyar <amag...@hortonworks.com>
Committed: Tue Oct 17 16:15:15 2017 +0200

----------------------------------------------------------------------
 .../server/alerts/JmxServerSideAlert.java       | 42 ++++++------
 .../server/state/alert/AlertDefinition.java     | 13 ++++
 .../ambari/server/state/alert/MetricSource.java | 59 ++++++++++++++++-
 .../ambari/server/state/alert/Reporting.java    | 16 ++---
 .../AlertDefinitionResourceProviderTest.java    |  4 +-
 .../ambari/server/state/alert/JmxInfoTest.java  | 68 ++++++++++++++++++++
 6 files changed, 162 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
index a4b86f8..09eb0a4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
@@ -23,6 +23,7 @@ import static java.util.Collections.singletonList;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
@@ -35,7 +36,6 @@ import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.alert.AlertDefinition;
 import org.apache.ambari.server.state.alert.AlertDefinitionFactory;
 import org.apache.ambari.server.state.alert.MetricSource;
-import org.apache.ambari.server.state.alert.Reporting;
 import org.apache.ambari.server.state.alert.ServerSource;
 import org.apache.ambari.server.state.services.MetricsRetrievalService;
 import org.slf4j.Logger;
@@ -64,41 +64,35 @@ public class JmxServerSideAlert extends AlertRunnable {
   List<Alert> execute(Cluster cluster, AlertDefinitionEntity entity) throws 
AmbariException {
     AlertDefinition alertDef = definitionFactory.coerce(entity);
     ServerSource serverSource = (ServerSource) alertDef.getSource();
-    URI jmxUrl = jmxUrl(cluster, serverSource);
-    JMXMetricHolder metricHolder = jmxMetric(serverSource, jmxUrl);
-    return metricHolder == null
-      ? emptyList()
-      : alerts(alertDef, serverSource.getJmxInfo(), metricHolder, 
serverSource.getReporting());
+    return buildAlert(jmxMetric(serverSource, cluster), 
serverSource.getJmxInfo(), alertDef)
+      .map(alert -> singletonList(alert))
+      .orElse(emptyList());
   }
 
-  private URI jmxUrl(Cluster cluster, ServerSource serverSource) throws 
AmbariException {
-    return 
serverSource.getUri().resolve(config(cluster)).resolve(serverSource.getJmxInfo().getUrlSuffix());
+  public Optional<Alert> buildAlert(Optional<JMXMetricHolder> metricHolder, 
MetricSource.JmxInfo jmxInfo, AlertDefinition alertDef) {
+    return metricHolder.flatMap(metric -> buildAlert(metric, jmxInfo, 
alertDef));
   }
 
-  private Map<String, Map<String, String>> config(Cluster cluster) throws 
AmbariException {
-    return configHelper.getEffectiveConfigProperties(cluster, 
configHelper.getEffectiveDesiredTags(cluster, null));
+  private Optional<Alert> buildAlert(JMXMetricHolder metricHolder, 
MetricSource.JmxInfo jmxInfo, AlertDefinition alertDef) {
+    List<Object> allMetrics = metricHolder.findAll(jmxInfo.getPropertyList());
+    return jmxInfo.eval(metricHolder).map(val -> 
alertDef.buildAlert(val.doubleValue(), allMetrics));
   }
 
-  private JMXMetricHolder jmxMetric(ServerSource serverSource, URI jmxUri) {
+  private Optional<JMXMetricHolder> jmxMetric(ServerSource serverSource, 
Cluster cluster) throws AmbariException {
+    URI jmxUri = jmxUrl(cluster, serverSource);
     URLStreamProvider streamProvider = new URLStreamProvider(
       serverSource.getUri().getConnectionTimeoutMsec(),
       serverSource.getUri().getReadTimeoutMsec(),
       ComponentSSLConfiguration.instance());
     
metricsRetrievalService.submitRequest(MetricsRetrievalService.MetricSourceType.JMX,
 streamProvider, jmxUri.toString());
-    return metricsRetrievalService.getCachedJMXMetric(jmxUri.toString());
+    return 
Optional.ofNullable(metricsRetrievalService.getCachedJMXMetric(jmxUri.toString()));
+  }
+
+  private URI jmxUrl(Cluster cluster, ServerSource serverSource) throws 
AmbariException {
+    return 
serverSource.getUri().resolve(config(cluster)).resolve(serverSource.getJmxInfo().getUrlSuffix());
   }
 
-  private List<Alert> alerts(AlertDefinition alertDef, MetricSource.JmxInfo 
jmxInfo, JMXMetricHolder jmxMetricHolder, Reporting reporting) throws 
AmbariException {
-    List<Object> metrics = jmxMetricHolder.findAll(jmxInfo.getPropertyList());
-    if (metrics.isEmpty()) {
-      return emptyList();
-    }
-    if (metrics.get(0) instanceof Number) {
-      Alert alert = reporting.alert(((Number) metrics.get(0)).doubleValue(), 
metrics, alertDef);
-      return singletonList(alert);
-    } else {
-      LOG.info("Unsupported metrics value: {} when running alert: {}", 
metrics.get(0), alertDef);
-      return emptyList();
-    }
+  private Map<String, Map<String, String>> config(Cluster cluster) throws 
AmbariException {
+    return configHelper.getEffectiveConfigProperties(cluster, 
configHelper.getEffectiveDesiredTags(cluster, null));
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
index 665430d..f1f21a4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
@@ -18,7 +18,10 @@
 package org.apache.ambari.server.state.alert;
 
 import java.util.HashSet;
+import java.util.List;
 
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
 import org.apache.commons.lang.StringUtils;
 
 import com.google.gson.annotations.SerializedName;
@@ -354,6 +357,16 @@ public class AlertDefinition {
   }
 
   /**
+   * Map the incoming value to {@link AlertState} and generate an alert with 
that state.
+   */
+  public Alert buildAlert(double value, List<Object> args) {
+    Reporting reporting = source.getReporting();
+    Alert alert = new Alert(name, null, serviceName, componentName, null, 
reporting.state(value));
+    alert.setText(reporting.formatMessage(value, args));
+    return alert;
+  }
+
+  /**
    * Gets equality based on name only.
    *
    * @see #deeplyEquals(Object)

http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricSource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricSource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricSource.java
index d7283fe..019f3b1 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricSource.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricSource.java
@@ -17,8 +17,16 @@
  */
 package org.apache.ambari.server.state.alert;
 
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.IntStream.range;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+
+import org.apache.ambari.server.controller.jmx.JMXMetricHolder;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
 
 import com.google.gson.annotations.SerializedName;
 
@@ -127,7 +135,7 @@ public class MetricSource extends Source {
     @SerializedName("property_list")
     private List<String> propertyList;
 
-    private String value;
+    private String value = "{0}";
 
     @SerializedName("url_suffix")
     private String urlSuffix = "/jmx";
@@ -136,8 +144,16 @@ public class MetricSource extends Source {
       return propertyList;
     }
 
-    public String getValue() {
-      return value;
+    public void setPropertyList(List<String> propertyList) {
+      this.propertyList = propertyList;
+    }
+
+    public void setValue(String value) {
+      this.value = value;
+    }
+
+    public Value getValue() {
+      return new Value(value);
     }
 
     @Override
@@ -159,5 +175,42 @@ public class MetricSource extends Source {
     public String getUrlSuffix() {
       return urlSuffix;
     }
+
+    public Optional<Number> eval(JMXMetricHolder jmxMetricHolder) {
+      List<Object> metrics = jmxMetricHolder.findAll(propertyList);
+      if (metrics.isEmpty()) {
+        return Optional.empty();
+      } else {
+        Object value = getValue().eval(metrics);
+        return value instanceof Number ? Optional.of((Number)value) : 
Optional.empty();
+      }
+    }
+  }
+
+  public static class Value {
+    private final String value;
+
+    public Value(String value) {
+      this.value = value;
+    }
+
+    /**
+     * Evaluate an expression like "{0}/({0} + {1}) * 100.0" where each 
positional argument represent a metrics value.
+     * The value is converted to SpEL syntax:
+     *  #var0/(#var0 + #var1) * 100.0
+     * then it is evaluated in the context of the metrics parameters.
+     */
+    public Object eval(List<Object> metrics) {
+      StandardEvaluationContext context = new StandardEvaluationContext();
+      context.setVariables(range(0, metrics.size()).boxed().collect(toMap(i -> 
"var" + i, metrics::get)));
+      return new SpelExpressionParser()
+        .parseExpression(value.replaceAll("(\\{(\\d+)\\})", "#var$2"))
+        .getValue(context);
+    }
+
+    @Override
+    public String toString() {
+      return value;
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
index 51d074e..a7e11e1 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
@@ -23,7 +23,6 @@ import java.text.MessageFormat;
 import java.util.List;
 
 import org.apache.ambari.server.alerts.Threshold;
-import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.AlertState;
 
 import com.google.gson.annotations.SerializedName;
@@ -208,16 +207,7 @@ public class Reporting {
     return true;
   }
 
-  /**
-   * Map the incoming value to {@link AlertState} and generate an alert with 
that state.
-   */
-  public Alert alert(double value, List<Object> args, AlertDefinition 
alertDef) {
-    Alert alert = new Alert(alertDef.getName(), null, 
alertDef.getServiceName(), alertDef.getComponentName(), null, state(value));
-    alert.setText(MessageFormat.format(message(value), args.toArray()));
-    return alert;
-  }
-
-  private AlertState state(double value) {
+  public AlertState state(double value) {
     return getThreshold().state(PERCENT == getType() ? value * 100 : value);
   }
 
@@ -225,6 +215,10 @@ public class Reporting {
     return new Threshold(getOk().getValue(), getWarning().getValue(), 
getCritical().getValue());
   }
 
+  public String formatMessage(double value, List<Object> args) {
+    return MessageFormat.format(message(value), args.toArray());
+  }
+
   private String message(double value) {
     switch (state(value)) {
       case OK:

http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
index 3ef2c48..e8ad651 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
@@ -457,7 +457,7 @@ public class AlertDefinitionResourceProviderTest {
 
     // JMX
     requestProps.put("AlertDefinition/source/jmx/value",
-        source.getJmxInfo().getValue());
+        source.getJmxInfo().getValue().toString());
     requestProps.put("AlertDefinition/source/jmx/property_list",
         source.getJmxInfo().getPropertyList());
 
@@ -600,7 +600,7 @@ public class AlertDefinitionResourceProviderTest {
 
     // JMX
     requestProps.put("AlertDefinition/source/jmx/value",
-        source.getJmxInfo().getValue());
+        source.getJmxInfo().getValue().toString());
     requestProps.put("AlertDefinition/source/jmx/property_list",
         source.getJmxInfo().getPropertyList());
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/b99b5ecb/ambari-server/src/test/java/org/apache/ambari/server/state/alert/JmxInfoTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/state/alert/JmxInfoTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/state/alert/JmxInfoTest.java
new file mode 100644
index 0000000..308ab4f
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/state/alert/JmxInfoTest.java
@@ -0,0 +1,68 @@
+package org.apache.ambari.server.state.alert;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+import java.util.Optional;
+
+import org.apache.ambari.server.controller.jmx.JMXMetricHolder;
+import org.junit.Test;
+
+public class JmxInfoTest {
+  private static final String JMX_PROP_NAME1 = 
"Hadoop:service=NameNode,name=FSNamesystem/CapacityUsed";
+  private static final String JMX_PROP_NAME2 = 
"Hadoop:service=NameNode,name=FSNamesystem/CapacityRemaining";
+
+  @Test
+  public void testFindJmxMetricsAndCalculateSimpleValue() throws Exception {
+    MetricSource.JmxInfo jmxInfo = jmxInfoWith("{1}");
+    JMXMetricHolder metrics = metrics(12.5, 3.5);
+    assertThat(jmxInfo.eval(metrics), is(Optional.of(3.5)));
+  }
+
+  @Test
+  public void testFindJmxMetricsAndCalculateComplexValue() throws Exception {
+    MetricSource.JmxInfo jmxInfo = jmxInfoWith("2 * ({0} + {1})");
+    JMXMetricHolder metrics = metrics(12.5, 2.5);
+    assertThat(jmxInfo.eval(metrics), is(Optional.of(30.0)));
+  }
+
+  @Test
+  public void testReturnsEmptyWhenJmxPropertyWasNotFound() throws Exception {
+    MetricSource.JmxInfo jmxInfo = new MetricSource.JmxInfo();
+    jmxInfo.setPropertyList(asList("notfound/notfound"));
+    JMXMetricHolder metrics = metrics(1, 2);
+    assertThat(jmxInfo.eval(metrics), is(Optional.empty()));
+  }
+
+  private MetricSource.JmxInfo jmxInfoWith(String value) {
+    MetricSource.JmxInfo jmxInfo = new MetricSource.JmxInfo();
+    jmxInfo.setValue(value);
+    jmxInfo.setPropertyList(asList(JMX_PROP_NAME1, JMX_PROP_NAME2));
+    return jmxInfo;
+  }
+
+  private JMXMetricHolder metrics(final double jmxValue1, final double 
jmxValue2) {
+    JMXMetricHolder metrics = new JMXMetricHolder();
+    metrics.setBeans(asList(
+      new HashMap<String,Object>() {{
+        put("name", name(JMX_PROP_NAME1));
+        put(key(JMX_PROP_NAME1), jmxValue1);
+      }},
+      new HashMap<String,Object>() {{
+        put("name", name(JMX_PROP_NAME2));
+        put(key(JMX_PROP_NAME2), jmxValue2);
+      }}
+    ));
+    return metrics;
+  }
+
+  private String name(String jmxProp) {
+    return jmxProp.split("/")[0];
+  }
+
+  private String key(String jmxProp) {
+    return jmxProp.split("/")[1];
+  }
+}
\ No newline at end of file

Reply via email to