You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by am...@apache.org on 2018/01/10 11:00:03 UTC

[ambari] 02/15: AMBARI-22115. Alerts for OneFS mpack (amagyar)

This is an automated email from the ASF dual-hosted git repository.

amagyar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git

commit f1758bc9f931b578bb63b345542551ef6be32427
Author: Attila Magyar <am...@hortonworks.com>
AuthorDate: Thu Oct 12 15:21:13 2017 +0200

    AMBARI-22115. Alerts for OneFS mpack (amagyar)
---
 ambari-server/docs/configuration/index.md          |   1 +
 .../ambari/server/alerts/JmxServerSideAlert.java   | 104 +++++++++++++++++++++
 .../org/apache/ambari/server/alerts/Threshold.java |  73 +++++++++++++++
 .../ambari/server/configuration/Configuration.java |  11 +++
 .../ambari/server/controller/ControllerModule.java |   1 +
 .../server/controller/jmx/JMXMetricHolder.java     |  27 ++++++
 .../java/org/apache/ambari/server/state/Alert.java |   1 +
 .../apache/ambari/server/state/alert/AlertUri.java |  41 ++++++++
 .../ambari/server/state/alert/MetricSource.java    |   7 ++
 .../ambari/server/state/alert/Reporting.java       |  43 +++++++++
 .../ambari/server/state/alert/ServerSource.java    |  15 +++
 .../state/services/AmbariServerAlertService.java   |   7 +-
 .../apache/ambari/server/alerts/ThresholdTest.java |  90 ++++++++++++++++++
 .../server/controller/jmx/JMXMetricHolderTest.java |  61 ++++++++++++
 .../ambari/server/state/alert/AlertUriTest.java    |  60 ++++++++++++
 15 files changed, 541 insertions(+), 1 deletion(-)

diff --git a/ambari-server/docs/configuration/index.md b/ambari-server/docs/configuration/index.md
index 9dbe9c4..8f1531b 100644
--- a/ambari-server/docs/configuration/index.md
+++ b/ambari-server/docs/configuration/index.md
@@ -57,6 +57,7 @@ The following are the properties which can be used to configure Ambari.
 | alerts.execution.scheduler.threadpool.size.core | The core number of threads used to process incoming alert events. The value should be increased as the size of the cluster increases. |`2` | 
 | alerts.execution.scheduler.threadpool.size.max | The number of threads used to handle alerts received from the Ambari Agents. The value should be increased as the size of the cluster increases. |`2` | 
 | alerts.execution.scheduler.threadpool.worker.size | The number of queued alerts allowed before discarding old alerts which have not been handled. The value should be increased as the size of the cluster increases. |`2000` | 
