ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jonathanhur...@apache.org
Subject ambari git commit: AMBARI-15686 - Alert Dispatch Scheduling Changes to Support Repeat Tolerance (jonathanhurley)
Date Mon, 04 Apr 2016 19:49:51 GMT
Repository: ambari
Updated Branches:
  refs/heads/trunk 27d06a1bd -> 18449b5b8


AMBARI-15686 - Alert Dispatch Scheduling Changes to Support Repeat Tolerance (jonathanhurley)


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

Branch: refs/heads/trunk
Commit: 18449b5b804790c15f0917ccb43b9a8be4bd5ca1
Parents: 27d06a1
Author: Jonathan Hurley <jhurley@hortonworks.com>
Authored: Mon Apr 4 11:33:36 2016 -0400
Committer: Jonathan Hurley <jhurley@hortonworks.com>
Committed: Mon Apr 4 15:49:42 2016 -0400

----------------------------------------------------------------------
 .../internal/AlertResourceProvider.java         |   7 +-
 .../server/events/AlertStateChangeEvent.java    |   7 +-
 .../alerts/AlertAggregateListener.java          |  15 +-
 .../listeners/alerts/AlertReceivedListener.java | 157 +++++++++-----
 .../alerts/AlertStateChangedListener.java       |  20 +-
 .../server/orm/entities/AlertCurrentEntity.java |  34 ++-
 .../org/apache/ambari/server/state/Alert.java   | 106 +++++++---
 .../ambari/server/state/AlertFirmness.java      |  37 ++++
 .../server/upgrade/UpgradeCatalog240.java       |  10 +-
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |   3 +-
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |   3 +-
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |   3 +-
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |   3 +-
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |   3 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |   3 +-
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |   3 +-
 .../alerts/AggregateAlertListenerTest.java      |  92 ++++++--
 .../state/alerts/AlertReceivedListenerTest.java | 211 ++++++++++++++++++-
 .../alerts/AlertStateChangedEventTest.java      | 102 +++++++--
 .../state/alerts/InitialAlertEventTest.java     |  21 +-
 .../server/upgrade/UpgradeCatalog240Test.java   |  15 +-
 21 files changed, 696 insertions(+), 159 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
index 5a985d6..4c20c6c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertResourceProvider.java
@@ -77,6 +77,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider implements
   protected static final String ALERT_REPEAT_TOLERANCE = "Alert/repeat_tolerance";
   protected static final String ALERT_OCCURRENCES = "Alert/occurrences";
   protected static final String ALERT_REPEAT_TOLERANCE_REMAINING = "Alert/repeat_tolerance_remaining";
+  protected static final String ALERT_FIRMNESS = "Alert/firmness";
 
   private static Set<String> pkPropertyIds = new HashSet<String>(
       Arrays.asList(ALERT_ID, ALERT_DEFINITION_NAME));
@@ -117,6 +118,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider implements
     PROPERTY_IDS.add(ALERT_REPEAT_TOLERANCE);
     PROPERTY_IDS.add(ALERT_OCCURRENCES);
     PROPERTY_IDS.add(ALERT_REPEAT_TOLERANCE_REMAINING);
+    PROPERTY_IDS.add(ALERT_FIRMNESS);
 
     // keys
     KEY_PROPERTY_IDS.put(Resource.Type.Alert, ALERT_ID);
@@ -264,8 +266,8 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider implements
 
     // repeat tolerance values
     int repeatTolerance = definition.getRepeatTolerance();
