Browse Source

AMBARI-8514 - Alerts: Create A Global Alert Target For All Alert Groups (jonathanhurley)

Jonathan Hurley 10 năm trước cách đây
mục cha
commit
2518a56a3c
20 tập tin đã thay đổi với 477 bổ sung160 xóa
  1. 2 3
      ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java
  2. 3 3
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
  3. 5 9
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java
  4. 35 8
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java
  5. 35 6
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
  6. 29 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertTargetEntity.java
  7. 18 47
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java
  8. 22 0
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java
  9. 26 10
      ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog200.java
  10. 1 0
      ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
  11. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
  12. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  13. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
  14. 1 0
      ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
  15. 115 65
      ambari-server/src/main/resources/alert-templates.xml
  16. 7 1
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java
  17. 49 1
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java
  18. 17 4
      ambari-server/src/test/java/org/apache/ambari/server/orm/OrmTestHelper.java
  19. 87 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java
  20. 22 1
      ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog200Test.java

+ 2 - 3
ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java

@@ -34,7 +34,6 @@ import javax.ws.rs.core.UriInfo;
 
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.controller.spi.Resource;
-import org.apache.ambari.server.state.alert.AlertGroup;
 
 /**
  * The {@link AlertGroupService} handles CRUD operations for a cluster's alert
@@ -100,8 +99,8 @@ public class AlertGroupService extends BaseService {
   }
 
   /**
-   * Create a request capturing the group ID and resource type for an
-   * {@link AlertGroup}.
+   * Create a request capturing the group ID and resource type for an alert
+   * group.
    *
    * @param groupId
    *          the unique ID of the group to create the query for (not

+ 3 - 3
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java

@@ -233,7 +233,7 @@ public class AlertDefinitionResourceProvider extends AbstractControllerResourceP
       if (null != id) {
         AlertDefinitionEntity entity = alertDefinitionDAO.findById(Long.parseLong(id));
         if (null != entity) {
-          results.add(toResource(false, clusterName, entity, requestPropertyIds));
+          results.add(toResource(clusterName, entity, requestPropertyIds));
         }
       } else {
         Cluster cluster = null;
@@ -247,7 +247,7 @@ public class AlertDefinitionResourceProvider extends AbstractControllerResourceP
             cluster.getClusterId());
 
         for (AlertDefinitionEntity entity : entities) {
-          results.add(toResource(true, clusterName, entity, requestPropertyIds));
+          results.add(toResource(clusterName, entity, requestPropertyIds));
         }
       }
     }
@@ -615,7 +615,7 @@ public class AlertDefinitionResourceProvider extends AbstractControllerResourceP
     return categoryJson;
   }
 
-  private Resource toResource(boolean isCollection, String clusterName,
+  private Resource toResource(String clusterName,
       AlertDefinitionEntity entity, Set<String> requestedIds) {
     Resource resource = new ResourceImpl(Resource.Type.AlertDefinition);
     resource.setProperty(ALERT_DEF_ID, entity.getDefinitionId());

+ 5 - 9
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java

@@ -46,7 +46,6 @@ import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.orm.entities.AlertGroupEntity;
 import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 import org.apache.ambari.server.state.Cluster;
-import org.apache.ambari.server.state.alert.AlertGroup;
 import org.apache.ambari.server.state.alert.AlertTarget;
 import org.apache.commons.lang.StringUtils;
 
@@ -54,7 +53,7 @@ import com.google.inject.Inject;
 
 /**
  * The {@link AlertGroupResourceProvider} class deals with managing the CRUD
- * operations for {@link AlertGroup}, including property coercion to and from
+ * operations alert groups, including property coercion to and from
  * {@link AlertGroupEntity}.
  */
 @StaticallyInject
@@ -154,7 +153,7 @@ public class AlertGroupResourceProvider extends
       if (null != id) {
         AlertGroupEntity entity = s_dao.findGroupById(Long.parseLong(id));
         if (null != entity) {
-          results.add(toResource(false, clusterName, entity, requestPropertyIds));
+          results.add(toResource(clusterName, entity, requestPropertyIds));
         }
       } else {
         Cluster cluster = null;
@@ -168,7 +167,7 @@ public class AlertGroupResourceProvider extends
         List<AlertGroupEntity> entities = s_dao.findAllGroups(cluster.getClusterId());
 
         for (AlertGroupEntity entity : entities) {
-          results.add(toResource(true, clusterName, entity, requestPropertyIds));
+          results.add(toResource(clusterName, entity, requestPropertyIds));
         }
       }
     }
@@ -374,16 +373,13 @@ public class AlertGroupResourceProvider extends
   /**
    * Convert the given {@link AlertGroupEntity} to a {@link Resource}.
    *
-   * @param isCollection
-   *          {@code true} if the resource is part of a collection.
    * @param entity
    *          the entity to convert.
    * @param requestedIds
    *          the properties that were requested or {@code null} for all.
    * @return the resource representation of the entity (never {@code null}).
    */
