Explorar o código

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

Jonathan Hurley %!s(int64=10) %!d(string=hai) anos
pai
achega
2518a56a3c
Modificáronse 20 ficheiros con 477 adicións e 160 borrados
  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.
    *