-    int occurrences = entity.getOccurrences();
-    int remaining = (occurrences > repeatTolerance) ? 0 : (repeatTolerance - occurrences);
+    long occurrences = entity.getOccurrences();
+    long remaining = (occurrences > repeatTolerance) ? 0 : (repeatTolerance - occurrences);
 
     // the OK state is special; when received, we ignore tolerance and notify
     if (history.getAlertState() == AlertState.OK) {
@@ -275,6 +277,7 @@ public class AlertResourceProvider extends ReadOnlyResourceProvider implements
     setResourceProperty(resource, ALERT_REPEAT_TOLERANCE, repeatTolerance, requestedIds);
     setResourceProperty(resource, ALERT_OCCURRENCES, occurrences, requestedIds);
     setResourceProperty(resource, ALERT_REPEAT_TOLERANCE_REMAINING, remaining, requestedIds);
+    setResourceProperty(resource, ALERT_FIRMNESS, entity.getFirmness(), requestedIds);
 
     if (isCollection) {
       // !!! want name/id to be populated as if it were a PK when requesting the

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
index 60dbec4..ac1eb8d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AlertStateChangeEvent.java
@@ -20,11 +20,15 @@ package org.apache.ambari.server.events;
 import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 
 /**
  * The {@link AlertStateChangeEvent} is fired when an {@link Alert} instance has
- * its {@link AlertState} changed.
+ * its {@link AlertState} changed or has it's {@link AlertFirmness} changed.
+ * <p/>
+ * An {@link AlertState} change coupled with a {@link AlertFirmness#HARD}
+ * firmness is what would eventually trigger notifications to be created.
  */
 public class AlertStateChangeEvent extends AlertEvent {
 
@@ -93,6 +97,7 @@ public class AlertStateChangeEvent extends AlertEvent {
     StringBuilder buffer = new StringBuilder("AlertStateChangeEvent{");
     buffer.append("cluserId=").append(m_clusterId);
     buffer.append(", fromState=").append(m_fromState);
+    buffer.append(", firmness=").append(m_currentAlert.getFirmness());
     buffer.append(", alert=").append(m_alert);
 
     buffer.append("}");

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertAggregateListener.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertAggregateListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertAggregateListener.java
index 0bcfa2f..950797c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertAggregateListener.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertAggregateListener.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import com.google.inject.Provider;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.EagerSingleton;
 import org.apache.ambari.server.events.AggregateAlertRecalculateEvent;
@@ -32,7 +31,9 @@ import org.apache.ambari.server.events.InitialAlertEvent;
 import org.apache.ambari.server.events.publishers.AlertEventPublisher;
 import org.apache.ambari.server.orm.dao.AlertSummaryDTO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.alert.AggregateDefinitionMapping;
@@ -47,6 +48,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.eventbus.AllowConcurrentEvents;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 /**
@@ -56,7 +58,10 @@ import com.google.inject.Singleton;
  * <p/>
  * This listener is only needed on state changes as aggregation of alerts is
  * only performed against the state of an alert and not the values that
- * contributed to that state.
+ * contributed to that state. However, this listener should only be concerned
+ * with {@link AlertFirmness#HARD} events as they represent a true change in the
+ * state of an alert. Calculations should never be performed on
+ * {@link AlertFirmness#SOFT} alerts since they may be false positives.
  */
 @Singleton
 @EagerSingleton
@@ -118,6 +123,12 @@ public class AlertAggregateListener {
   public void onAlertStateChangeEvent(AlertStateChangeEvent event) {
     LOG.debug("Received event {}", event);
 
+    // do not recalculate on SOFT events
+    AlertCurrentEntity currentEntity = event.getCurrentAlert();
+    if (currentEntity.getFirmness() == AlertFirmness.SOFT) {
+      return;
+    }
+
     onAlertEvent(event.getClusterId(), event.getAlert().getName());
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java
index fbd5c12..2800ac6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java
@@ -17,9 +17,8 @@
  */
 package org.apache.ambari.server.events.listeners.alerts;
 
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.EagerSingleton;
@@ -39,10 +38,12 @@ import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.MaintenanceState;
+import org.apache.ambari.server.state.alert.SourceType;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -122,13 +123,15 @@ public class AlertReceivedListener {
       LOG.debug(event.toString());
     }
 
-    //play around too many commits
+    // process the list of alerts inside of a single transaction to prevent too
+    // many transactions/commits
     List<Alert> alerts = event.getAlerts();
 
-    Map<Alert, AlertCurrentEntity> toCreate = new HashMap<Alert, AlertCurrentEntity>();
-    Map<Alert, AlertCurrentEntity> toMerge = new HashMap<Alert, AlertCurrentEntity>();
-    Map<Alert, AlertCurrentEntity> toCreateHistoryAndMerge = new HashMap<Alert, AlertCurrentEntity>();
-    Map<Alert, AlertState> oldStates = new HashMap<Alert, AlertState>();
+    List<AlertCurrentEntity> toCreate = new ArrayList<>();
+    List<AlertCurrentEntity> toMerge = new ArrayList<>();
+    List<AlertCurrentEntity> toCreateHistoryAndMerge = new ArrayList<>();
+
+    List<AlertEvent> alertEvents = new ArrayList<>(20);
 
     for (Alert alert : alerts) {
       // jobs that were running when a service/component/host was changed
@@ -139,7 +142,7 @@ public class AlertReceivedListener {
 
       Long clusterId = getClusterIdByName(alert.getCluster());
       if (clusterId == null) {
-        //check event
+        // check event
         clusterId = event.getClusterId();
       }
 
@@ -199,21 +202,45 @@ public class AlertReceivedListener {
         current.setLatestTimestamp(alert.getTimestamp());
         current.setOriginalTimestamp(alert.getTimestamp());
 
-        toCreate.put(alert, current);
+        // brand new alert instances being received are always HARD
+        current.setFirmness(AlertFirmness.HARD);
+
+        // store the entity for creation later
+        toCreate.add(current);
+
+        // create the event to fire later
+        alertEvents.add(new InitialAlertEvent(clusterId, alert, current));
 
       } else if (alertState == current.getAlertHistory().getAlertState()
           || alertState == AlertState.SKIPPED) {
 
-        // update the timestamp
+        // update the timestamp no matter what
         current.setLatestTimestamp(alert.getTimestamp());
 
-        // only update some fields if the alert isn't being skipped
+        // only update some fields if the alert isn't SKIPPED
         if (alertState != AlertState.SKIPPED) {
           current.setLatestText(alert.getText());
-          current.setOccurrences(current.getOccurrences() + 1);
+
+          // ++ the occurrences (should be safe enough since we should ever only
+          // be handling unique alert events concurrently
+          long occurrences = current.getOccurrences() + 1;
+          current.setOccurrences(occurrences);
+
+          // ensure that if we've met the repeat tolerance and the alert is
+          // still SOFT, then we transition it to HARD - we also need to fire an
+          // event
+          AlertFirmness currentFirmness = current.getFirmness();
+          int repeatTolerance = definition.getRepeatTolerance();
+          if (currentFirmness == AlertFirmness.SOFT && occurrences >= repeatTolerance) {
+            current.setFirmness(AlertFirmness.HARD);
+
+            // create the event to fire later
+            alertEvents.add(new AlertStateChangeEvent(clusterId, alert, current, alertState));
+          }
         }
 
-        toMerge.put(alert, current);
+        // store the entity for merging later
+        toMerge.add(current);
       } else {
         if (LOG.isDebugEnabled()) {
           LOG.debug(
@@ -258,8 +285,17 @@ public class AlertReceivedListener {
             break;
         }
 
-        toCreateHistoryAndMerge.put(alert, current);
-        oldStates.put(alert, oldState);
+        // set the firmness of the new alert state based on the state & type
+        AlertFirmness firmness = calculateFirmnessForStateChange(definition, alertState,
+            current.getOccurrences());
+
+        current.setFirmness(firmness);
+
+        // store the entity for merging later
+        toCreateHistoryAndMerge.add(current);
+
+        // create the event to fire later
+        alertEvents.add(new AlertStateChangeEvent(clusterId, alert, current, oldState));
       }
     }
 
@@ -268,38 +304,8 @@ public class AlertReceivedListener {
     saveEntities(toCreate, toMerge, toCreateHistoryAndMerge);
 
     // broadcast events
-    for (Map.Entry<Alert, AlertCurrentEntity> entry : toCreate.entrySet()) {
-      Alert alert = entry.getKey();
-      AlertCurrentEntity entity = entry.getValue();
-      Long clusterId = getClusterIdByName(alert.getCluster());
-      if (clusterId == null) {
-        //super rare case, cluster was removed after isValid() check
-        LOG.error("Unable to process alert {} for an invalid cluster named {}",
-          alert.getName(), alert.getCluster());
-        continue;
-      }
-
-      InitialAlertEvent initialAlertEvent = new InitialAlertEvent(
-        clusterId, alert, entity);
-
-      m_alertEventPublisher.publish(initialAlertEvent);
-    }
-
-    for (Map.Entry<Alert, AlertCurrentEntity> entry : toCreateHistoryAndMerge.entrySet()) {
-      Alert alert = entry.getKey();
-      AlertCurrentEntity entity = entry.getValue();
-      Long clusterId = getClusterIdByName(alert.getCluster());
-      if (clusterId == null) {
-        //super rare case, cluster was removed after isValid() check
-        LOG.error("Unable to process alert {} for an invalid cluster named {}",
-          alert.getName(), alert.getCluster());
-        continue;
-      }
-
-      AlertStateChangeEvent alertChangedEvent = new AlertStateChangeEvent(clusterId, alert, entity,
-        oldStates.get(alert));
-
-      m_alertEventPublisher.publish(alertChangedEvent);
+    for (AlertEvent eventToFire : alertEvents) {
+      m_alertEventPublisher.publish(eventToFire);
     }
   }
 
@@ -325,20 +331,17 @@ public class AlertReceivedListener {
    * @param toCreateHistoryAndMerge - create new history, merge alert
    */
   @Transactional
-  void saveEntities(Map<Alert, AlertCurrentEntity> toCreate,
-      Map<Alert, AlertCurrentEntity> toMerge,
-      Map<Alert, AlertCurrentEntity> toCreateHistoryAndMerge) {
-    for (Map.Entry<Alert, AlertCurrentEntity> entry : toCreate.entrySet()) {
-      AlertCurrentEntity entity = entry.getValue();
+  void saveEntities(List<AlertCurrentEntity> toCreate, List<AlertCurrentEntity> toMerge,
+      List<AlertCurrentEntity> toCreateHistoryAndMerge) {
+    for (AlertCurrentEntity entity : toCreate) {
       m_alertsDao.create(entity);
     }
 
-    for (AlertCurrentEntity entity : toMerge.values()) {
+    for (AlertCurrentEntity entity : toMerge) {
       m_alertsDao.merge(entity, m_configuration.isAlertCacheEnabled());
     }
 
-    for (Map.Entry<Alert, AlertCurrentEntity> entry : toCreateHistoryAndMerge.entrySet()) {
-      AlertCurrentEntity entity = entry.getValue();
+    for (AlertCurrentEntity entity : toCreateHistoryAndMerge) {
       m_alertsDao.create(entity.getAlertHistory());
       m_alertsDao.merge(entity);
 
@@ -480,7 +483,7 @@ public class AlertReceivedListener {
   }
 
   /**
-   * Convenience to create a new alert.
+   * Convenience method to create a new historical alert.
    *
    * @param clusterId
    *          the cluster id
@@ -512,4 +515,46 @@ public class AlertReceivedListener {
 
     return history;
   }
+
+  /**
+   * Gets the firmness for an {@link AlertCurrentEntity}. The following rules
+   * apply:
+   * <ul>
+   * <li>If an alert is {@link AlertState#OK}, then the firmness is always
+   * {@link AlertFirmness#HARD}.</li>
+   * <li>If an alert is {@link SourceType#AGGREGATE}, then the firmness is
+   * always {@link AlertFirmness#HARD}.</li>
+   * <li>Otherwise, the firmness will be {@link AlertFirmness#SOFT} unless the
+   * repeat tolerance has been met.</li>
+   * </ul>
+   *
+   * @param definition
+   *          the definition to read any repeat tolerance overrides from.
+   * @param state
+   *          the state of the {@link AlertCurrentEntity}.
+   * @param the
+   *          occurrences of the alert in the current state (used for
+   *          calculation firmness when moving between non-OK states)
+   * @return
+   */
+  private AlertFirmness calculateFirmnessForStateChange(AlertDefinitionEntity definition,
+      AlertState state, long occurrences) {
+    if (state == AlertState.OK) {
+      return AlertFirmness.HARD;
+    }
+
+    if (definition.getSourceType() == SourceType.AGGREGATE) {
+      return AlertFirmness.HARD;
+    }
+
+    if (definition.getRepeatTolerance() <= 1) {
+      return AlertFirmness.HARD;
+    }
+
+    if (definition.getRepeatTolerance() <= occurrences) {
+      return AlertFirmness.HARD;
+    }
+
+    return AlertFirmness.SOFT;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertStateChangedListener.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertStateChangedListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertStateChangedListener.java
index 08563e3..73c2f1b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertStateChangedListener.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertStateChangedListener.java
@@ -33,6 +33,7 @@ import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
 import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.MaintenanceState;
 import org.apache.ambari.server.state.NotificationState;
@@ -48,6 +49,9 @@ import com.google.inject.Singleton;
  * The {@link AlertStateChangedListener} class response to
  * {@link AlertStateChangeEvent} and updates {@link AlertNoticeEntity} instances
  * in the database.
+ * <p/>
+ * {@link AlertNoticeEntity} instances will only be updated if the firmness of
+ * the alert is {@link AlertFirmness#HARD}.
  */
 @Singleton
 @EagerSingleton
@@ -65,10 +69,10 @@ public class AlertStateChangedListener {
   private static final Logger ALERT_LOG = LoggerFactory.getLogger("alerts");
 
   /**
-   * [CRITICAL] [HDFS] [namenode_hdfs_blocks_health] (NameNode Blocks Health)
-   * Total Blocks:[100], Missing Blocks:[6]
+   * [CRITICAL] [HARD] [HDFS] [namenode_hdfs_blocks_health] (NameNode Blocks
+   * Health) Total Blocks:[100], Missing Blocks:[6]
    */
-  private static final String ALERT_LOG_MESSAGE = "[{}] [{}] [{}] ({}) {}";
+  private static final String ALERT_LOG_MESSAGE = "[{}] [{}] [{}] [{}] ({}) {}";
 
   /**
    * Used for looking up groups and targets.
@@ -100,18 +104,19 @@ public class AlertStateChangedListener {
     LOG.debug("Received event {}", event);
 
     Alert alert = event.getAlert();
+    AlertCurrentEntity current = event.getCurrentAlert();
     AlertHistoryEntity history = event.getNewHistoricalEntry();
     AlertDefinitionEntity definition = history.getAlertDefinition();
 
     // log to the alert audit log so there is physical record even if
     // definitions and historical enties are removed
-    ALERT_LOG.info(ALERT_LOG_MESSAGE, alert.getState(),
+    ALERT_LOG.info(ALERT_LOG_MESSAGE, alert.getState(), current.getFirmness(),
         definition.getServiceName(), definition.getDefinitionName(),
         definition.getLabel(), alert.getText());
 
-    // normal logging for Ambari
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("An alert has changed state: {}", event);
+    // do nothing if the firmness is SOFT
+    if (current.getFirmness() == AlertFirmness.SOFT) {
+      return;
     }
 
     // don't create any outbound alert notices if in MM
@@ -144,6 +149,7 @@ public class AlertStateChangedListener {
         notices.add(notice);
       }
     }
+
     m_alertsDispatchDao.createNotices(notices);
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java
index 31f2154..8b6dc45 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java
@@ -33,6 +33,7 @@ import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import javax.persistence.TableGenerator;
 
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.MaintenanceState;
 
@@ -92,7 +93,11 @@ public class AlertCurrentEntity {
    *
    */
   @Column(name="occurrences", nullable=false)
-  private Integer occurrences = Integer.valueOf(1);
+  private Long occurrences = Long.valueOf(1);
+
+  @Column(name = "firmness", nullable = false)
+  @Enumerated(value = EnumType.STRING)
+  private AlertFirmness firmness = AlertFirmness.HARD;
 
   /**
    * Unidirectional one-to-one association to {@link AlertHistoryEntity}
@@ -221,7 +226,7 @@ public class AlertCurrentEntity {
    *
    * @return the number of occurrences.
    */
-  public Integer getOccurrences() {
+  public Long getOccurrences() {
     return occurrences;
   }
 
@@ -233,7 +238,7 @@ public class AlertCurrentEntity {
    * @see #getOccurrences()
    *
    */
-  public void setOccurrences(int occurrences) {
+  public void setOccurrences(long occurrences) {
     this.occurrences = occurrences;
   }
 
@@ -241,11 +246,32 @@ public class AlertCurrentEntity {
    * @param occurrences
    *          the occurrences to set
    */
-  public void setOccurrences(Integer occurrences) {
+  public void setOccurrences(Long occurrences) {
     this.occurrences = occurrences;
   }
 
   /**
+   * Gets the firmness of the alert, indicating whether or not it could be a
+   * potential false positive.
+   *
+   * @return the alert firmness.
+   */
+  public AlertFirmness getFirmness() {
+    return firmness;
+  }
+
+  /**
+   * Sets the firmness of the alert, indicating whether or not it could be a
+   * potential false positive.
+   *
+   * @param firmness
+   *          the firmness (not {@code null}).
+   */
+  public void setFirmness(AlertFirmness firmness) {
+    this.firmness = firmness;
+  }
+
+  /**
    * Gets the associated {@link AlertHistoryEntity} entry for this current alert
    * instance.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/state/Alert.java
----------------------------------------------------------------------
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 f91d372..8252e0d 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
@@ -188,50 +188,96 @@ public class Alert {
     this.cluster = cluster;
   }
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public int hashCode() {
-    int result = alertHashCode();
-
-    result += 31 * result + (null != instance ? instance.hashCode() : 0);
-
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((state == null) ? 0 : state.hashCode());
+    result = prime * result + ((name == null) ? 0 : name.hashCode());
+    result = prime * result + ((service == null) ? 0 : service.hashCode());
+    result = prime * result + ((component == null) ? 0 : component.hashCode());
+    result = prime * result + ((hostName == null) ? 0 : hostName.hashCode());
+    result = prime * result + ((cluster == null) ? 0 : cluster.hashCode());
+    result = prime * result + ((instance == null) ? 0 : instance.hashCode());
     return result;
   }
 
   /**
-   * An alert's uniqueness comes from a combination of name, instance, service,
-   * component and host.
+   * {@inheritDoc}
    */
   @Override
-  public boolean equals(Object o) {
-    if (null == o || !Alert.class.isInstance(o)) {
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (obj == null) {
       return false;
     }
 
-    return hashCode() == o.hashCode();
-  }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
 
-  /**
-   * @return the hashcode of the alert without instance info
-   */
-  private int alertHashCode() {
-    int result = (null != name) ? name.hashCode() : 0;
-    result += 31 * result + (null != service ? service.hashCode() : 0);
-    result += 31 * result + (null != component ? component.hashCode() : 0);
-    result += 31 * result + (null != hostName ? hostName.hashCode() : 0);
+    Alert other = (Alert) obj;
 
-    return result;
-  }
+    if (state != other.state) {
+      return false;
+    }
 
-  /**
-   * Checks equality with another alert, not taking into account instance info
-   *
-   * @param that
-   *          the other alert to compare against
-   * @return <code>true</code> when the alert is equal in every way except the
-   *         instance info
-   */
-  public boolean almostEquals(Alert that) {
-    return alertHashCode() == that.alertHashCode();
+    if (name == null) {
+      if (other.name != null) {
+        return false;
+      }
+    } else if (!name.equals(other.name)) {
+      return false;
+    }
+
+    if (service == null) {
+      if (other.service != null) {
+        return false;
+      }
+    } else if (!service.equals(other.service)) {
+      return false;
+    }
+
+    if (component == null) {
+      if (other.component != null) {
+        return false;
+      }
+    } else if (!component.equals(other.component)) {
+      return false;
+    }
+
+    if (hostName == null) {
+      if (other.hostName != null) {
+        return false;
+      }
+    } else if (!hostName.equals(other.hostName)) {
+      return false;
+    }
+
+    if (cluster == null) {
+      if (other.cluster != null) {
+        return false;
+      }
+    } else if (!cluster.equals(other.cluster)) {
+      return false;
+    }
+
+    if (instance == null) {
+      if (other.instance != null) {
+        return false;
+      }
+    } else if (!instance.equals(other.instance)) {
+      return false;
+    }
+
+
+    return true;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/state/AlertFirmness.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/AlertFirmness.java b/ambari-server/src/main/java/org/apache/ambari/server/state/AlertFirmness.java
new file mode 100644
index 0000000..a996f52
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/AlertFirmness.java
@@ -0,0 +1,37 @@
+/**
+ * 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;
+
+/**
+ * The {@link AlertFirmness} enum is used to represent whether an alert should
+ * be considered as a real alert or whether it could still potentially be a
+ * false positive. Alerts which are {@link #SOFT} must have more occurrences in
+ * order to rule out the possibility of a false positive.
+ */
+public enum AlertFirmness {
+  /**
+   * The alert is a potential false positive and needs more instances to be
+   * confirmed.
+   */
+  SOFT,
+
+  /**
+   * The alert is not a potential false-positive.
+   */
+  HARD;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
index f4a0b8c..b3241e0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java
@@ -41,6 +41,7 @@ import org.apache.ambari.server.orm.dao.RoleAuthorizationDAO;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.orm.entities.PermissionEntity;
 import org.apache.ambari.server.orm.entities.RoleAuthorizationEntity;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Config;
@@ -69,6 +70,7 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
   protected static final String ALERT_DEFINITION_TABLE = "alert_definition";
   protected static final String ALERT_CURRENT_TABLE = "alert_current";
   protected static final String ALERT_CURRENT_OCCURRENCES_COLUMN = "occurrences";
+  protected static final String ALERT_CURRENT_FIRMNESS_COLUMN = "firmness";
   protected static final String HELP_URL_COLUMN = "help_url";
   protected static final String REPEAT_TOLERANCE_COLUMN = "repeat_tolerance";
   protected static final String REPEAT_TOLERANCE_ENABLED_COLUMN = "repeat_tolerance_enabled";
@@ -482,14 +484,18 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog {
   /**
    * Updates the {@value #ALERT_CURRENT_TABLE} in the following ways:
    * <ul>
-   * <li>Craetes the {@value #ALERT_CURRENT_OCCURRENCES_COLUMN} column</li>
+   * <li>Creates the {@value #ALERT_CURRENT_OCCURRENCES_COLUMN} column</li>
+   * <li>Creates the {@value #ALERT_CURRENT_FIRMNESS_COLUMN} column</li>
    * </ul>
    *
    * @throws SQLException
    */
   protected void updateAlertCurrentTable() throws SQLException {
     dbAccessor.addColumn(ALERT_CURRENT_TABLE,
-        new DBColumnInfo(ALERT_CURRENT_OCCURRENCES_COLUMN, Integer.class, null, 1, false));
+        new DBColumnInfo(ALERT_CURRENT_OCCURRENCES_COLUMN, Long.class, null, 1, false));
+
+    dbAccessor.addColumn(ALERT_CURRENT_TABLE, new DBColumnInfo(ALERT_CURRENT_FIRMNESS_COLUMN,
+        String.class, 255, AlertFirmness.HARD.name(), false));
   }
 
   protected void setRoleSortOrder() throws SQLException {

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
index 2fb2195..81b97bb 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -918,7 +918,8 @@ CREATE TABLE alert_current (
   original_timestamp BIGINT NOT NULL,
   latest_timestamp BIGINT NOT NULL,
   latest_text VARCHAR(3000),
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences BIGINT NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index 6a3712e..021d2b8 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -931,7 +931,8 @@ CREATE TABLE alert_current (
   original_timestamp BIGINT NOT NULL,
   latest_timestamp BIGINT NOT NULL,
   latest_text TEXT,
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences BIGINT NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index 964941f..0320178 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -921,7 +921,8 @@ CREATE TABLE alert_current (
   original_timestamp NUMBER(19) NOT NULL,
   latest_timestamp NUMBER(19) NOT NULL,
   latest_text CLOB,
-  occurrences INTEGER DEFAULT 1 NOT NULL
+  occurrences NUMBER(19) DEFAULT 1 NOT NULL,
+  firmness VARCHAR2(255) DEFAULT 'HARD' NOT NULL,
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 366bbeb..585cdce 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -925,7 +925,8 @@ CREATE TABLE alert_current (
   original_timestamp BIGINT NOT NULL,
   latest_timestamp BIGINT NOT NULL,
   latest_text TEXT,
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences BIGINT NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
index f7e7262..b546865 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
@@ -1012,7 +1012,8 @@ CREATE TABLE ambari.alert_current (
   original_timestamp BIGINT NOT NULL,
   latest_timestamp BIGINT NOT NULL,
   latest_text TEXT,
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences BIGINT NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES ambari.alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES ambari.alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
index bdb6843..8bfd9dd 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -920,7 +920,8 @@ CREATE TABLE alert_current (
   original_timestamp NUMERIC(19) NOT NULL,
   latest_timestamp NUMERIC(19) NOT NULL,
   latest_text TEXT,
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences NUMERIC(19) NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
index c6df84a..57ab922 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -1032,7 +1032,8 @@ CREATE TABLE alert_current (
   original_timestamp BIGINT NOT NULL,
   latest_timestamp BIGINT NOT NULL,
   latest_text TEXT,
-  occurrences INTEGER NOT NULL DEFAULT 1,
+  occurrences BIGINT NOT NULL DEFAULT 1,
+  firmness VARCHAR(255) NOT NULL DEFAULT 'HARD',
   PRIMARY KEY CLUSTERED (alert_id),
   FOREIGN KEY (definition_id) REFERENCES alert_definition(definition_id),
   FOREIGN KEY (history_id) REFERENCES alert_history(alert_id)

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AggregateAlertListenerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AggregateAlertListenerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AggregateAlertListenerTest.java
index 29969d6..64ee936 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AggregateAlertListenerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AggregateAlertListenerTest.java
@@ -17,8 +17,6 @@
  */
 package org.apache.ambari.server.state.alerts;
 
-import junit.framework.Assert;
-
 import org.apache.ambari.server.events.AlertReceivedEvent;
 import org.apache.ambari.server.events.AlertStateChangeEvent;
 import org.apache.ambari.server.events.MockEventListener;
@@ -28,7 +26,9 @@ import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.dao.AlertSummaryDTO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
 import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
+import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.alert.AggregateDefinitionMapping;
 import org.apache.ambari.server.state.alert.AggregateSource;
 import org.apache.ambari.server.state.alert.AlertDefinition;
@@ -47,6 +47,8 @@ import com.google.inject.Module;
 import com.google.inject.persist.PersistService;
 import com.google.inject.util.Modules;
 
+import junit.framework.Assert;
+
 /**
  * Tests the {@link AlertAggregateListener}.
  */
@@ -92,24 +94,11 @@ public class AggregateAlertListenerTest {
    */
   @Test
   public void testAlertNoticeCreationFromEvent() throws Exception {
+    AlertDefinition aggregateDefinition = getAggregateAlertDefinition();
     AlertCurrentEntity currentEntityMock = EasyMock.createNiceMock(AlertCurrentEntity.class);
+    AlertHistoryEntity historyEntityMock = EasyMock.createNiceMock(AlertHistoryEntity.class);
 
-    // setup the mocks for the aggregate definition to avoid NPEs
-    AlertDefinition aggregateDefinition = new AlertDefinition();
-    aggregateDefinition.setName("mock-aggregate-alert");
-    AggregateSource aggregateSource = new AggregateSource();
-    aggregateSource.setAlertName("mock-aggregate-alert");
-    Reporting reporting = new Reporting();
-    ReportTemplate criticalTemplate = new ReportTemplate();
-    ReportTemplate okTemplate = new ReportTemplate();
-    criticalTemplate.setValue(.05);
-    criticalTemplate.setText("CRITICAL");
-    okTemplate.setText("OK");
-    reporting.setCritical(criticalTemplate);
-    reporting.setWarning(criticalTemplate);
-    reporting.setOk(okTemplate);
-    aggregateSource.setReporting(reporting);
-    aggregateDefinition.setSource(aggregateSource);
+    EasyMock.expect(currentEntityMock.getAlertHistory()).andReturn(historyEntityMock).atLeastOnce();
 
     EasyMock.expect(
         m_aggregateMapping.getAggregateDefinition(EasyMock.anyLong(), EasyMock.eq("mock-alert"))).andReturn(
@@ -120,7 +109,7 @@ public class AggregateAlertListenerTest {
         m_alertsDao.findAggregateCounts(EasyMock.anyLong(), EasyMock.eq("mock-aggregate-alert"))).andReturn(
         summaryDTO).atLeastOnce();
 
-    EasyMock.replay(m_alertsDao, m_aggregateMapping);
+    EasyMock.replay(m_alertsDao, m_aggregateMapping, currentEntityMock);
 
     // check that we're starting at 0
     Assert.assertEquals(0, m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
@@ -150,6 +139,71 @@ public class AggregateAlertListenerTest {
   }
 
   /**
+   * Tests that the {@link AlertAggregateListener} disregards
+   * {@link AlertFirmness#SOFT} alerts.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testNoAggregateCalculationOnSoftAlert() throws Exception {
+    AlertDefinition aggregateDefinition = getAggregateAlertDefinition();
+    AlertCurrentEntity currentEntityMock = EasyMock.createNiceMock(AlertCurrentEntity.class);
+    AlertHistoryEntity historyEntityMock = EasyMock.createNiceMock(AlertHistoryEntity.class);
+
+    EasyMock.expect(currentEntityMock.getAlertHistory()).andReturn(historyEntityMock).atLeastOnce();
+    EasyMock.expect(currentEntityMock.getFirmness()).andReturn(AlertFirmness.SOFT).atLeastOnce();
+
+    EasyMock.expect(m_aggregateMapping.getAggregateDefinition(EasyMock.anyLong(),
+        EasyMock.eq("mock-alert"))).andReturn(aggregateDefinition).atLeastOnce();
+
+    AlertSummaryDTO summaryDTO = new AlertSummaryDTO(5, 0, 0, 0, 0);
+    EasyMock.expect(m_alertsDao.findAggregateCounts(EasyMock.anyLong(),
+        EasyMock.eq("mock-aggregate-alert"))).andReturn(summaryDTO).atLeastOnce();
+
+    EasyMock.replay(m_alertsDao, m_aggregateMapping, currentEntityMock);
+
+    // check that we're starting at 0
+    Assert.assertEquals(0, m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
+
+    // trigger an alert which would normally trigger the aggregate, except that
+    // the alert will be SOFT and should not cause a recalculation
+    Alert alert = new Alert("mock-alert", null, null, null, null, null);
+    AlertAggregateListener aggregateListener = m_injector.getInstance(AlertAggregateListener.class);
+    AlertStateChangeEvent event = new AlertStateChangeEvent(0, alert, currentEntityMock, null);
+    aggregateListener.onAlertStateChangeEvent(event);
+
+    // ensure that the aggregate listener did not trigger an alert in response
+    // to the SOFT alert
+    Assert.assertEquals(0, m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
+  }
+
+  /**
+   * Gets a mocked {@link AlertDefinition}.
+   *
+   * @return
+   */
+  private AlertDefinition getAggregateAlertDefinition() {
+    // setup the mocks for the aggregate definition to avoid NPEs
+    AlertDefinition aggregateDefinition = new AlertDefinition();
+    aggregateDefinition.setName("mock-aggregate-alert");
+    AggregateSource aggregateSource = new AggregateSource();
+    aggregateSource.setAlertName("mock-aggregate-alert");
+    Reporting reporting = new Reporting();
+    ReportTemplate criticalTemplate = new ReportTemplate();
+    ReportTemplate okTemplate = new ReportTemplate();
+    criticalTemplate.setValue(.05);
+    criticalTemplate.setText("CRITICAL");
+    okTemplate.setText("OK");
+    reporting.setCritical(criticalTemplate);
+    reporting.setWarning(criticalTemplate);
+    reporting.setOk(okTemplate);
+    aggregateSource.setReporting(reporting);
+    aggregateDefinition.setSource(aggregateSource);
+
+    return aggregateDefinition;
+  }
+
+  /**
    *
    */
   private class MockModule implements Module {

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java
index 7d6c691..f8a1f64 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java
@@ -25,6 +25,7 @@ import java.util.UUID;
 import org.apache.ambari.server.controller.RootServiceResponseFactory.Components;
 import org.apache.ambari.server.controller.RootServiceResponseFactory.Services;
 import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.AlertStateChangeEvent;
 import org.apache.ambari.server.events.listeners.alerts.AlertReceivedListener;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
@@ -34,6 +35,7 @@ import org.apache.ambari.server.orm.dao.AlertsDAO;
 import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
@@ -528,29 +530,228 @@ public class AlertReceivedListenerTest {
     assertEquals(1, allCurrent.size());
 
     // check occurrences (should be 1 since it's the first)
-    assertEquals(1, (int) allCurrent.get(0).getOccurrences());
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
 
     // send OK again, then check that the value incremented
     listener.onAlertEvent(event);
     allCurrent = m_dao.findCurrent();
-    assertEquals(2, (int) allCurrent.get(0).getOccurrences());
+    assertEquals(2, (long) allCurrent.get(0).getOccurrences());
 
     // now change to WARNING and check that it reset the counter
     alert.setState(AlertState.WARNING);
     listener.onAlertEvent(event);
     allCurrent = m_dao.findCurrent();
-    assertEquals(1, (int) allCurrent.get(0).getOccurrences());
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
 
     // send another WARNING
     listener.onAlertEvent(event);
     allCurrent = m_dao.findCurrent();
-    assertEquals(2, (int) allCurrent.get(0).getOccurrences());
+    assertEquals(2, (long) allCurrent.get(0).getOccurrences());
 
     // now change from WARNING to CRITICAL; because they are both non-OK states,
     // the counter should continue
     alert.setState(AlertState.CRITICAL);
     listener.onAlertEvent(event);
     allCurrent = m_dao.findCurrent();
-    assertEquals(3, (int) allCurrent.get(0).getOccurrences());
+    assertEquals(3, (long) allCurrent.get(0).getOccurrences());
+  }
+
+  /**
+   * Tests that we correctly record alert firmness depending on several factors,
+   * such as {@link AlertState} and {@link SourceType}.
+   */
+  @Test
+  public void testAlertFirmness() throws Exception {
+    String definitionName = ALERT_DEFINITION + "1";
+    String serviceName = "HDFS";
+    String componentName = "NAMENODE";
+    String text = serviceName + " " + componentName + " is OK";
+
+    // start out with a critical alert to verify that all new alerts are always
+    // HARD
+    Alert alert = new Alert(definitionName, null, serviceName, componentName, HOST1,
+        AlertState.CRITICAL);
+
+    alert.setCluster(m_cluster.getClusterName());
+    alert.setLabel(ALERT_LABEL);
+    alert.setText(text);
+    alert.setTimestamp(1L);
+
+    // fire the alert, and check that the new entry was created with the right
+    // timestamp
+    AlertReceivedListener listener = m_injector.getInstance(AlertReceivedListener.class);
+    AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(), alert);
+    listener.onAlertEvent(event);
+
+    List<AlertCurrentEntity> allCurrent = m_dao.findCurrent();
+    assertEquals(1, allCurrent.size());
+
+    // check occurrences (should be 1 since it's the first)
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+
+    // check that the state is HARD since it's the first alert
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // move the repeat tolerance to 2 to test out SOFT alerts
+    AlertDefinitionEntity definition = allCurrent.get(0).getAlertHistory().getAlertDefinition();
+    definition.setRepeatTolerance(2);
+    definition.setRepeatToleranceEnabled(true);
+
+    m_definitionDao.merge(definition);
+
+    // change state to OK, and ensure that all OK alerts are hard
+    alert.setState(AlertState.OK);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // change state to CRITICAL and verify we are soft with 1 occurrence
+    alert.setState(AlertState.CRITICAL);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.SOFT, allCurrent.get(0).getFirmness());
+
+    // send a 2nd CRITICAL and made sure the occurrences are 2 and the firmness
+    // is HARD
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(2, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+  }
+
+  /**
+   * Tests that we correctly record alert firmness when an alert moves back and
+   * forth between non-OK states (such as between {@link AlertState#WARNING} and
+   * {@link AlertState#CRITICAL}). These are technically alert state changes and
+   * will fire {@link AlertStateChangeEvent}s but we only want to handle them
+   * when they are HARD.
+   */
+  @Test
+  public void testAlertFirmnessWithinNonOKStates() throws Exception {
+    String definitionName = ALERT_DEFINITION + "1";
+    String serviceName = "HDFS";
+    String componentName = "NAMENODE";
+    String text = serviceName + " " + componentName + " is OK";
+
+    Alert alert = new Alert(definitionName, null, serviceName, componentName, HOST1, AlertState.OK);
+    alert.setCluster(m_cluster.getClusterName());
+    alert.setLabel(ALERT_LABEL);
+    alert.setText(text);
+    alert.setTimestamp(1L);
+
+    // fire the alert, and check that the new entry was created correctly
+    AlertReceivedListener listener = m_injector.getInstance(AlertReceivedListener.class);
+    AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(), alert);
+    listener.onAlertEvent(event);
+
+    List<AlertCurrentEntity> allCurrent = m_dao.findCurrent();
+    assertEquals(1, allCurrent.size());
+
+    // check occurrences (should be 1 since it's the first) and state (HARD)
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // move the repeat tolerance to 4 to test out SOFT alerts between states
+    AlertDefinitionEntity definition = allCurrent.get(0).getAlertHistory().getAlertDefinition();
+    definition.setRepeatTolerance(4);
+    definition.setRepeatToleranceEnabled(true);
+    m_definitionDao.merge(definition);
+
+    // change state to WARNING, should still be SOFT
+    alert.setState(AlertState.WARNING);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.SOFT, allCurrent.get(0).getFirmness());
+    assertEquals(AlertState.WARNING, allCurrent.get(0).getAlertHistory().getAlertState());
+
+    // change state to CRITICAL, should still be SOFT, but occurrences of non-OK
+    // increases to 2
+    alert.setState(AlertState.CRITICAL);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(2, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.SOFT, allCurrent.get(0).getFirmness());
+    assertEquals(AlertState.CRITICAL, allCurrent.get(0).getAlertHistory().getAlertState());
+
+    // change state to WARNING, should still be SOFT, but occurrences of non-OK
+    // increases to 3
+    alert.setState(AlertState.WARNING);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(3, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.SOFT, allCurrent.get(0).getFirmness());
+    assertEquals(AlertState.WARNING, allCurrent.get(0).getAlertHistory().getAlertState());
+
+    // change state to CRITICAL, occurrences is not met, should be HARD
+    alert.setState(AlertState.CRITICAL);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(4, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+    assertEquals(AlertState.CRITICAL, allCurrent.get(0).getAlertHistory().getAlertState());
+  }
+
+  /**
+   * Tests that {@link SourceType#AGGREGATE} alerts are always HARD.
+   */
+  @Test
+  public void testAggregateAlertFirmness() throws Exception {
+    AlertDefinitionEntity definition = new AlertDefinitionEntity();
+    definition.setDefinitionName("aggregate-alert-firmness-test");
+    definition.setServiceName("HDFS");
+    definition.setComponentName("NAMENODE");
+    definition.setClusterId(m_cluster.getClusterId());
+    definition.setHash(UUID.randomUUID().toString());
+    definition.setScheduleInterval(Integer.valueOf(60));
+    definition.setScope(Scope.SERVICE);
+    definition.setSource("{\"type\" : \"AGGREGATE\"}");
+    definition.setSourceType(SourceType.AGGREGATE);
+
+    // turn this up way high to ensure that we correctly short-circuit these
+    // types of alerts and always consider them HARD
+    definition.setRepeatTolerance(100);
+    definition.setRepeatToleranceEnabled(true);
+
+    m_definitionDao.create(definition);
+
+    Alert alert = new Alert(definition.getDefinitionName(), null, definition.getServiceName(),
+        definition.getComponentName(), HOST1, AlertState.OK);
+
+    alert.setCluster(m_cluster.getClusterName());
+    alert.setLabel(ALERT_LABEL);
+    alert.setText("Aggregate alerts are always HARD");
+    alert.setTimestamp(1L);
+
+    // fire the alert, and check that the new entry was created
+    AlertReceivedListener listener = m_injector.getInstance(AlertReceivedListener.class);
+    AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(), alert);
+    listener.onAlertEvent(event);
+
+    List<AlertCurrentEntity> allCurrent = m_dao.findCurrent();
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // change state
+    alert.setState(AlertState.CRITICAL);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // change state
+    alert.setState(AlertState.WARNING);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(2, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
+
+    // change state
+    alert.setState(AlertState.OK);
+    listener.onAlertEvent(event);
+    allCurrent = m_dao.findCurrent();
+    assertEquals(1, (long) allCurrent.get(0).getOccurrences());
+    assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness());
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertStateChangedEventTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertStateChangedEventTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertStateChangedEventTest.java
index e42e1a7..dad1008 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertStateChangedEventTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertStateChangedEventTest.java
@@ -23,8 +23,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import junit.framework.Assert;
-
 import org.apache.ambari.server.events.AggregateAlertRecalculateEvent;
 import org.apache.ambari.server.events.AlertEvent;
 import org.apache.ambari.server.events.AlertStateChangeEvent;
@@ -34,13 +32,16 @@ import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.orm.entities.AlertGroupEntity;
 import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.orm.entities.AlertNoticeEntity;
 import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
+import org.apache.ambari.server.state.MaintenanceState;
 import org.apache.ambari.server.utils.EventBusSynchronizer;
 import org.easymock.EasyMock;
 import org.junit.After;
@@ -54,9 +55,13 @@ import com.google.inject.Module;
 import com.google.inject.persist.PersistService;
 import com.google.inject.util.Modules;
 
+import junit.framework.Assert;
+
 /**
  * Tests that {@link AlertStateChangeEvent} instances cause
- * {@link AlertNoticeEntity} instances to be created.
+ * {@link AlertNoticeEntity} instances to be created. Outbound notifications
+ * should only be created when received alerts which have a firmness of
+ * {@link AlertFirmness#HARD}.
  */
 public class AlertStateChangedEventTest {
 
@@ -123,28 +128,28 @@ public class AlertStateChangedEventTest {
 
     EasyMock.replay(alertTarget, alertGroup, dispatchDao);
 
-    AlertDefinitionEntity definition = EasyMock.createNiceMock(AlertDefinitionEntity.class);
-    EasyMock.expect(definition.getDefinitionId()).andReturn(1L);
-    EasyMock.expect(definition.getServiceName()).andReturn("HDFS");
-    EasyMock.expect(definition.getLabel()).andReturn("hdfs-foo-alert");
-    EasyMock.expect(definition.getDescription()).andReturn("HDFS Foo Alert");
+    AlertDefinitionEntity definition = getMockAlertDefinition();
 
+    AlertCurrentEntity current = getMockedAlertCurrentEntity();
     AlertHistoryEntity history = EasyMock.createNiceMock(AlertHistoryEntity.class);
     AlertStateChangeEvent event = EasyMock.createNiceMock(AlertStateChangeEvent.class);
     Alert alert = EasyMock.createNiceMock(Alert.class);
 
+    EasyMock.expect(current.getAlertHistory()).andReturn(history).anyTimes();
+    EasyMock.expect(current.getFirmness()).andReturn(AlertFirmness.HARD).atLeastOnce();
     EasyMock.expect(history.getAlertState()).andReturn(AlertState.CRITICAL).atLeastOnce();
     EasyMock.expect(history.getAlertDefinition()).andReturn(definition).atLeastOnce();
     EasyMock.expect(alert.getText()).andReturn("The HDFS Foo Alert Is Not Good").atLeastOnce();
     EasyMock.expect(alert.getState()).andReturn(AlertState.CRITICAL).atLeastOnce();
+    EasyMock.expect(event.getCurrentAlert()).andReturn(current).atLeastOnce();
     EasyMock.expect(event.getNewHistoricalEntry()).andReturn(history).atLeastOnce();
     EasyMock.expect(event.getAlert()).andReturn(alert).atLeastOnce();
 
-    EasyMock.replay(definition, history, event, alert);
+    EasyMock.replay(definition, current, history, event, alert);
 
     // async publishing
     eventPublisher.publish(event);
-    EasyMock.verify(dispatchDao, history, event);
+    EasyMock.verify(dispatchDao, current, history, event);
   }
 
   /**
@@ -177,16 +182,16 @@ public class AlertStateChangedEventTest {
     // that the create alert notice method was not called
     EasyMock.replay(alertTarget, alertGroup, dispatchDao);
 
-    AlertDefinitionEntity definition = EasyMock.createNiceMock(AlertDefinitionEntity.class);
-    EasyMock.expect(definition.getDefinitionId()).andReturn(1L);
-    EasyMock.expect(definition.getServiceName()).andReturn("HDFS");
-    EasyMock.expect(definition.getLabel()).andReturn("hdfs-foo-alert");
-    EasyMock.expect(definition.getDescription()).andReturn("HDFS Foo Alert");
+    AlertDefinitionEntity definition = getMockAlertDefinition();
 
+    AlertCurrentEntity current = getMockedAlertCurrentEntity();
     AlertHistoryEntity history = EasyMock.createNiceMock(AlertHistoryEntity.class);
     AlertStateChangeEvent event = EasyMock.createNiceMock(AlertStateChangeEvent.class);
     Alert alert = EasyMock.createNiceMock(Alert.class);
 
+    EasyMock.expect(current.getAlertHistory()).andReturn(history).anyTimes();
+    EasyMock.expect(current.getFirmness()).andReturn(AlertFirmness.HARD).atLeastOnce();
+
     // use WARNING to ensure that the target (which only cares about OK/CRIT)
     // does not receive the alert notice
     EasyMock.expect(history.getAlertState()).andReturn(AlertState.WARNING).atLeastOnce();
@@ -194,14 +199,77 @@ public class AlertStateChangedEventTest {
     EasyMock.expect(history.getAlertDefinition()).andReturn(definition).atLeastOnce();
     EasyMock.expect(alert.getText()).andReturn("The HDFS Foo Alert Is Not Good").atLeastOnce();
     EasyMock.expect(alert.getState()).andReturn(AlertState.WARNING).atLeastOnce();
+    EasyMock.expect(event.getCurrentAlert()).andReturn(current).atLeastOnce();
     EasyMock.expect(event.getNewHistoricalEntry()).andReturn(history).atLeastOnce();
     EasyMock.expect(event.getAlert()).andReturn(alert).atLeastOnce();
 
-    EasyMock.replay(definition, history, event, alert);
+    EasyMock.replay(definition, current, history, event, alert);
 
     // async publishing
     eventPublisher.publish(event);
-    EasyMock.verify(dispatchDao, history, event);
+    EasyMock.verify(dispatchDao, current, history, event);
+  }
+
+  /**
+   * Tests that an alert with a firmness of {@link AlertFirmness#SOFT} does not
+   * trigger any notifications.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testSoftAlertDoesNotCreateNotifications() throws Exception {
+    EasyMock.replay(dispatchDao);
+
+    AlertDefinitionEntity definition = getMockAlertDefinition();
+
+    AlertCurrentEntity current = getMockedAlertCurrentEntity();
+    AlertHistoryEntity history = EasyMock.createNiceMock(AlertHistoryEntity.class);
+    AlertStateChangeEvent event = EasyMock.createNiceMock(AlertStateChangeEvent.class);
+    Alert alert = EasyMock.createNiceMock(Alert.class);
+
+    // make the alert SOFT so that no notifications are sent
+    EasyMock.expect(current.getAlertHistory()).andReturn(history).anyTimes();
+    EasyMock.expect(current.getFirmness()).andReturn(AlertFirmness.SOFT).atLeastOnce();
+
+
+    EasyMock.expect(history.getAlertDefinition()).andReturn(definition).atLeastOnce();
+    EasyMock.expect(alert.getText()).andReturn("The HDFS Foo Alert Is Not Good").atLeastOnce();
+    EasyMock.expect(alert.getState()).andReturn(AlertState.CRITICAL).atLeastOnce();
+    EasyMock.expect(event.getCurrentAlert()).andReturn(current).atLeastOnce();
+    EasyMock.expect(event.getNewHistoricalEntry()).andReturn(history).atLeastOnce();
+    EasyMock.expect(event.getAlert()).andReturn(alert).atLeastOnce();
+
+    EasyMock.replay(definition, current, history, event, alert);
+
+    // async publishing
+    eventPublisher.publish(event);
+    EasyMock.verify(dispatchDao, current, history, event);
+  }
+
+  /**
+   * Gets an {@link AlertDefinitionEntity} with some mocked calls expected.
+   *
+   * @return
+   */
+  private AlertDefinitionEntity getMockAlertDefinition() {
+    AlertDefinitionEntity definition = EasyMock.createNiceMock(AlertDefinitionEntity.class);
+    EasyMock.expect(definition.getDefinitionId()).andReturn(1L);
+    EasyMock.expect(definition.getServiceName()).andReturn("HDFS");
+    EasyMock.expect(definition.getLabel()).andReturn("hdfs-foo-alert");
+    EasyMock.expect(definition.getDescription()).andReturn("HDFS Foo Alert");
+
+    return definition;
+  }
+
+  /**
+   * Gets an {@link AlertCurrentEntity} with some mocked calls expected.
+   *
+   * @return
+   */
+  private AlertCurrentEntity getMockedAlertCurrentEntity() {
+    AlertCurrentEntity current = EasyMock.createNiceMock(AlertCurrentEntity.class);
+    EasyMock.expect(current.getMaintenanceState()).andReturn(MaintenanceState.OFF).anyTimes();
+    return current;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/InitialAlertEventTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/InitialAlertEventTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/InitialAlertEventTest.java
index 3ddeb2a..1875ba6 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/InitialAlertEventTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/InitialAlertEventTest.java
@@ -17,7 +17,7 @@
  */
 package org.apache.ambari.server.state.alerts;
 
-import junit.framework.Assert;
+import java.util.List;
 
 import org.apache.ambari.server.events.AlertReceivedEvent;
 import org.apache.ambari.server.events.InitialAlertEvent;
@@ -27,8 +27,10 @@ import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
@@ -48,6 +50,8 @@ import com.google.inject.Module;
 import com.google.inject.persist.PersistService;
 import com.google.inject.util.Modules;
 
+import junit.framework.Assert;
+
 /**
  * Tests that {@link InitialAlertEventTest} instances are fired correctly.
  */
@@ -130,17 +134,24 @@ public class InitialAlertEventTest {
     // create the "first" alert
     Alert alert = new Alert(definition.getDefinitionName(), null,
         definition.getServiceName(), definition.getComponentName(), null,
-        AlertState.OK);
+        AlertState.CRITICAL);
+
     alert.setCluster(m_clusterName);
 
-    AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(),
-        alert);
+    AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(), alert);
 
     // public the received event
     m_eventPublisher.publish(event);
 
-    // ensure we now have a history item
+    // ensure we now have a history item and a current
     Assert.assertEquals(1, m_alertsDao.findAll().size());
+    List<AlertCurrentEntity> currentAlerts = m_alertsDao.findCurrent();
+    Assert.assertEquals(1, currentAlerts.size());
+
+    // verify that that initial alert is HARD
+    Assert.assertEquals(AlertFirmness.HARD, currentAlerts.get(0).getFirmness());
+    Assert.assertEquals(AlertState.CRITICAL,
+        currentAlerts.get(0).getAlertHistory().getAlertState());
 
     // verify that the initial alert event was triggered
     Assert.assertEquals(1,

http://git-wip-us.apache.org/repos/asf/ambari/blob/18449b5b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
index 3723ff8..ea0547b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog240Test.java
@@ -53,6 +53,7 @@ import org.apache.ambari.server.orm.DBAccessor;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.orm.dao.StackDAO;
+import org.apache.ambari.server.state.AlertFirmness;
 import org.apache.ambari.server.state.stack.OsFamily;
 import org.easymock.Capture;
 import org.easymock.CaptureType;
@@ -170,10 +171,13 @@ public class UpgradeCatalog240Test {
     Capture<DBAccessor.DBColumnInfo> capturedRepeatToleranceColumnInfo = newCapture();
     Capture<DBAccessor.DBColumnInfo> capturedRepeatToleranceEnabledColumnInfo = newCapture();
     Capture<DBAccessor.DBColumnInfo> capturedOccurrencesColumnInfo = newCapture();
+    Capture<DBAccessor.DBColumnInfo> capturedFirmnessColumnInfo = newCapture();
+    
     dbAccessor.addColumn(eq(UpgradeCatalog240.ALERT_DEFINITION_TABLE), capture(capturedHelpURLColumnInfo));
     dbAccessor.addColumn(eq(UpgradeCatalog240.ALERT_DEFINITION_TABLE), capture(capturedRepeatToleranceColumnInfo));
     dbAccessor.addColumn(eq(UpgradeCatalog240.ALERT_DEFINITION_TABLE), capture(capturedRepeatToleranceEnabledColumnInfo));
     dbAccessor.addColumn(eq(UpgradeCatalog240.ALERT_CURRENT_TABLE), capture(capturedOccurrencesColumnInfo));
+    dbAccessor.addColumn(eq(UpgradeCatalog240.ALERT_CURRENT_TABLE), capture(capturedFirmnessColumnInfo));
 
     // Test creation of blueprint_setting table
     Capture<List<DBAccessor.DBColumnInfo>> capturedBlueprintSettingColumns = EasyMock.newCapture();
@@ -289,9 +293,16 @@ public class UpgradeCatalog240Test {
     DBAccessor.DBColumnInfo columnOccurrencesInfo = capturedOccurrencesColumnInfo.getValue();
     Assert.assertNotNull(columnOccurrencesInfo);
     Assert.assertEquals(UpgradeCatalog240.ALERT_CURRENT_OCCURRENCES_COLUMN, columnOccurrencesInfo.getName());
-    Assert.assertEquals(Integer.class, columnOccurrencesInfo.getType());
+    Assert.assertEquals(Long.class, columnOccurrencesInfo.getType());
     Assert.assertEquals(1, columnOccurrencesInfo.getDefaultValue());
-    Assert.assertEquals(false, columnOccurrencesInfo.isNullable());    
+    Assert.assertEquals(false, columnOccurrencesInfo.isNullable());
+
+    DBAccessor.DBColumnInfo columnFirmnessInfo = capturedFirmnessColumnInfo.getValue();
+    Assert.assertNotNull(columnFirmnessInfo);
+    Assert.assertEquals(UpgradeCatalog240.ALERT_CURRENT_FIRMNESS_COLUMN, columnFirmnessInfo.getName());
+    Assert.assertEquals(String.class, columnFirmnessInfo.getType());
+    Assert.assertEquals(AlertFirmness.HARD.name(), columnFirmnessInfo.getDefaultValue());
+    Assert.assertEquals(false, columnFirmnessInfo.isNullable());    
     
     assertEquals(expectedCaptures, actualCaptures);
 


Mime
View raw message