+| alerts.server.side.scheduler.threadpool.size.core | The core pool size of the executor service that runs server side alerts. |`4` | 
 | alerts.snmp.dispatcher.udp.port | The UDP port to use when binding the SNMP dispatcher on Ambari Server startup. If no port is specified, then a random port will be used. | | 
 | alerts.template.file | The full path to the XML file that describes the different alert templates. | | 
 | ambari.display.url | The URL to use when creating messages which should include the Ambari Server URL.<br/><br/>The following are examples of valid values:<ul><li>`http://ambari.apache.org:8080`</ul> | | 
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
new file mode 100644
index 0000000..a4b86f8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/JmxServerSideAlert.java
@@ -0,0 +1,104 @@
+/*
+ * 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.alerts;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
+import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.controller.jmx.JMXMetricHolder;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.Cluster;
+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;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+/**
+ * I represent a "SERVER" {@link org.apache.ambari.server.state.alert.SourceType} alert
+ * which can pull JMX metrics from a remote cluster.
+ */
+public class JmxServerSideAlert extends AlertRunnable {
+  private final static Logger LOG = LoggerFactory.getLogger(JmxServerSideAlert.class);
+  @Inject
+  private AlertDefinitionFactory definitionFactory;
+  @Inject
+  private MetricsRetrievalService metricsRetrievalService;
+  @Inject
+  private ConfigHelper configHelper;
+
+  public JmxServerSideAlert(String definitionName) {
+    super(definitionName);
+  }
+
+  @Override
+  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());
+  }
+
+  private URI jmxUrl(Cluster cluster, ServerSource serverSource) throws AmbariException {
+    return serverSource.getUri().resolve(config(cluster)).resolve(serverSource.getJmxInfo().getUrlSuffix());
+  }
+
+  private Map<String, Map<String, String>> config(Cluster cluster) throws AmbariException {
+    return configHelper.getEffectiveConfigProperties(cluster, configHelper.getEffectiveDesiredTags(cluster, null));
+  }
+
+  private JMXMetricHolder jmxMetric(ServerSource serverSource, URI jmxUri) {
+    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());
+  }
+
+  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();
+    }
+  }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/alerts/Threshold.java b/ambari-server/src/main/java/org/apache/ambari/server/alerts/Threshold.java
new file mode 100644
index 0000000..ae33eeb
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/Threshold.java
@@ -0,0 +1,73 @@
+/*
+ * 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.alerts;
+
+import javax.annotation.Nullable;
+
+import org.apache.ambari.server.state.AlertState;
+
+/**
+ * I'm a 3 level threshold where each level corresponds to ok, warn and critical levels.
+ * The levels are either increasing or decreasing numerical sequences.
+ * My main responsibility is to map incoming values to {@link AlertState} based on the threshold levels.
+ */
+public class Threshold {
+  private final Double okValue;
+  private final double warnValue;
+  private final double critValue;
+
+  public Threshold(@Nullable Double okValue, double warnValue, double critValue) {
+    this.okValue = okValue;
+    this.warnValue = warnValue;
+    this.critValue = critValue;
+  }
+
+  public AlertState state(double value) {
+    return directionUp() ? stateWhenDirectionUp(value) : stateWhenDirectionDown(value);
+  }
+
+  private boolean directionUp() {
+    return critValue >= warnValue;
+  }
+
+  private AlertState stateWhenDirectionUp(double value) {
+    if (value >= critValue) {
+      return AlertState.CRITICAL;
+    }
+    if (value >= warnValue) {
+      return AlertState.WARNING;
+    }
+    if (okValue == null || value >= okValue) {
+      return AlertState.OK;
+    }
+    return AlertState.UNKNOWN;
+  }
+
+  private AlertState stateWhenDirectionDown(double value) {
+    if (value <= critValue) {
+      return AlertState.CRITICAL;
+    }
+    if (value <= warnValue) {
+      return AlertState.WARNING;
+    }
+    if (okValue == null || value <= okValue) {
+      return AlertState.OK;
+    }
+    return AlertState.UNKNOWN;
+  }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index 7003c98..09d5799 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -2786,6 +2786,13 @@ public class Configuration {
   @Markdown(description = "Whether security password encryption is enabled or not. In case it is we store passwords in their own file(s); otherwise we store passwords in the Ambari credential store.")
   public static final ConfigurationProperty<Boolean> SECURITY_PASSWORD_ENCRYPTON_ENABLED = new ConfigurationProperty<Boolean>("security.passwords.encryption.enabled", false);
 
+  /**
+   * The core pool size of the executor service that runs server side alerts.
+   */
+  @Markdown(description = "The core pool size of the executor service that runs server side alerts.")
+  public static final ConfigurationProperty<Integer> SERVER_SIDE_ALERTS_CORE_POOL_SIZE = new ConfigurationProperty<>(
+          "alerts.server.side.scheduler.threadpool.size.core", 4);
+
 
   private static final Logger LOG = LoggerFactory.getLogger(
     Configuration.class);
@@ -6234,4 +6241,8 @@ public class Configuration {
   public String getAutoGroupCreation() {
     return getProperty(AUTO_GROUP_CREATION);
   }
+
+  public int getAlertServiceCorePoolSize() {
+    return Integer.parseInt(getProperty(SERVER_SIDE_ALERTS_CORE_POOL_SIZE));
+  }
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index ed7513f..2938b26 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -359,6 +359,7 @@ public class ControllerModule extends AbstractModule {
     // So it's an "additional time", given to stage to finish execution before
     // it is considered as timed out
     bindConstant().annotatedWith(Names.named("actionTimeout")).to(600000L);
+    bindConstant().annotatedWith(Names.named("alertServiceCorePoolSize")).to(configuration.getAlertServiceCorePoolSize());
 
     bindConstant().annotatedWith(Names.named("dbInitNeeded")).to(dbInitNeeded);
     bindConstant().annotatedWith(Names.named("statusCheckInterval")).to(5000L);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/jmx/JMXMetricHolder.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/jmx/JMXMetricHolder.java
index 81d72fb..f6ae54f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/jmx/JMXMetricHolder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/jmx/JMXMetricHolder.java
@@ -18,13 +18,18 @@
 
 package org.apache.ambari.server.controller.jmx;
 
+import static java.util.stream.Collectors.toList;
+
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  *
  */
 public final class JMXMetricHolder {
+  private static final String NAME_KEY = "name";
 
   private List<Map<String, Object>> beans;
 
@@ -47,4 +52,26 @@ public final class JMXMetricHolder {
     }
     return stringBuilder.toString();
   }
+
+  public List<Object> findAll(List<String> properties) {
+    return properties.stream()
+      .map(this::find)
+      .filter(Optional::isPresent)
+      .map(Optional::get)
+      .collect(toList());
+  }
+
+  public Optional<Object> find(String property) {
+    String propertyName = property.split("/")[0];
+    String propertyValue = property.split("/")[1];
+    return beans.stream()
+      .filter(each -> propertyName.equals(name(each)))
+      .map(each -> each.get(propertyValue))
+      .filter(Objects::nonNull)
+      .findFirst();
+  }
+
+  private String name(Map<String, Object> bean) {
+    return bean.containsKey(NAME_KEY) ? (String) bean.get(NAME_KEY) : null;
+  }
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java b/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
index 5d2ecc6..49bd5d2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
@@ -56,6 +56,7 @@ public class Alert {
     component = componentName;
     this.hostName = hostName;
     state = alertState;
+    timestamp = System.currentTimeMillis();
   }
 
   public Alert() {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertUri.java b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertUri.java
index 93801d5..b3ef2de 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertUri.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertUri.java
@@ -17,8 +17,13 @@
  */
 package org.apache.ambari.server.state.alert;
 
+import java.net.URI;
+import java.util.Map;
 import java.util.Set;
 
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.state.kerberos.VariableReplacementHelper;
+
 import com.google.gson.annotations.SerializedName;
 
 /**
@@ -87,6 +92,13 @@ public class AlertUri {
   @SerializedName("connection_timeout")
   private float m_connectionTimeout = 5.0f;
 
+
+  /**
+   * An optional read timeout value for connections.
+   */
+  @SerializedName("read_timeout")
+  private float readTimeout = 15.0f;
+
   /**
    * If present, then the component supports HA mode and the properties
    * contained within need to be checked to see if an HA URI is required to be
@@ -117,6 +129,18 @@ public class AlertUri {
     m_httpUri = httpUri;
   }
 
+  public void setHttpsUri(String httpsUri) {
+    this.m_httpsUri = httpsUri;
+  }
+
+  public void setHttpsPropertyValue(String m_httpsPropertyValue) {
+    this.m_httpsPropertyValue = m_httpsPropertyValue;
+  }
+
+  public void setHttpsProperty(String m_httpsProperty) {
+    this.m_httpsProperty = m_httpsProperty;
+  }
+
   /**
    * Gets the default port to use on the host running the alert if none of the
    * http properties are available.
@@ -276,6 +300,23 @@ public class AlertUri {
     }
   }
 
+  public URI resolve(Map<String, Map<String, String>> config) throws AmbariException {
+    VariableReplacementHelper variableReplacer = new VariableReplacementHelper();
+    String httpsProperty = variableReplacer.replaceVariables(m_httpsProperty, config);
+    String httpsPropertyValue = variableReplacer.replaceVariables(m_httpsPropertyValue, config);
+    return httpsProperty == null || !httpsProperty.equals(httpsPropertyValue)
+      ? URI.create(String.format("http://%s", variableReplacer.replaceVariables(m_httpUri, config)))
+      : URI.create(String.format("https://%s", variableReplacer.replaceVariables(m_httpsUri, config)));
+  }
+
+  public int getConnectionTimeoutMsec() {
+    return (int) m_connectionTimeout * 1000;
+  }
+
+  public int getReadTimeoutMsec() {
+    return (int) readTimeout * 1000;
+  }
+
   /**
    * {@inheritDoc}
    */
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 11eee05..d7283fe 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
@@ -129,6 +129,9 @@ public class MetricSource extends Source {
 
     private String value;
 
+    @SerializedName("url_suffix")
+    private String urlSuffix = "/jmx";
+
     public List<String> getPropertyList() {
       return propertyList;
     }
@@ -152,5 +155,9 @@ public class MetricSource extends Source {
       return list1.equals(list2);
 
     }
+
+    public String getUrlSuffix() {
+      return urlSuffix;
+    }
   }
 }
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 4aeba45..51d074e 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
@@ -17,6 +17,15 @@
  */
 package org.apache.ambari.server.state.alert;
 
+import static org.apache.ambari.server.state.alert.Reporting.ReportingType.PERCENT;
+
+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;
 
 /**
@@ -200,6 +209,40 @@ public class Reporting {
   }
 
   /**
+   * 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) {
+    return getThreshold().state(PERCENT == getType() ? value * 100 : value);
+  }
+
+  private Threshold getThreshold() {
+    return new Threshold(getOk().getValue(), getWarning().getValue(), getCritical().getValue());
+  }
+
+  private String message(double value) {
+    switch (state(value)) {
+      case OK:
+        return getOk().getText();
+      case WARNING:
+        return getWarning().getText();
+      case CRITICAL:
+        return getCritical().getText();
+      case UNKNOWN:
+        return "Unknown";
+      case SKIPPED:
+        return "Skipped";
+      default:
+        throw new IllegalStateException("Invalid alert state: " + state(value));
+    }
+  }
+
+  /**
    * The {@link ReportTemplate} class is used to pair a label and threshhold
    * value.
    * <p/>
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
index c58867a..765bdea 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
@@ -28,6 +28,13 @@ public class ServerSource extends ParameterizedSource {
   @SerializedName("class")
   private String m_class;
 
+  @SerializedName("uri")
+  private AlertUri uri = null;
+
+  @SerializedName("jmx")
+  private MetricSource.JmxInfo jmxInfo = null;
+
+
   /**
    * Gets the fully qualified classname specified in the source.
    */
@@ -35,6 +42,14 @@ public class ServerSource extends ParameterizedSource {
     return m_class;
   }
 
+  public MetricSource.JmxInfo getJmxInfo() {
+    return jmxInfo;
+  }
+
+  public AlertUri getUri() {
+    return uri;
+  }
+
   /**
    * {@inheritDoc}
    */
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java b/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
index 305f693..6cce1b2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
@@ -45,6 +45,7 @@ import com.google.common.util.concurrent.AbstractScheduledService;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Provider;
+import com.google.inject.name.Named;
 
 /**
  * The {@link AmbariServerAlertService} is used to manage the dynamically loaded
@@ -87,7 +88,7 @@ public class AmbariServerAlertService extends AbstractScheduledService {
   /**
    * The executor to use to run all {@link Runnable} alert classes.
    */
-  private final ScheduledExecutorService m_scheduledExecutorService = Executors.newScheduledThreadPool(3);
+  private ScheduledExecutorService m_scheduledExecutorService;
 
   /**
    * A map of all of the definition names to {@link ScheduledFuture}s.
@@ -101,6 +102,10 @@ public class AmbariServerAlertService extends AbstractScheduledService {
   public AmbariServerAlertService() {
   }
 
+  @Inject
+  public void initExecutor(@Named("alertServiceCorePoolSize") int alertServiceCorePoolSize) {
+    this.m_scheduledExecutorService = Executors.newScheduledThreadPool(alertServiceCorePoolSize);
+  }
   /**
    * {@inheritDoc}
    */
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/alerts/ThresholdTest.java b/ambari-server/src/test/java/org/apache/ambari/server/alerts/ThresholdTest.java
new file mode 100644
index 0000000..07bfa03
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/alerts/ThresholdTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.alerts;
+
+import static org.apache.ambari.server.state.AlertState.CRITICAL;
+import static org.apache.ambari.server.state.AlertState.OK;
+import static org.apache.ambari.server.state.AlertState.UNKNOWN;
+import static org.apache.ambari.server.state.AlertState.WARNING;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import org.apache.ambari.server.state.AlertState;
+import org.junit.Test;
+
+public class ThresholdTest {
+  @Test
+  public void testBetweenOkAndWarnIsOk_dirUp() throws Exception {
+    Threshold threshold = new Threshold(10.0, 20.0, 30.0);
+    assertState(threshold, OK, 10, 15, 19);
+  }
+
+  @Test
+  public void testBetweenWarnAndCritIsWarn_dirUp() throws Exception {
+    Threshold threshold = new Threshold(10.0, 20.0, 30.0);
+    assertState(threshold, WARNING, 20, 25, 29);
+  }
+
+  @Test
+  public void testAboveCritIsCrit_dirUp() throws Exception {
+    Threshold threshold = new Threshold(10.0, 20.0, 30.0);
+    assertState(threshold, CRITICAL, 30, 40, 99999);
+  }
+
+  @Test
+  public void testBelowOkIsUnknown_dirUp() throws Exception {
+    Threshold threshold = new Threshold(10.0, 20, 30);
+    assertState(threshold, UNKNOWN, 9, 2, -99999);
+  }
+
+  @Test
+  public void testBelowCritIsCrit_dirDown() throws Exception {
+    Threshold threshold = new Threshold(40.0, 30.0, 20.0);
+    assertState(threshold, CRITICAL, 20, 15, 2, -99999);
+  }
+
+  @Test
+  public void testBetweenWarnAndCritIsWarn_dirDown() throws Exception {
+    Threshold threshold = new Threshold(40.0, 30.0, 20.0);
+    assertState(threshold, WARNING, 30, 25, 21);
+  }
+
+  @Test
+  public void testBetweenOkAndWarnIsOk_dirDown() throws Exception {
+    Threshold threshold = new Threshold(40.0, 30.0, 20.0);
+    assertState(threshold, OK, 40, 35, 31);
+  }
+
+  @Test
+  public void testAboveOkIsUnknown_dirDown() throws Exception {
+    Threshold threshold = new Threshold(40.0, 30.0, 20.0);
+    assertState(threshold, UNKNOWN, 41, 50, 9999);
+  }
+
+  @Test
+  public void testOkIsOptional() throws Exception {
+    Threshold threshold = new Threshold(null, 20.0, 30.0);
+    assertState(threshold, OK, 10, 15, 19);
+  }
+
+  private void assertState(Threshold threshold, AlertState expectedState, int... values) {
+    for (int value: values) {
+      assertThat(expectedState, is(threshold.state(value)));
+    }
+  }
+}
\ No newline at end of file
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/jmx/JMXMetricHolderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/jmx/JMXMetricHolderTest.java
new file mode 100644
index 0000000..61d5bae
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/jmx/JMXMetricHolderTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jmx;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class JMXMetricHolderTest {
+  private JMXMetricHolder metrics = new JMXMetricHolder();
+
+  @Before
+  public void setUp() {
+    metrics.setBeans(asList(
+      new HashMap<String, Object>() {{
+        put("name", "bean1");
+        put("value", "val1");
+      }},
+      new HashMap<String, Object>() {{
+        put("name", "bean2");
+        put("value", "val2");
+      }}
+    ));
+  }
+
+  @Test
+  public void testFindSingleBeanByName() throws Exception {
+    assertThat(metrics.find("bean1/value"), is(Optional.of("val1")));
+    assertThat(metrics.find("bean2/value"), is(Optional.of("val2")));
+    assertThat(metrics.find("bean3/notfound"), is(Optional.empty()));
+  }
+
+  @Test
+  public void testFindMultipleBeansByName() throws Exception {
+    List<Object> result = metrics.findAll(asList("bean1/value", "bean2/value", "bean3/notfound"));
+    assertThat(result, hasItems("val1", "val2"));
+  }
+}
\ No newline at end of file
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alert/AlertUriTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alert/AlertUriTest.java
new file mode 100644
index 0000000..295e364
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alert/AlertUriTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.state.alert;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+public class AlertUriTest {
+  @Test
+  public void testChoosesHttpByDefault() throws Exception {
+    AlertUri uri = new AlertUri();
+    uri.setHttpUri("${config1/http-host}/path");
+    assertThat(resolved(uri), is("http://http-host/path"));
+  }
+
+  @Test
+  public void testChoosesHttpsBasedOnProperties() throws Exception {
+    AlertUri uri = new AlertUri();
+    uri.setHttpUri("${config1/http-host}/path");
+    uri.setHttpsUri("${config1/https-host}/path");
+    uri.setHttpsProperty("${config1/use-http}");
+    uri.setHttpsPropertyValue("YES");
+    assertThat(resolved(uri), is("https://https-host/path"));
+  }
+
+  private Map<String, Map<String, String>> config() {
+    return new HashMap<String, Map<String, String>>() {{
+        put("config1", new HashMap<String, String>() {{
+          put("http-host", "http-host");
+          put("https-host", "https-host");
+          put("use-http", "YES");
+        }});
+      }};
+  }
+
+  private String resolved(AlertUri uri) throws AmbariException {
+    return uri.resolve(config()).toString();
+  }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@ambari.apache.org" <co...@ambari.apache.org>.