AMBARI-6915 - Alerts: Change AlertDefinition to Support a Reporting Member 
(jonathanhurley)


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

Branch: refs/heads/branch-alerts-dev
Commit: 0ac9cb3facaff211c5c679d609f59aaca633d267
Parents: 72ebd26
Author: Jonathan Hurley <jhur...@hortonworks.com>
Authored: Tue Aug 19 10:14:30 2014 -0400
Committer: Jonathan Hurley <jhur...@hortonworks.com>
Committed: Wed Aug 20 10:54:28 2014 -0400

----------------------------------------------------------------------
 .../server/api/services/AmbariMetaInfo.java     | 16 +---
 .../AlertDefinitionResourceProvider.java        | 63 ++++++++++++--
 .../server/state/alert/AggregateSource.java     | 38 ++++++++
 .../server/state/alert/AlertDefinition.java     | 17 ++++
 .../state/alert/AlertDefinitionFactory.java     | 84 +++++++++++++++---
 .../server/state/alert/PercentSource.java       | 76 ++++++++++++++++
 .../ambari/server/state/alert/PortSource.java   | 46 ++++++++++
 .../ambari/server/state/alert/Reporting.java    | 92 ++++++++++++++++++++
 .../ambari/server/state/alert/ScriptSource.java | 36 ++++++++
 .../ambari/server/state/alert/Source.java       | 13 ++-
 .../ambari/server/state/alert/SourceType.java   |  9 +-
 .../server/api/services/AmbariMetaInfoTest.java | 51 +++++++++++
 .../AlertDefinitionResourceProviderTest.java    | 89 +++++++++++++++++--
 .../stacks/HDP/2.0.5/services/HDFS/alerts.json  | 61 ++++++++-----
 14 files changed, 626 insertions(+), 65 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
index 3347a77..2eec33b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
@@ -31,7 +31,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Scanner;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -1093,19 +1092,6 @@ public class AmbariMetaInfo {
       return null;
     }
 
-    Set<AlertDefinition> defs = new HashSet<AlertDefinition>();
-    Map<String, List<AlertDefinition>> map = 
alertDefinitionFactory.getAlertDefinitions(alertsFile);
-
-    for (Entry<String, List<AlertDefinition>> entry : map.entrySet()) {
-      for (AlertDefinition ad : entry.getValue()) {
-        ad.setServiceName(serviceName);
-        if (!entry.getKey().equals("service")) {
-          ad.setComponentName(entry.getKey());
-        }
-      }
-      defs.addAll(entry.getValue());
-    }
-
-    return defs;
+    return alertDefinitionFactory.getAlertDefinitions(alertsFile, serviceName);
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
index f20a9a9..bed25e7 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
@@ -61,18 +61,26 @@ import com.google.inject.Injector;
  */
 public class AlertDefinitionResourceProvider extends 
AbstractControllerResourceProvider {
 
+  protected static final String ALERT_DEF = "AlertDefinition";
+
   protected static final String ALERT_DEF_CLUSTER_NAME = 
"AlertDefinition/cluster_name";
   protected static final String ALERT_DEF_ID = "AlertDefinition/id";
   protected static final String ALERT_DEF_NAME = "AlertDefinition/name";
   protected static final String ALERT_DEF_LABEL = "AlertDefinition/label";
   protected static final String ALERT_DEF_INTERVAL = 
"AlertDefinition/interval";
-  protected static final String ALERT_DEF_SOURCE_TYPE = 
"AlertDefinition/source/type";
-  protected static final String ALERT_DEF_SOURCE = "AlertDefinition/source";
   protected static final String ALERT_DEF_SERVICE_NAME = 
"AlertDefinition/service_name";
   protected static final String ALERT_DEF_COMPONENT_NAME = 
"AlertDefinition/component_name";
   protected static final String ALERT_DEF_ENABLED = "AlertDefinition/enabled";
   protected static final String ALERT_DEF_SCOPE = "AlertDefinition/scope";
 
+  protected static final String ALERT_DEF_SOURCE = "AlertDefinition/source";
+  protected static final String ALERT_DEF_SOURCE_TYPE = 
"AlertDefinition/source/type";
+  protected static final String ALERT_DEF_SOURCE_REPORTING = 
"AlertDefinition/source/reporting";
+  protected static final String ALERT_DEF_SOURCE_REPORTING_OK = 
"AlertDefinition/source/reporting/ok";
+  protected static final String ALERT_DEF_SOURCE_REPORTING_WARNING = 
"AlertDefinition/source/reporting/warning";
+  protected static final String ALERT_DEF_SOURCE_REPORTING_CRITICAL = 
"AlertDefinition/source/reporting/critical";
+
+
   private static Set<String> pkPropertyIds = new HashSet<String>(
       Arrays.asList(ALERT_DEF_ID, ALERT_DEF_NAME));
 
@@ -176,21 +184,64 @@ public class AlertDefinitionResourceProvider extends 
AbstractControllerResourceP
               SourceType.class)));
     }
 
