AMBARI-22236. Expression parser support for JMXServerSide alerts (amagyar)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ae45a551 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ae45a551 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ae45a551 Branch: refs/heads/branch-feature-AMBARI-22008 Commit: ae45a551f2b1029285e891772a3580ae741f1efb Parents: 362b4f0 Author: Attila Magyar <amag...@hortonworks.com> Authored: Tue Oct 17 16:15:15 2017 +0200 Committer: Attila Magyar <amag...@hortonworks.com> Committed: Wed Nov 22 10:50:40 2017 +0100 ---------------------------------------------------------------------- .../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/ae45a551/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/ae45a551/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/ae45a551/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/ae45a551/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/ae45a551/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/ae45a551/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