-  private Resource toResource(boolean isCollection, String clusterName,
-      AlertGroupEntity entity,
+  private Resource toResource(String clusterName, AlertGroupEntity entity,
       Set<String> requestedIds) {
 
     Resource resource = new ResourceImpl(Resource.Type.AlertGroup);
@@ -408,7 +404,7 @@ public class AlertGroupResourceProvider extends
       resource.setProperty(ALERT_GROUP_DEFINITIONS, definitionList);
     }
 
-    if( !isCollection ){
+    if (BaseProvider.isPropertyRequested(ALERT_GROUP_TARGETS, requestedIds)) {
       Set<AlertTargetEntity> targetEntities = entity.getAlertTargets();
 
       List<AlertTarget> targets = new ArrayList<AlertTarget>(

+ 35 - 8
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java

@@ -42,8 +42,10 @@ import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
+import org.apache.ambari.server.orm.entities.AlertGroupEntity;
 import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 import org.apache.ambari.server.state.AlertState;
+import org.apache.ambari.server.state.alert.AlertGroup;
 import org.apache.ambari.server.state.alert.AlertTarget;
 import org.apache.commons.lang.StringUtils;
 
@@ -67,6 +69,7 @@ public class AlertTargetResourceProvider extends
   protected static final String ALERT_TARGET_PROPERTIES = "AlertTarget/properties";
   protected static final String ALERT_TARGET_GROUPS = "AlertTarget/groups";
   protected static final String ALERT_TARGET_STATES = "AlertTarget/alert_states";
+  protected static final String ALERT_TARGET_GLOBAL = "AlertTarget/global";
 
   private static final Set<String> PK_PROPERTY_IDS = new HashSet<String>(
       Arrays.asList(ALERT_TARGET_ID, ALERT_TARGET_NAME));
@@ -90,6 +93,7 @@ public class AlertTargetResourceProvider extends
     PROPERTY_IDS.add(ALERT_TARGET_PROPERTIES);
     PROPERTY_IDS.add(ALERT_TARGET_GROUPS);
     PROPERTY_IDS.add(ALERT_TARGET_STATES);
+    PROPERTY_IDS.add(ALERT_TARGET_GLOBAL);
 
     // keys
     KEY_PROPERTY_IDS.put(Resource.Type.AlertTarget, ALERT_TARGET_ID);
@@ -142,7 +146,7 @@ public class AlertTargetResourceProvider extends
     if( null == predicate ){
       List<AlertTargetEntity> entities = s_dao.findAllTargets();
       for (AlertTargetEntity entity : entities) {
-        results.add(toResource(true, entity, requestPropertyIds));
+        results.add(toResource(entity, requestPropertyIds));
       }
     } else {
       for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
@@ -153,7 +157,7 @@ public class AlertTargetResourceProvider extends
 
         AlertTargetEntity entity = s_dao.findTargetById(Long.parseLong(id));
         if (null != entity) {
-          results.add(toResource(false, entity, requestPropertyIds));
+          results.add(toResource(entity, requestPropertyIds));
         }
       }
     }
@@ -234,6 +238,7 @@ public class AlertTargetResourceProvider extends
       String description = (String) requestMap.get(ALERT_TARGET_DESCRIPTION);
       String notificationType = (String) requestMap.get(ALERT_TARGET_NOTIFICATION_TYPE);
       Collection<String> alertStates = (Collection<String>) requestMap.get(ALERT_TARGET_STATES);
+      String globalProperty = (String) requestMap.get(ALERT_TARGET_GLOBAL);
 
       if (StringUtils.isEmpty(name)) {
         throw new IllegalArgumentException(
@@ -251,6 +256,12 @@ public class AlertTargetResourceProvider extends
             "Alert targets must be created with their connection properties");
       }
 
+      // global not required
+      boolean isGlobal = false;
+      if (null != globalProperty) {
+        isGlobal = Boolean.parseBoolean(globalProperty);
+      }
+
       // set the states that this alert target cares about
       final Set<AlertState> alertStateSet;
       if (null != alertStates) {
@@ -267,6 +278,7 @@ public class AlertTargetResourceProvider extends
       entity.setProperties(properties);
       entity.setTargetName(name);
       entity.setAlertStates(alertStateSet);
+      entity.setGlobal(isGlobal);
 
       entities.add(entity);
     }
@@ -349,18 +361,13 @@ public class AlertTargetResourceProvider extends
   /**
    * Convert the given {@link AlertTargetEntity} to a {@link Resource}.
    *
-   * @param isCollection
-   *          {@code true} if the resource is part of a collection. Some
-   *          properties are not returned when a collection of targets is
-   *          requested.
    * @param entity
    *          the entity to convert.
    * @param requestedIds
    *          the properties that were requested or {@code null} for all.
    * @return the resource representation of the entity (never {@code null}).
    */
-  private Resource toResource(boolean isCollection, AlertTargetEntity entity,
-      Set<String> requestedIds) {
+  private Resource toResource(AlertTargetEntity entity, Set<String> requestedIds) {
 
     Resource resource = new ResourceImpl(Resource.Type.AlertTarget);
     resource.setProperty(ALERT_TARGET_ID, entity.getTargetId());
@@ -383,6 +390,26 @@ public class AlertTargetResourceProvider extends
     setResourceProperty(resource, ALERT_TARGET_STATES, entity.getAlertStates(),
         requestedIds);
 
+    setResourceProperty(resource, ALERT_TARGET_GLOBAL, entity.isGlobal(),
+        requestedIds);
+
+    if (BaseProvider.isPropertyRequested(ALERT_TARGET_GROUPS, requestedIds)) {
+      Set<AlertGroupEntity> groupEntities = entity.getAlertGroups();
+      List<AlertGroup> groups = new ArrayList<AlertGroup>(
+          groupEntities.size());
+
+      for (AlertGroupEntity groupEntity : groupEntities) {
+        AlertGroup group = new AlertGroup();
+        group.setId(groupEntity.getGroupId());
+        group.setName(groupEntity.getGroupName());
+        group.setClusterName(groupEntity.getClusterId());
+        group.setDefault(groupEntity.isDefault());
+        groups.add(group);
+      }
+
+      resource.setProperty(ALERT_TARGET_GROUPS, groups);
+    }
+
     return resource;
   }
 

+ 35 - 6
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java

@@ -45,7 +45,6 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.NotificationState;
 import org.apache.ambari.server.state.Service;
-import org.apache.ambari.server.state.alert.AlertGroup;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -257,8 +256,21 @@ public class AlertDispatchDAO {
   }
 
   /**
-   * Gets all of the {@link AlertGroup} instances that include the specified
-   * alert definition. Service default groups will also be returned.
+   * Gets all global alert targets stored in the database.
+   *
+   * @return all global alert targets or empty list if none exist (never
+   *         {@code null}).
+   */
+  public List<AlertTargetEntity> findAllGlobalTargets() {
+    TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertTargetEntity.findAllGlobal", AlertTargetEntity.class);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets all of the {@link AlertGroupEntity} instances that include the
+   * specified alert definition. Service default groups will also be returned.
    *
    * @param definitionEntity
    *          the definition that the group must include (not {@code null}).
@@ -381,12 +393,21 @@ public class AlertDispatchDAO {
   /**
    * Persists a new alert group.
    *
-   * @param alertGroup
+   * @param group
    *          the group to persist (not {@code null}).
    */
   @Transactional
-  public void create(AlertGroupEntity alertGroup) {
-    entityManagerProvider.get().persist(alertGroup);
+  public void create(AlertGroupEntity group) {
+    entityManagerProvider.get().persist(group);
+
+    // associate the group with all alert targets
+    List<AlertTargetEntity> targets = findAllGlobalTargets();
+    if (!targets.isEmpty()) {
+      for (AlertTargetEntity target : targets) {
+        group.addAlertTarget(target);
+      }
+      entityManagerProvider.get().merge(group);
+    }
   }
 
   /**
@@ -507,6 +528,14 @@ public class AlertDispatchDAO {
   @Transactional
   public void create(AlertTargetEntity alertTarget) {
     entityManagerProvider.get().persist(alertTarget);
+
+    if (alertTarget.isGlobal()) {
+      List<AlertGroupEntity> groups = findAllGroups();
+      for (AlertGroupEntity group : groups) {
+        group.addAlertTarget(alertTarget);
+        merge(group);
+      }
+    }
   }
 
   /**

+ 29 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertTargetEntity.java

@@ -44,6 +44,8 @@ import javax.persistence.TableGenerator;
 
 import org.apache.ambari.server.state.AlertState;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * The {@link AlertTargetEntity} class represents audience that will receive
  * dispatches when an alert is triggered.
@@ -53,6 +55,7 @@ import org.apache.ambari.server.state.AlertState;
 @TableGenerator(name = "alert_target_id_generator", table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value", pkColumnValue = "alert_target_id_seq", initialValue = 0, allocationSize = 1)
 @NamedQueries({
     @NamedQuery(name = "AlertTargetEntity.findAll", query = "SELECT alertTarget FROM AlertTargetEntity alertTarget"),
+    @NamedQuery(name = "AlertTargetEntity.findAllGlobal", query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.isGlobal = 1"),
     @NamedQuery(name = "AlertTargetEntity.findByName", query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.targetName = :targetName"),
     @NamedQuery(name = "AlertTargetEntity.findByIds", query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.targetId IN :targetIds") })
 public class AlertTargetEntity {
@@ -76,6 +79,9 @@ public class AlertTargetEntity {
   @Column(name = "target_name", unique = true, nullable = false, length = 255)
   private String targetName;
 
+  @Column(name = "is_global", nullable = false, length = 1)
+  private Integer isGlobal = Integer.valueOf(0);
+
   /**
    * Bi-directional many-to-many association to {@link AlertGroupEntity}
    */
@@ -170,6 +176,27 @@ public class AlertTargetEntity {
     return targetName;
   }
 
+  /**
+   * Gets whether the alert target is a global target and will receive
+   * notifications triggered for any alert group.
+   *
+   * @return the isGlobal {@code} if the target is global.
+   */
+  public boolean isGlobal() {
+    return isGlobal == 0 ? false : true;
+  }
+
+  /**
+   * Sets whether the alert target is a global target and will receive
+   * notifications triggered for any alert group.
+   *
+   * @param isGlobal
+   *          {@code} if the target is global.
+   */
+  public void setGlobal(boolean isGlobal) {
+    this.isGlobal = isGlobal ? 1 : 0;
+  }
+
   /**
    * Gets the alert states that will cause a triggered alert to be sent to this
    * target. A target may be associated with a group where an alert has changed
@@ -217,7 +244,7 @@ public class AlertTargetEntity {
       return Collections.emptySet();
     }
 
-    return Collections.unmodifiableSet(alertGroups);
+    return ImmutableSet.copyOf(alertGroups);
   }
 
   /**
@@ -254,7 +281,7 @@ public class AlertTargetEntity {
   @PreRemove
   public void preRemove() {
     Set<AlertGroupEntity> groups = getAlertGroups();
-    if (null == groups || groups.size() == 0) {
+    if (groups.isEmpty()) {
       return;
     }
 

+ 18 - 47
ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java

@@ -17,25 +17,23 @@
  */
 package org.apache.ambari.server.state.alert;
 
-import java.util.List;
+import org.codehaus.jackson.annotate.JsonProperty;
 
 /**
- * The {@link AlertGroup} class represents a grouping of {@link AlertDefinition}
- * instances as well as the targets that will be invoked when an alert is
- * triggered.
+ * The {@link AlertGroup} class is used to represent a grouping of alert
+ * definitions when returning alert targets.
  */
 public class AlertGroup {
-  private String m_id;
+  private long m_id;
   private String m_name;
-  private String m_clusterName;
+  private long m_clusterId;
   private boolean m_isDefault;
-  private List<AlertDefinition> m_definitions;
-  private List<AlertTarget> m_targets;
 
   /**
    * @return the id
    */
-  public String getId() {
+  @JsonProperty("id")
+  public long getId() {
     return m_id;
   }
 
@@ -43,13 +41,14 @@ public class AlertGroup {
    * @param id
    *          the id to set
    */
-  public void setId(String id) {
+  public void setId(long id) {
     m_id = id;
   }
 
   /**
    * @return the name
    */
+  @JsonProperty("name")
   public String getName() {
     return m_name;
   }
@@ -63,23 +62,25 @@ public class AlertGroup {
   }
 
   /**
-   * @return the clusterName
+   * @return the cluster ID
    */
-  public String getClusterName() {
-    return m_clusterName;
+  @JsonProperty("cluster_id")
+  public long getClusterId() {
+    return m_clusterId;
   }
 
   /**
-   * @param clusterName
-   *          the clusterName to set
+   * @param clusterId
+   *          the cluster id to set
    */
-  public void setClusterName(String clusterName) {
-    m_clusterName = clusterName;
+  public void setClusterName(long clusterId) {
+    m_clusterId = clusterId;
   }
 
   /**
    * @return the isDefault
    */
+  @JsonProperty("default")
   public boolean isDefault() {
     return m_isDefault;
   }
@@ -91,34 +92,4 @@ public class AlertGroup {
   public void setDefault(boolean isDefault) {
     m_isDefault = isDefault;
   }
-
-  /**
-   * @return the definitions
-   */
-  public List<AlertDefinition> getDefinitions() {
-    return m_definitions;
-  }
-
-  /**
-   * @param definitions
-   *          the definitions to set
-   */
-  public void setDefinitions(List<AlertDefinition> definitions) {
-    m_definitions = definitions;
-  }
-
-  /**
-   * @return the targets
-   */
-  public List<AlertTarget> getTargets() {
-    return m_targets;
-  }
-
-  /**
-   * @param targets
-   *          the targets to set
-   */
-  public void setTargets(List<AlertTarget> targets) {
-    m_targets = targets;
-  }
 }

+ 22 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java

@@ -34,6 +34,7 @@ public class AlertTarget {
   private String m_description;
   private String m_notificationType;
   private Map<String, String> m_properties;
+  private boolean m_isGlobal;
 
   /**
    * @return the id
@@ -115,6 +116,26 @@ public class AlertTarget {
     m_properties = properties;
   }
 
+  /**
+   * Gets whether the alert target is global.
+   *
+   * @return {@code true} if global.
+   */
+  @JsonProperty("global")
+  public boolean isGlobal() {
+    return m_isGlobal;
+  }
+
+  /**
+   * Sets whether the alert target is global.
+   *
+   * @param isGlobal
+   *          {@code true} if the alert target is global.
+   */
+  public void setGlobal(boolean isGlobal) {
+    m_isGlobal = isGlobal;
+  }
+
   /**
    * @param entity
    * @return
@@ -125,6 +146,7 @@ public class AlertTarget {
     target.setDescription(entity.getDescription());
     target.setName(entity.getTargetName());
     target.setNotificationType(entity.getNotificationType());
+    target.setGlobal(entity.isGlobal());
     return target;
   }
 }

+ 26 - 10
ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog200.java

@@ -100,10 +100,7 @@ public class UpgradeCatalog200 extends AbstractUpgradeCatalog {
   @Override
   protected void executeDDLUpdates() throws AmbariException, SQLException {
     prepareRollingUpgradesDDL();
-
-    // add ignore_host column to alert_definition
-    dbAccessor.addColumn(ALERT_DEFINITION_TABLE, new DBColumnInfo(
-        "ignore_host", Short.class, 1, 0, false));
+    executeAlertDDLUpdates();
 
     // add security_state to various tables
     dbAccessor.addColumn("hostcomponentdesiredstate", new DBColumnInfo(
@@ -113,9 +110,34 @@ public class UpgradeCatalog200 extends AbstractUpgradeCatalog {
     dbAccessor.addColumn("servicedesiredstate", new DBColumnInfo(
         "security_state", String.class, 32, SecurityState.UNSECURED.toString(), false));
 
+    // Alter column : make viewinstanceproperty.value & viewinstancedata.value
+    // nullable
+    dbAccessor.alterColumn("viewinstanceproperty", new DBColumnInfo("value",
+        String.class, 2000, null, true));
+    dbAccessor.alterColumn("viewinstancedata", new DBColumnInfo("value",
+        String.class, 2000, null, true));
+
+    ddlUpdateRepositoryVersion();
+  }
+
+  /**
+   * Execute all of the alert DDL updates.
+   *
+   * @throws AmbariException
+   * @throws SQLException
+   */
+  private void executeAlertDDLUpdates() throws AmbariException, SQLException {
+    // add ignore_host column to alert_definition
+    dbAccessor.addColumn(ALERT_DEFINITION_TABLE, new DBColumnInfo(
+        "ignore_host", Short.class, 1, 0, false));
+
     dbAccessor.addColumn(ALERT_DEFINITION_TABLE, new DBColumnInfo(
         "description", char[].class, 32672, null, true));
 
+    // update alert target
+    dbAccessor.addColumn(ALERT_TARGET_TABLE, new DBColumnInfo("is_global",
+        Short.class, 1, 0, false));
+
     // create alert_target_states table
     ArrayList<DBColumnInfo> columns = new ArrayList<DBColumnInfo>();
     columns.add(new DBColumnInfo("target_id", Long.class, null, null, false));
@@ -124,12 +146,6 @@ public class UpgradeCatalog200 extends AbstractUpgradeCatalog {
     dbAccessor.addFKConstraint(ALERT_TARGET_STATES_TABLE,
         "fk_alert_target_states_target_id", "target_id", ALERT_TARGET_TABLE,
         "target_id", false);
-
-    // Alter column : make viewinstanceproperty.value & viewinstancedata.value nullable
-    dbAccessor.alterColumn("viewinstanceproperty", new DBColumnInfo("value", String.class, 2000, null, true));
-    dbAccessor.alterColumn("viewinstancedata", new DBColumnInfo("value", String.class, 2000, null, true));
-
-    ddlUpdateRepositoryVersion();
   }
 
   /**

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql

@@ -652,6 +652,7 @@ CREATE TABLE alert_target (
   notification_type VARCHAR(64) NOT NULL,
   properties TEXT,
   description VARCHAR(1024),
+  is_global SMALLINT NOT NULL DEFAULT 0,
   PRIMARY KEY (target_id)
 );
 

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql

@@ -642,6 +642,7 @@ CREATE TABLE alert_target (
   notification_type VARCHAR2(64) NOT NULL,
   properties CLOB,
   description VARCHAR2(1024),
+  is_global NUMBER(1) DEFAULT 0 NOT NULL,
   PRIMARY KEY (target_id)
 );
 

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql

@@ -639,6 +639,7 @@ CREATE TABLE alert_target (
   notification_type VARCHAR(64) NOT NULL,
   properties TEXT,
   description VARCHAR(1024),
+  is_global SMALLINT NOT NULL DEFAULT 0,
   PRIMARY KEY (target_id)
 );
 

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql

@@ -712,6 +712,7 @@ CREATE TABLE ambari.alert_target (
   notification_type VARCHAR(64) NOT NULL,
   properties TEXT,
   description VARCHAR(1024),
+  is_global SMALLINT NOT NULL DEFAULT 0,
   PRIMARY KEY (target_id)
 );
 

+ 1 - 0
ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql

@@ -224,6 +224,7 @@ CREATE TABLE alert_target (
   notification_type VARCHAR(64) NOT NULL,
   properties TEXT,
   description VARCHAR(1024),
+  is_global SMALLINT NOT NULL DEFAULT 0,
   PRIMARY KEY (target_id)
 );
 

+ 115 - 65
ambari-server/src/main/resources/alert-templates.xml

@@ -26,97 +26,147 @@
 #set( $services = $summary.getServices() )
 <html>
   <style type="text/css">
-    .ok{
-      color: green;
-    }
-    .warning{
-      color: ffff00;
-    }
-    .critical{
-      color: red;
-    }
-    .unknown{
-      color: brown;
-    }
-    .section{
-      background-color: #eee;
-      padding: 20px;
-      margin-top: 10px;
-      margin-left: 20px;
-      margin-right: 20px;
-      text-align: left;
-      border-radius: 10px;
-      font-family: Arial, Helvetica, sans-serif;
+    html {
+      font-family:sans-serif;
+      -webkit-text-size-adjust:100%;
+      -ms-text-size-adjust:100%;
     }
-    .footer{
-      margin-top: 10px;
-      margin-left: 20px;
-      margin-right: 20px;
-      font-family: Arial, Helvetica, sans-serif;
-      font-size: 12px;
+    body {
+      margin:10px;
     }
-    .alert{    
-      margin: 5px;
-      font-weight: normal;
-      font-size: 14px;   
+    footer,header {
+      display:block;
     }
-    .alertText{
-      font-size: 12px;
+    table {
+      border-spacing:0;
+      border-collapse:collapse;
     }
-    h1{
-      margin: 0 0 0.5em 0;
-      color: #343434;
-      font-weight: normal;
-      font-size: 20px;
+    td,th {
+      padding:10px;
     }
-    table {
-      width: 100%;
+    .panel {
+      margin-bottom:20px;
+      background-color:#fff;
+      border:1px solid transparent;
+      border-radius:4px;
+      -webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);
+      box-shadow:0 1px 1px rgba(0,0,0,.05);
+    }
+    .panel-body {
+      padding:15px;
+    }
+    .panel-heading {
+      padding:10px 15px;
+      border-bottom:1px solid transparent;
+      border-top-left-radius:3px;
+      border-top-right-radius:3px;
+    }
+    .panel-title {
+      margin-top:0;
+      margin-bottom:0;
+      font-size:16px;
+      color:inherit;
+    }
+    .panel-default {
+      border-color:#ddd;
+    }
+    .panel-default > .panel-heading {
+      color:#333;
+      background-color:#f5f5f5;
+      border-color:#ddd;
+    }
+    .panel-primary {
+      border-color: #337ab7;
+    }
+    .panel-primary > .panel-heading {
+      color: #fff;
+      background-color: #337ab7;
+      border-color: #337ab7;
+    }
+    .label {
+      display:inline;
+      padding:.3em 1em;
+      font-size:75%;
+      font-weight:bold;
+      line-height:1;
+      color:#fff;
+      text-align:center;
+      white-space:nowrap;
+      vertical-align:baseline;
+      border-radius:.25em;
+    }
+    .label-unknown {
+      background-color:#777;
+    }
+    .label-primary {
+      background-color:#337ab7;
     }
-    tr{
-      margin-botton: 5px;
-    }    
-    td{
-      text-align: left;
-      vertical-align: top;
+    .label-ok {
+      background-color:#5cb85c;
+    }
+    .label-warning {
+      background-color:#f0ad4e;
+    }
+    .label-critical {
+      background-color:#d9534f;
+    }
+    .label-small {
+      font-size:12px;
+    }
+    .ambari-footer{
+      font-family: Arial, Helvetica, sans-serif;
+      font-size: 12px;    
     }
   </style>
-  <div class="section">
-    <h1>Services Reporting Alerts</h1>  
-      #foreach( $alertState in $alertStates )
-        #if( $summary.getServicesByAlertState($alertState)  )
-          <div class="alert">
-            <span class="$alertState">$alertState</span> $summary.getServicesByAlertState($alertState)
-          </div>
+  <div class="panel panel-primary">
+    <div class="panel-heading">
+      <h3 class="panel-title">Services Reporting Alerts</h3>
+    </div>
+    <div class="panel-body">
+      <table>
+        #foreach( $alertState in $alertStates )
+          #if( $summary.getServicesByAlertState($alertState)  )
+            <tr>
+              <td>
+                <span class="label label-$alertState">$alertState</span>
+              </td>
+              <td>
+                $summary.getServicesByAlertState($alertState)
+              </td>
+            </tr>
+          #end
         #end
-      #end
+      </table> 
+    </div>
   </div>
 
   #foreach( $service in $services )
-    <div class="section">
-      <h1>$service</h1>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">$service</h3>
+    </div>
+    <div class="panel-body">
       <table>
         #foreach( $alertState in $alertStates )
             #foreach( $alert in $summary.getAlerts($service,$alertState) )
-              <tr class="alert">
-                <td width="100px">
-                  <div class="$alertState">
-                    $alertState
-                  </div>
+              <tr>
+                <td>
+                  <span class="label label-$alertState">$alertState</span>
                 </td>
                 <td>
                   $alert.getAlertDefinition().getLabel()
-                  <br/>
-                  <span class="alertText">
+                  <div class="label-small">
                     $alert.getAlertText()
-                  <span>
+                  </div>
                 </td>
               </tr>
             #end
         #end
       </table>
     </div>
+  </div>
   #end
-  <div class="footer">
+  <div class="ambari-footer">
     This notification was sent to $dispatch.getTargetName()
     <br/>
     Apache Ambari $ambari.getServerVersion()

+ 7 - 1
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java

@@ -173,6 +173,9 @@ public class AlertGroupResourceProviderTest {
     // verify definitions do not come back when not requested
     assertNull(r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_DEFINITIONS));
 
+    // verify alerts do not come back when not requested
+    assertNull(r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_TARGETS));
+
     verify(m_amc, m_clusters, m_cluster, m_dao);
   }
 
@@ -208,13 +211,16 @@ public class AlertGroupResourceProviderTest {
         r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME));
 
 
-    // verify definitions do not come back when not requested
+    // verify definitions and targets come back when requested
     List<AlertDefinitionResponse> definitions = (List<AlertDefinitionResponse>) r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_DEFINITIONS);
+    List<AlertTarget> targets = (List<AlertTarget>) r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_TARGETS);
 
     assertNotNull(definitions);
     assertEquals(1, definitions.size());
     assertEquals(ALERT_DEF_NAME, definitions.get(0).getName());
     assertEquals(SourceType.METRIC, definitions.get(0).getSourceType());
+    assertNotNull(targets);
+    assertEquals(1, targets.size());
 
     verify(m_amc, m_clusters, m_cluster, m_dao);
   }

+ 49 - 1
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java

@@ -127,6 +127,7 @@ public class AlertTargetResourceProviderTest {
 
     assertNull(properties);
     assertNull(alertStates);
+    assertNull(resource.getPropertyValue(AlertTargetResourceProvider.ALERT_TARGET_GLOBAL));
 
     verify(m_dao);
   }
@@ -142,7 +143,8 @@ public class AlertTargetResourceProviderTest {
         AlertTargetResourceProvider.ALERT_TARGET_ID,
         AlertTargetResourceProvider.ALERT_TARGET_NAME,
         AlertTargetResourceProvider.ALERT_TARGET_NOTIFICATION_TYPE,
-        AlertTargetResourceProvider.ALERT_TARGET_STATES);
+        AlertTargetResourceProvider.ALERT_TARGET_STATES,
+        AlertTargetResourceProvider.ALERT_TARGET_GLOBAL);
 
     Predicate predicate = new PredicateBuilder().property(
         AlertTargetResourceProvider.ALERT_TARGET_ID).equals(
@@ -177,6 +179,10 @@ public class AlertTargetResourceProviderTest {
 
     assertNull(properties);
 
+    assertEquals(
+        Boolean.FALSE,
+        resource.getPropertyValue(AlertTargetResourceProvider.ALERT_TARGET_GLOBAL));
+
     // ask for all fields
     request = PropertyHelper.getReadRequest();
     results = provider.getResources(request, predicate);
@@ -219,6 +225,48 @@ public class AlertTargetResourceProviderTest {
     assertEquals(ALERT_TARGET_DESC, entity.getDescription());
     assertEquals(ALERT_TARGET_TYPE, entity.getNotificationType());
     assertEquals(ALERT_TARGET_PROPS, entity.getProperties());
+    assertEquals(false, entity.isGlobal());
+
+    // no alert states were set explicitely in the request, so all should be set
+    // by the backend
+    assertNotNull(entity.getAlertStates());
+    assertEquals(EnumSet.allOf(AlertState.class), entity.getAlertStates());
+
+    verify(m_amc, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testCreateGlobalTarget() throws Exception {
+    Capture<List<AlertTargetEntity>> listCapture = new Capture<List<AlertTargetEntity>>();
+
+    m_dao.createTargets(capture(listCapture));
+    expectLastCall();
+
+    replay(m_amc, m_dao);
+
+    AlertTargetResourceProvider provider = createProvider(m_amc);
+    Map<String, Object> requestProps = getCreationProperties();
+
+    // make this alert target global
+    requestProps.put(AlertTargetResourceProvider.ALERT_TARGET_GLOBAL, "true");
+
+    Request request = PropertyHelper.getCreateRequest(
+        Collections.singleton(requestProps), null);
+
+    provider.createResources(request);
+
+    Assert.assertTrue(listCapture.hasCaptured());
+    AlertTargetEntity entity = listCapture.getValue().get(0);
+    Assert.assertNotNull(entity);
+
+    assertEquals(ALERT_TARGET_NAME, entity.getTargetName());
+    assertEquals(ALERT_TARGET_DESC, entity.getDescription());
+    assertEquals(ALERT_TARGET_TYPE, entity.getNotificationType());
+    assertEquals(ALERT_TARGET_PROPS, entity.getProperties());
+    assertEquals(true, entity.isGlobal());
 
     // no alert states were set explicitely in the request, so all should be set
     // by the backend

+ 17 - 4
ambari-server/src/test/java/org/apache/ambari/server/orm/OrmTestHelper.java

@@ -433,7 +433,6 @@ public class OrmTestHelper {
    *
    * @return
    */
-  @Transactional
   public AlertTargetEntity createAlertTarget() throws Exception {
     AlertTargetEntity target = new AlertTargetEntity();
     target.setDescription("Target Description");
@@ -445,6 +444,23 @@ public class OrmTestHelper {
     return alertDispatchDAO.findTargetById(target.getTargetId());
   }
 
+  /**
+   * Creates a global alert target.
+   *
+   * @return
+   */
+  public AlertTargetEntity createGlobalAlertTarget() throws Exception {
+    AlertTargetEntity target = new AlertTargetEntity();
+    target.setDescription("Target Description");
+    target.setNotificationType("EMAIL");
+    target.setProperties("Target Properties");
+    target.setTargetName("Target Name " + System.currentTimeMillis());
+    target.setGlobal(true);
+
+    alertDispatchDAO.create(target);
+    return alertDispatchDAO.findTargetById(target.getTargetId());
+  }
+
   /**
    * Creates an alert definition.
    *
@@ -452,7 +468,6 @@ public class OrmTestHelper {
    * @return
    * @throws Exception
    */
-  @Transactional
   public AlertDefinitionEntity createAlertDefinition(long clusterId)
       throws Exception {
     AlertDefinitionEntity definition = new AlertDefinitionEntity();
@@ -479,7 +494,6 @@ public class OrmTestHelper {
    * @return
    * @throws Exception
    */
-  @Transactional
   public AlertGroupEntity createAlertGroup(long clusterId,
       Set<AlertTargetEntity> targets) throws Exception {
     AlertGroupEntity group = new AlertGroupEntity();
@@ -499,7 +513,6 @@ public class OrmTestHelper {
    * @return
    * @throws Exception
    */
-  @Transactional
   public List<AlertGroupEntity> createDefaultAlertGroups(long clusterId)
       throws Exception {
     AlertGroupEntity hdfsGroup = new AlertGroupEntity();

+ 87 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDispatchDAOTest.java

@@ -172,6 +172,23 @@ public class AlertDispatchDAOTest {
     assertEquals(2, targets.size());
   }
 
+  /**
+   *
+   */
+  @Test
+  public void testFindAllGlobalTargets() throws Exception {
+    List<AlertTargetEntity> targets = m_dao.findAllGlobalTargets();
+    assertNotNull(targets);
+    assertEquals(0, targets.size());
+
+    m_helper.createGlobalAlertTarget();
+    m_helper.createGlobalAlertTarget();
+    m_helper.createGlobalAlertTarget();
+
+    targets = m_dao.findAllGlobalTargets();
+    assertEquals(3, targets.size());
+  }
+
   /**
    *
    */
@@ -281,6 +298,7 @@ public class AlertDispatchDAOTest {
 
     AlertGroupEntity group = m_helper.createAlertGroup(
         m_cluster.getClusterId(), targets);
+
     AlertTargetEntity actual = m_dao.findTargetById(target.getTargetId());
     assertNotNull(actual);
 
@@ -288,6 +306,7 @@ public class AlertDispatchDAOTest {
     assertEquals(target.getDescription(), actual.getDescription());
     assertEquals(target.getNotificationType(), actual.getNotificationType());
     assertEquals(target.getProperties(), actual.getProperties());
+    assertEquals(false, actual.isGlobal());
 
     assertNotNull(actual.getAlertGroups());
     Iterator<AlertGroupEntity> iterator = actual.getAlertGroups().iterator();
@@ -298,6 +317,74 @@ public class AlertDispatchDAOTest {
     assertEquals(targetCount + 1, m_dao.findAllTargets().size());
   }
 
+  /**
+   *
+   */
+  @Test
+  public void testCreateGlobalTarget() throws Exception {
+    AlertTargetEntity target = m_helper.createGlobalAlertTarget();
+    assertTrue( target.isGlobal() );
+
+    target = m_dao.findTargetByName(target.getTargetName());
+    assertTrue( target.isGlobal() );
+  }
+
+  /**
+   *
+   */
+  @Test
+  public void testGlobalTargetAssociations() throws Exception {
+    AlertGroupEntity group = m_helper.createAlertGroup(
+        m_cluster.getClusterId(), null);
+
+    group = m_dao.findGroupById(group.getGroupId());
+    assertNotNull(group);
+    assertEquals(0, group.getAlertTargets().size());
+
+    AlertTargetEntity target = m_helper.createGlobalAlertTarget();
+    assertTrue(target.isGlobal());
+
+    group = m_dao.findGroupById(group.getGroupId());
+    assertNotNull(group);
+    assertEquals(1, group.getAlertTargets().size());
+
+    List<AlertGroupEntity> groups = m_dao.findAllGroups();
+    target = m_dao.findTargetById(target.getTargetId());
+    assertEquals(groups.size(), target.getAlertGroups().size());
+
+    m_dao.remove(target);
+
+    group = m_dao.findGroupById(group.getGroupId());
+    assertNotNull(group);
+    assertEquals(0, group.getAlertTargets().size());
+  }
+
+  /**
+   * Tests that a newly created group is correctly associated with all global
+   * targets.
+   */
+  @Test
+  public void testGlobalTargetAssociatedWithNewGroup() throws Exception {
+    AlertTargetEntity target1 = m_helper.createGlobalAlertTarget();
+    AlertTargetEntity target2 = m_helper.createGlobalAlertTarget();
+    assertTrue(target1.isGlobal());
+    assertTrue(target2.isGlobal());
+
+    AlertGroupEntity group = m_helper.createAlertGroup(
+        m_cluster.getClusterId(), null);
+
+    group = m_dao.findGroupById(group.getGroupId());
+    assertNotNull(group);
+    assertEquals(2, group.getAlertTargets().size());
+
+    Iterator<AlertTargetEntity> iterator = group.getAlertTargets().iterator();
+    AlertTargetEntity groupTarget1 = iterator.next();
+    AlertTargetEntity groupTarget2 = iterator.next();
+
+    assertTrue(groupTarget1.isGlobal());
+    assertTrue(groupTarget2.isGlobal());
+  }
+
   /**
    *
    */

+ 22 - 1
ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog200Test.java

@@ -113,6 +113,7 @@ public class UpgradeCatalog200Test {
 
     Capture<DBAccessor.DBColumnInfo> alertDefinitionIgnoreColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
     Capture<DBAccessor.DBColumnInfo> alertDefinitionDescriptionColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
+    Capture<DBAccessor.DBColumnInfo> alertTargetGlobalColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
     Capture<DBAccessor.DBColumnInfo> hostComponentStateColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
     Capture<DBAccessor.DBColumnInfo> hostComponentStateSecurityStateColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
     Capture<DBAccessor.DBColumnInfo> hostComponentDesiredStateSecurityStateColumnCapture = new Capture<DBAccessor.DBColumnInfo>();
@@ -137,6 +138,10 @@ public class UpgradeCatalog200Test {
     dbAccessor.createTable(eq("alert_target_states"),
         capture(alertTargetStatesCapture), eq("target_id"));
 
+    // alert target
+    dbAccessor.addColumn(eq("alert_target"),
+        capture(alertTargetGlobalColumnCapture));
+
     // Host Component State
     dbAccessor.addColumn(eq("hostcomponentstate"),
         capture(hostComponentStateColumnCapture));
@@ -189,6 +194,9 @@ public class UpgradeCatalog200Test {
     verifyAlertDefinitionIgnoreColumn(alertDefinitionIgnoreColumnCapture);
     verifyAlertDefinitionDescriptionColumn(alertDefinitionDescriptionColumnCapture);
 
+    // verify alert target column for is_global
+    verifyAlertTargetGlobal(alertTargetGlobalColumnCapture);
+
     // verify new table for alert target states
     verifyAlertTargetStatesTable(alertTargetStatesCapture);
 
@@ -345,13 +353,26 @@ public class UpgradeCatalog200Test {
   /**
    * Verifies alert_target_states table.
    *
-   * @param alertTargetStatesColumnCapture
+   * @param alertTargetStatesCapture
    */
   private void verifyAlertTargetStatesTable(
       Capture<List<DBAccessor.DBColumnInfo>> alertTargetStatesCapture) {
     Assert.assertEquals(2, alertTargetStatesCapture.getValue().size());
   }
 
+  /**
+   * Verifies is_global added to alert target table.
+   *
+   * @param alertTargetGlobalCapture
+   */
+  private void verifyAlertTargetGlobal(
+      Capture<DBAccessor.DBColumnInfo> alertTargetGlobalCapture) {
+    DBColumnInfo column = alertTargetGlobalCapture.getValue();
+    Assert.assertEquals(0, column.getDefaultValue());
+    Assert.assertEquals(Short.class, column.getType());
+    Assert.assertEquals("is_global", column.getName());
+  }
+
   /**
    * Verifies new security_state column in servicedesiredsstate table.
    *