-    JsonObject jsonObj = new JsonObject();
+    // !!! Alert structures contain nested objects; reconstruct a valid
+    // JSON from the flat, exploded properties so that a Source instance can
+    // be properly persisted
+    JsonObject source = new JsonObject();
+    JsonObject reporting = new JsonObject();
+    JsonObject reportingOk = new JsonObject();
+    JsonObject reportingWarning = new JsonObject();
+    JsonObject reportingCritical = new JsonObject();
 
     for (Entry<String, Object> entry : requestMap.entrySet()) {
       String propCat = PropertyHelper.getPropertyCategory(entry.getKey());
       String propName = PropertyHelper.getPropertyName(entry.getKey());
 
+      if (propCat.equals(ALERT_DEF) && "source".equals(propName)) {
+        source.addProperty(propName, entry.getValue().toString());
+      }
+
       if (propCat.equals(ALERT_DEF_SOURCE)) {
-        jsonObj.addProperty(propName, entry.getValue().toString());
+        source.addProperty(propName, entry.getValue().toString());
+      }
+
+      if (propCat.equals(ALERT_DEF_SOURCE_REPORTING)) {
+        reporting.addProperty(propName, entry.getValue().toString());
+      }
+
+      if (propCat.equals(ALERT_DEF_SOURCE_REPORTING_OK)) {
+        reportingOk.addProperty(propName, entry.getValue().toString());
+      }
+
+      if (propCat.equals(ALERT_DEF_SOURCE_REPORTING_WARNING)) {
+        reportingWarning.addProperty(propName, entry.getValue().toString());
+      }
+
+      if (propCat.equals(ALERT_DEF_SOURCE_REPORTING_CRITICAL)) {
+        reportingCritical.addProperty(propName, entry.getValue().toString());
       }
     }
 
-    if (0 == jsonObj.entrySet().size()) {
+    if (0 == source.entrySet().size()) {
       throw new IllegalArgumentException("Source must be specified");
     }
 
+    if (reportingOk.entrySet().size() > 0) {
+      reporting.add("ok", reportingOk);
+    }
+
+    if (reportingWarning.entrySet().size() > 0) {
+      reporting.add("warning", reportingWarning);
+    }
+
+    if (reportingCritical.entrySet().size() > 0) {
+      reporting.add("critical", reportingCritical);
+    }
+
+    if (reporting.entrySet().size() > 0) {
+      source.add("reporting", reporting);
+    }
+
     Cluster cluster = 
getManagementController().getClusters().getCluster(clusterName);
 
     AlertDefinitionEntity entity = new AlertDefinitionEntity();
@@ -207,7 +258,7 @@ public class AlertDefinitionResourceProvider extends 
AbstractControllerResourceP
     entity.setScheduleInterval(interval);
     entity.setServiceName((String) requestMap.get(ALERT_DEF_SERVICE_NAME));
     entity.setSourceType((String) requestMap.get(ALERT_DEF_SOURCE_TYPE));
-    entity.setSource(jsonObj.toString());
+    entity.setSource(source.toString());
 
     Scope scope = null;
     String desiredScope = (String) requestMap.get(ALERT_DEF_SCOPE);

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AggregateSource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AggregateSource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AggregateSource.java
new file mode 100644
index 0000000..d056c40
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AggregateSource.java
@@ -0,0 +1,38 @@
+/**
+ * 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 com.google.gson.annotations.SerializedName;
+
+/**
+ * Alert when the source type is defined as {@link SourceType#AGGREGATE}.
+ * Aggregate alerts are alerts that are triggered by collecting the states of
+ * all instances of the defined alert and calculating the overall state.
+ */
+public class AggregateSource extends Source {
+
+  @SerializedName("alert_name")
+  private String m_alertName = null;
+
+  /**
+   * @return the unique name of the alert that will have its values aggregated.
+   */
+  public String getAlertName() {
+    return m_alertName;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/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 8d9b3c2..15f4bfe 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
@@ -31,6 +31,7 @@ public class AlertDefinition {
   private int interval = 1;
   private boolean enabled = true;
   private Source source = null;
+  private String label = null;
 
   /**
    * @return the service name
@@ -117,6 +118,22 @@ public class AlertDefinition {
     source = definitionSource;
   }
 
+  /**
+   * @return the label for the definition or {@code null} if none.
+   */
+  public String getLabel() {
+    return label;
+  }
+
+  /**
+   * Sets the label for this definition.
+   *
+   * @param definitionLabel
+   */
+  public void setLabel(String definitionLabel) {
+    label = definitionLabel;
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (null == obj || !obj.getClass().equals(AlertDefinition.class)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinitionFactory.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinitionFactory.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinitionFactory.java
index 1775f88..5ddb521 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinitionFactory.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinitionFactory.java
@@ -20,8 +20,11 @@ package org.apache.ambari.server.state.alert;
 import java.io.File;
 import java.io.FileReader;
 import java.lang.reflect.Type;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
@@ -52,34 +55,58 @@ public class AlertDefinitionFactory {
   /**
    * Builder used for type adapter registration.
    */
-  private final GsonBuilder m_builder = new GsonBuilder().registerTypeAdapter(
-      Source.class, new AlertDefinitionSourceAdapter());
+  private final GsonBuilder m_builder = new GsonBuilder();
 
   /**
    * Thread safe deserializer.
    */
-  private final Gson m_gson = m_builder.create();
+  private final Gson m_gson;
 
+  /**
+   * Constructor.
+   */
+  public AlertDefinitionFactory() {
+    m_builder.registerTypeAdapter(Source.class,
+        new AlertDefinitionSourceAdapter());
+
+    m_gson = m_builder.create();
+  }
 
   /**
    * Gets a list of all of the alert definitions defined in the specified JSON
-   * {@link File}.
+   * {@link File} for the given service.
    *
    * @param alertDefinitionFile
+   * @param serviceName
    * @return
    * @throws AmbariException
    *           if there was a problem reading the file or parsing the JSON.
    */
-  public Map<String, List<AlertDefinition>> getAlertDefinitions(
-      File alertDefinitionFile) throws AmbariException {
+  public Set<AlertDefinition> getAlertDefinitions(File alertDefinitionFile,
+      String serviceName) throws AmbariException {
+    Map<String,List<AlertDefinition>> definitionMap = null;
+
     try {
       Type type = new TypeToken<Map<String, 
List<AlertDefinition>>>(){}.getType();
 
-      return m_gson.fromJson(new FileReader(alertDefinitionFile), type);
+      definitionMap = m_gson.fromJson(new FileReader(alertDefinitionFile), 
type);
     } catch (Exception e) {
       LOG.error("Could not read the alert definition file", e);
       throw new AmbariException("Could not read alert definition file", e);
     }
+
+    Set<AlertDefinition> definitions = new HashSet<AlertDefinition>();
+    for (Entry<String, List<AlertDefinition>> entry : 
definitionMap.entrySet()) {
+      for (AlertDefinition ad : entry.getValue()) {
+        ad.setServiceName(serviceName);
+        if (!entry.getKey().equals("service")) {
+          ad.setComponentName(entry.getKey());
+        }
+      }
+      definitions.addAll(entry.getValue());
+    }
+
+    return definitions;
   }
 
   /**
@@ -103,6 +130,7 @@ public class AlertDefinitionFactory {
     definition.setName(entity.getDefinitionName());
     definition.setScope(entity.getScope());
     definition.setServiceName(entity.getServiceName());
+    definition.setLabel(entity.getLabel());
 
     try{
       String sourceJson = entity.getSource();
@@ -118,6 +146,16 @@ public class AlertDefinitionFactory {
   }
 
   /**
+   * Gets an instance of {@link Gson} that can correctly serialize and
+   * deserialize an {@link AlertDefinition}.
+   *
+   * @return a {@link Gson} instance (not {@code null}).
+   */
+  public Gson getGson() {
+    return m_gson;
+  }
+
+  /**
    * Deserializes {@link Source} implementations.
    */
   private static final class AlertDefinitionSourceAdapter implements 
JsonDeserializer<Source>{
@@ -130,21 +168,41 @@ public class AlertDefinitionFactory {
       JsonObject jsonObj = (JsonObject) json;
 
       SourceType type = SourceType.valueOf(jsonObj.get("type").getAsString());
-      Class<? extends Source> cls = null;
+      Class<? extends Source> clazz = null;
 
       switch (type) {
-        case METRIC:
-          cls = MetricSource.class;
+        case METRIC:{
+          clazz = MetricSource.class;
+          break;
+        }
+        case PORT:{
+          clazz = PortSource.class;
           break;
+        }
+        case SCRIPT: {
+          clazz = ScriptSource.class;
+          break;
+        }
+        case AGGREGATE: {
+          clazz = AggregateSource.class;
+          break;
+        }
+        case PERCENT: {
+          clazz = PercentSource.class;
+          break;
+        }
         default:
           break;
       }
 
-      if (null != cls) {
-        return context.deserialize(json, cls);
-      } else {
+      if (null == clazz) {
+        LOG.warn(
+            "Unable to deserialize an alert definition with source type {}",
+            type);
         return null;
       }
+
+      return context.deserialize(json, clazz);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PercentSource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PercentSource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PercentSource.java
new file mode 100644
index 0000000..ef79cfd
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PercentSource.java
@@ -0,0 +1,76 @@
+/**
+ * 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 com.google.gson.annotations.SerializedName;
+
+/**
+ * Alert when the source type is defined as {@link SourceType#PERCENT}
+ */
+public class PercentSource extends Source {
+
+  @SerializedName("numerator")
+  private MetricFractionPart m_numerator = null;
+
+  @SerializedName("denominator")
+  private MetricFractionPart m_denominator = null;
+
+  /**
+   * Gets the numerator for the percent calculation.
+   * 
+   * @return a metric value representing the numerator (never {@code null}).
+   */
+  public MetricFractionPart getNumerator() {
+    return m_numerator;
+  }
+
+  /**
+   * Gets the denomintor for the percent calculation.
+   *
+   * @return a metric value representing the denominator (never {@code null}).
+   */
+  public MetricFractionPart getDenominator() {
+    return m_denominator;
+  }
+
+  /**
+   * The {@link MetricFractionPart} class represents either the numerator or 
the
+   * denominator of a fraction.
+   */
+  public static final class MetricFractionPart {
+    @SerializedName("jmx")
+    private String m_jmxInfo = null;
+
+    @SerializedName("ganglia")
+    private String m_gangliaInfo = null;
+
+    /**
+     * @return the jmx info, if this metric is jmx-based
+     */
+    public String getJmxInfo() {
+      return m_jmxInfo;
+    }
+
+    /**
+     * @return the ganglia info, if this metric is ganglia-based
+     */
+    public String getGangliaInfo() {
+      return m_gangliaInfo;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PortSource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PortSource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PortSource.java
new file mode 100644
index 0000000..afb60dd
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/PortSource.java
@@ -0,0 +1,46 @@
+/**
+ * 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 com.google.gson.annotations.SerializedName;
+
+/**
+ * Alert when the source type is defined as {@link SourceType#PORT}
+ */
+public class PortSource extends Source {
+
+  @SerializedName("uri")
+  private String m_uri = null;
+
+  @SerializedName("port")
+  private int m_port = 0;
+
+  /**
+   * @return the URI to check for a valid port
+   */
+  public String getUri() {
+    return m_uri;
+  }
+
+  /**
+   * @return the port to check on the given URI.
+   */
+  public int getPort() {
+    return m_port;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/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
new file mode 100644
index 0000000..7e63d43
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Reporting.java
@@ -0,0 +1,92 @@
+/**
+ * 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 com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link Reporting} class represents the OK/WARNING/CRITICAL structures in
+ * an {@link AlertDefinition}.
+ */
+public class Reporting {
+
+  /**
+   *
+   */
+  @SerializedName("ok")
+  private ReportTemplate m_ok;
+
+  /**
+   *
+   */
+  @SerializedName("warning")
+  private ReportTemplate m_warning;
+
+  /**
+   *
+   */
+  @SerializedName("critical")
+  private ReportTemplate m_critical;
+
+  /**
+   * @return the WARNING structure or {@code null} if none.
+   */
+  public ReportTemplate getWarning() {
+    return m_warning;
+  }
+
+  /**
+   * @return the CRITICAL structure or {@code null} if none.
+   */
+  public ReportTemplate getCritical() {
+    return m_critical;
+  }
+
+  /**
+   * @return the OK structure or {@code null} if none.
+   */
+  public ReportTemplate getOk() {
+    return m_ok;
+  }
+
+  /**
+   * The {@link ReportTemplate} class is used to pair a label and threshhold
+   * value.
+   */
+  public static final class ReportTemplate {
+    @SerializedName("text")
+    private String m_text;
+
+    @SerializedName("value")
+    private Double m_value = null;
+
+    /**
+     * @return the parameterized text of this template or {@code null} if none.
+     */
+    public String getText() {
+      return m_text;
+    }
+
+    /**
+     * @return the threshold value for this template or {@code null} if none.
+     */
+    public Double getValue() {
+      return m_value;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
new file mode 100644
index 0000000..13a6057
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
@@ -0,0 +1,36 @@
+/**
+ * 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 com.google.gson.annotations.SerializedName;
+
+/**
+ * Alert when the source type is defined as {@link SourceType#SCRIPT}
+ */
+public class ScriptSource extends Source {
+
+  @SerializedName("path")
+  private String m_path = null;
+
+  /**
+   * @return the path to the script file.
+   */
+  public String getPath() {
+    return m_path;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Source.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Source.java 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Source.java
index f64b7d2..cdce41c 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Source.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/Source.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.state.alert;
 
+import com.google.gson.annotations.SerializedName;
+
 /**
  * Abstract class that all known alert sources should extend.
  */
@@ -24,11 +26,20 @@ public abstract class Source {
 
   private SourceType type;
 
+  @SerializedName("reporting")
+  private Reporting reporting;
+
   /**
    * @return the type
    */
   public SourceType getType() {
     return type;
   }
-  
+
+  /**
+   * @return
+   */
+  public Reporting getReporting() {
+    return reporting;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
index 8289d6f..18c13bd 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
@@ -18,7 +18,7 @@
 package org.apache.ambari.server.state.alert;
 
 /**
- * Source type refers to how the alert is to be collected. 
+ * Source type refers to how the alert is to be collected.
  */
 public enum SourceType {
   /**
@@ -36,5 +36,10 @@ public enum SourceType {
   /**
    * Source is an aggregate of a collection of other alert states
    */
-  AGGREGATE
+  AGGREGATE,
+
+  /**
+   * Source is a ratio of two {@link #METRIC} values.
+   */
+  PERCENT;
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
index 68cbc92..6b50e16 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
@@ -29,6 +29,7 @@ import java.io.File;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -54,6 +55,8 @@ import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.Stack;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.alert.AlertDefinition;
+import org.apache.ambari.server.state.alert.Reporting;
+import org.apache.ambari.server.state.alert.Source;
 import org.apache.ambari.server.state.stack.MetricDefinition;
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
@@ -1420,5 +1423,53 @@ public class AmbariMetaInfoTest {
     Assert.assertNotNull(set);
     Assert.assertTrue(set.size() > 0);
 
+    // find two different definitions and test each one
+    AlertDefinition nameNodeProcess = null;
+    AlertDefinition nameNodeCpu = null;
+
+    Iterator<AlertDefinition> iterator = set.iterator();
+    while (iterator.hasNext()) {
+      AlertDefinition definition = iterator.next();
+      if (definition.getName().equals("namenode_process")) {
+        nameNodeProcess = definition;
+      }
+
+      if (definition.getName().equals("namenode_cpu")) {
+        nameNodeCpu = definition;
+      }
+    }
+
+    assertNotNull(nameNodeProcess);
+    assertNotNull(nameNodeCpu);
+
+    assertEquals("NameNode host CPU Utilization", nameNodeCpu.getLabel());
+
+    Source source = nameNodeProcess.getSource();
+    assertNotNull(source);
+
+    // test namenode_process
+    Reporting reporting = source.getReporting();
+    assertNotNull(reporting);
+    assertNotNull(reporting.getOk());
+    assertNotNull(reporting.getOk().getText());
+    assertNull(reporting.getOk().getValue());
+    assertNotNull(reporting.getCritical());
+    assertNotNull(reporting.getCritical().getText());
+    assertNull(reporting.getCritical().getValue());
+    assertNull(reporting.getWarning());
+
+    // test namenode_cpu
+    source = nameNodeCpu.getSource();
+    reporting = source.getReporting();
+    assertNotNull(reporting);
+    assertNotNull(reporting.getOk());
+    assertNotNull(reporting.getOk().getText());
+    assertNull(reporting.getOk().getValue());
+    assertNotNull(reporting.getCritical());
+    assertNotNull(reporting.getCritical().getText());
+    assertNotNull(reporting.getCritical().getValue());
+    assertNotNull(reporting.getWarning());
+    assertNotNull(reporting.getWarning().getText());
+    assertNotNull(reporting.getWarning().getValue());
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/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 333f674..7df999e 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
@@ -29,10 +29,12 @@ import static org.easymock.EasyMock.resetToStrict;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 
+import java.io.File;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -44,18 +46,24 @@ import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.alert.AlertDefinition;
+import org.apache.ambari.server.state.alert.AlertDefinitionFactory;
 import org.apache.ambari.server.state.alert.AlertDefinitionHash;
+import org.apache.ambari.server.state.alert.Source;
+import org.apache.ambari.server.state.alert.SourceType;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.gson.Gson;
 import com.google.inject.Binder;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -69,6 +77,7 @@ public class AlertDefinitionResourceProviderTest {
 
   private AlertDefinitionDAO dao = null;
   private AlertDefinitionHash definitionHash = null;
+  private AlertDefinitionFactory m_factory = new AlertDefinitionFactory();
   private Injector m_injector;
 
   private static String DEFINITION_UUID = UUID.randomUUID().toString();
@@ -82,6 +91,7 @@ public class AlertDefinitionResourceProviderTest {
         new InMemoryDefaultTestModule()).with(new MockModule()));
 
     AlertDefinitionResourceProvider.init(m_injector);
+    m_injector.injectMembers(m_factory);
   }
 
   /**
@@ -149,6 +159,7 @@ public class AlertDefinitionResourceProviderTest {
         AlertDefinitionResourceProvider.ALERT_DEF_ID,
         AlertDefinitionResourceProvider.ALERT_DEF_NAME,
         AlertDefinitionResourceProvider.ALERT_DEF_LABEL,
+        AlertDefinitionResourceProvider.ALERT_DEF_SOURCE,
         AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE);
 
     AmbariManagementController amc = 
createMock(AmbariManagementController.class);
@@ -174,9 +185,20 @@ public class AlertDefinitionResourceProviderTest {
     Resource r = results.iterator().next();
 
     Assert.assertEquals("my_def", 
r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_NAME));
-    Assert.assertEquals("metric", 
r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE));
+
+    Assert.assertEquals(
+        SourceType.METRIC.name(),
+        
r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE));
+
+    Source source = getMockSource();
+    String okJson = source.getReporting().getOk().getText();
+    Object reporting = 
r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_REPORTING);
+
+    Assert.assertTrue(reporting.toString().contains(okJson));
+
     Assert.assertEquals("Mock Label",
         r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_LABEL));
+
     Assert.assertNotNull(r.getPropertyValue("AlertDefinition/source/type"));
   }
 
@@ -203,14 +225,25 @@ public class AlertDefinitionResourceProviderTest {
 
     replay(amc, clusters, cluster, dao, definitionHash);
 
+    Gson gson = m_factory.getGson();
+    Source source = getMockSource();
+    String sourceJson = gson.toJson(source);
     AlertDefinitionResourceProvider provider = createProvider(amc);
 
     Map<String, Object> requestProps = new HashMap<String, Object>();
     requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_CLUSTER_NAME, 
"c1");
     requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_INTERVAL, "1");
     requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_NAME, "my_def");
-    requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_SERVICE_NAME, 
"HDFS");
-    requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE, 
"METRIC");
+
+    requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_SERVICE_NAME,
+        "HDFS");
+
+    requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE,
+        sourceJson);
+
+    requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE,
+        SourceType.METRIC.name());
+
     requestProps.put(AlertDefinitionResourceProvider.ALERT_DEF_LABEL,
         "Mock Label (Create)");
 
@@ -230,10 +263,23 @@ public class AlertDefinitionResourceProviderTest {
     Assert.assertEquals(Integer.valueOf(1), entity.getScheduleInterval());
     Assert.assertNull(entity.getScope());
     Assert.assertEquals("HDFS", entity.getServiceName());
-    Assert.assertNotNull(entity.getSource());
     Assert.assertEquals("METRIC", entity.getSourceType());
     Assert.assertEquals("Mock Label (Create)", entity.getLabel());
 
+    // verify Source
+    Assert.assertNotNull(entity.getSource());
+    Source actualSource = gson.fromJson(entity.getSource(), Source.class);
+    Assert.assertNotNull(actualSource);
+
+    assertEquals(source.getReporting().getOk().getText(),
+        source.getReporting().getOk().getText());
+
+    assertEquals(source.getReporting().getWarning().getText(),
+        source.getReporting().getWarning().getText());
+
+    assertEquals(source.getReporting().getCritical().getText(),
+        source.getReporting().getCritical().getText());
+
     verify(amc, clusters, cluster, dao);
 
   }
@@ -386,7 +432,10 @@ public class AlertDefinitionResourceProviderTest {
   /**
    * @return
    */
-  private List<AlertDefinitionEntity> getMockEntities() {
+  private List<AlertDefinitionEntity> getMockEntities() throws Exception {
+    Source source = getMockSource();
+    String sourceJson = new Gson().toJson(source);
+
     AlertDefinitionEntity entity = new AlertDefinitionEntity();
     entity.setClusterId(Long.valueOf(1L));
     entity.setComponentName(null);
@@ -397,13 +446,36 @@ public class AlertDefinitionResourceProviderTest {
     entity.setHash(DEFINITION_UUID);
     entity.setScheduleInterval(Integer.valueOf(2));
     entity.setServiceName(null);
-    entity.setSourceType("metric");
-    entity.setSource("{'jmx': 'beanName/attributeName', 'host': 
'{{aa:123445}}'}");
-
+    entity.setSourceType(SourceType.METRIC.name());
+    entity.setSource(sourceJson);
     return Arrays.asList(entity);
   }
 
   /**
+   * @return
+   */
+  private Source getMockSource() throws Exception {
+    File alertsFile = new File(
+        "src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json");
+
+    Assert.assertTrue(alertsFile.exists());
+
+    Set<AlertDefinition> set = m_factory.getAlertDefinitions(alertsFile, 
"HDFS");
+    AlertDefinition nameNodeCpu = null;
+    Iterator<AlertDefinition> definitions = set.iterator();
+    while (definitions.hasNext()) {
+      AlertDefinition definition = definitions.next();
+
+      if (definition.getName().equals("namenode_cpu")) {
+        nameNodeCpu = definition;
+      }
+    }
+
+    Assert.assertNotNull(nameNodeCpu.getSource());
+    return nameNodeCpu.getSource();
+  }
+
+  /**
   *
   */
   private class MockModule implements Module {
@@ -418,6 +490,7 @@ public class AlertDefinitionResourceProviderTest {
           EasyMock.createNiceMock(Clusters.class));
       binder.bind(Cluster.class).toInstance(
           EasyMock.createNiceMock(Cluster.class));
+      binder.bind(ActionMetadata.class);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/0ac9cb3f/ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json 
b/ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json
index 85aa3ab..02a9a58 100644
--- 
a/ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json
+++ 
b/ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json
@@ -1,18 +1,5 @@
 {
-  "service": [
-  ],
-  "SECONDARY_NAMENODE": [
-    {
-      "name": "secondary_namenode_process",
-      "label": "Secondary NameNode process",
-      "interval": 1,
-      "scope": "service",
-      "source": {
-        "type": "PORT",
-        "config": "{{hdfs-site/dfs.namenode.secondary.http-address}}:50071"
-      }
-    }
-  ],
+  "service": [],
   "NAMENODE": [
     {
       "name": "namenode_cpu",
@@ -21,7 +8,20 @@
       "source": {
         "type": "METRIC",
         "jmx": "java.lang:type=OperatingSystem/SystemCpuLoad",
-        "host": "{{hdfs-site/dfs.namenode.secondary.http-address}}"
+        "host": "{{hdfs-site/dfs.namenode.secondary.http-address}}",
+        "reporting": {
+          "ok": {
+            "text": "System CPU Load is OK"
+          },
+          "warning": {
+            "text": "System CPU Load is Nearing Critical",
+            "value": 70
+          },          
+          "critical": {
+            "text": "System CPU Load is Critical",
+            "value": 80
+          }
+        }
       }
     },
     {
@@ -31,8 +31,17 @@
       "scope": "host",
       "source": {
         "type": "PORT",
-        "uri": "{{hdfs-site/dfs.namenode.http-address}}:50070"
-       }
+        "uri": "{{hdfs-site/dfs.namenode.http-address}}",
+        "port": 50070,
+        "reporting": {
+          "ok": {
+            "text": "TCP OK - {0:.4f} response on port {1}"
+          },
+          "critical": {
+            "text": "TCP FAIL - {0:.4f} response on port {1}"
+          }
+        }        
+      }
     },
     {
       "name": "hdfs_last_checkpoint",
@@ -46,6 +55,18 @@
       }
     }
   ],
-  "DATANODE": [
-  ]
-}
+  "SECONDARY_NAMENODE": [
+    {
+      "name": "secondary_namenode_process",
+      "label": "Secondary NameNode process",
+      "interval": 1,
+      "scope": "service",
+      "source": {
+        "type": "PORT",        
+        "uri": "{{hdfs-site/dfs.namenode.secondary.http-address}}",
+        "port": 50070
+      }
+    }
+  ],  
+  "DATANODE": []
+}
\ No newline at end of file

Reply via email to