Browse Source

AMBARI-6528. Add AlertDefinition endpoint and resource provider (ncole)

Nate Cole 11 năm trước cách đây
mục cha
commit
39a92eb492
35 tập tin đã thay đổi với 1594 bổ sung36 xóa
  1. 44 0
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertDefResourceDefinition.java
  2. 1 0
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
  3. 4 0
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
  4. 93 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertDefinitionService.java
  5. 43 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
  6. 9 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
  7. 11 1
      ambari-server/src/main/java/org/apache/ambari/server/api/util/StackExtensionHelper.java
  8. 3 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
  9. 2 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
  10. 178 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProvider.java
  11. 5 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
  12. 24 3
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDefinitionDAO.java
  13. 229 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
  14. 138 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertsDAO.java
  15. 27 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java
  16. 66 12
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java
  17. 38 8
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java
  18. 34 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java
  19. 29 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertNoticeEntity.java
  20. 34 4
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertTargetEntity.java
  21. 17 1
      ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
  22. 117 0
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java
  23. 57 0
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricAlert.java
  24. 33 0
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/Scope.java
  25. 40 0
      ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java
  26. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  27. 1 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
  28. 4 0
      ambari-server/src/main/resources/key_properties.json
  29. 11 0
      ambari-server/src/main/resources/properties.json
  30. 59 0
      ambari-server/src/main/resources/stacks/HDP/2.0.6/services/HDFS/alerts.json
  31. 2 1
      ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java
  32. 22 0
      ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
  33. 162 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java
  34. 4 2
      ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDefinitionDAOTest.java
  35. 52 0
      ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json

+ 44 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertDefResourceDefinition.java

@@ -0,0 +1,44 @@
+/**
+ * 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.api.resources;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Resource Definition for AlertDefinition types.
+ * @author ncole
+ *
+ */
+public class AlertDefResourceDefinition extends BaseResourceDefinition {
+
+  public AlertDefResourceDefinition() {
+    super(Resource.Type.AlertDefinition);
+  }
+  
+  @Override
+  public String getPluralName() {
+    // TODO Auto-generated method stub
+    return "alert_definitions";
+  }
+  
+  @Override
+  public String getSingularName() {
+    return "alert_definition";
+  }
+  
+}

+ 1 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java

@@ -65,6 +65,7 @@ public class ClusterResourceDefinition extends BaseResourceDefinition {
     setChildren.add(new SubResourceDefinition(Resource.Type.Request));
     setChildren.add(new SubResourceDefinition(Resource.Type.Workflow));
     setChildren.add(new SubResourceDefinition(Resource.Type.ConfigGroup));
+    setChildren.add(new SubResourceDefinition(Resource.Type.AlertDefinition));
 
     return setChildren;
   }

+ 4 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java

@@ -233,6 +233,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
       case Permission:
         resourceDefinition = new PermissionResourceDefinition();
         break;
+        
+      case AlertDefinition:
+        resourceDefinition = new AlertDefResourceDefinition();
+        break;
 
       default:
         throw new IllegalArgumentException("Unsupported resource type: " + type);

+ 93 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertDefinitionService.java

@@ -0,0 +1,93 @@
+/**
+ * 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.api.services;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Endpoint for alert definitions.
+ */
+public class AlertDefinitionService extends BaseService {
+
+  private String m_clusterName = null;
+  
+  AlertDefinitionService(String clusterName) {
+    m_clusterName = clusterName;
+  }
+  
+  @GET
+  @Produces("text/plain")
+  public Response getDefinitions(String body,
+      @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+      createResourceInstance(m_clusterName, null));
+  }
+  
+  @POST
+  @Produces("text/plain")
+  public Response createDefinition(String body,
+      @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST,
+      createResourceInstance(m_clusterName, null));
+  }
+  
+  
+  @GET
+  @Path("{alertDefinitionId}")
+  @Produces("text/plain")
+  public Response getDefinitions(String body,
+      @Context HttpHeaders headers,
+      @Context UriInfo ui,
+      @PathParam("alertDefinitionId") Long id) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+      createResourceInstance(m_clusterName, id));
+  }
+  
+  
+  /**
+   * Create a request schedule resource instance
+   * @param clusterName
+   * @param requestScheduleId
+   * @return
+   */
+  private ResourceInstance createResourceInstance(String clusterName,
+      Long definitionId) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Cluster, clusterName);
+    mapIds.put(Resource.Type.AlertDefinition, null == definitionId ? null : definitionId.toString());
+
+    return createResource(Resource.Type.AlertDefinition, mapIds);
+  }
+  
+}

+ 43 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java

@@ -31,6 +31,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Scanner;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -58,6 +59,7 @@ import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.Stack;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.alert.AlertDefinition;
 import org.apache.ambari.server.state.stack.LatestRepoCallable;
 import org.apache.ambari.server.state.stack.MetricDefinition;
 import org.apache.ambari.server.state.stack.RepositoryXml;
@@ -86,6 +88,7 @@ public class AmbariMetaInfo {
   public static final String SERVICE_CONFIG_FILE_NAME_POSTFIX = ".xml";
   public static final String RCO_FILE_NAME = "role_command_order.json";
   public static final String SERVICE_METRIC_FILE_NAME = "metrics.json";
+  public static final String SERVICE_ALERT_FILE_NAME = "alerts.json";
   /**
    * This string is used in placeholder in places that are common for
    * all operating systems or in situations where os type is not important.
@@ -1049,5 +1052,45 @@ public class AmbariMetaInfo {
     }
     return requiredProperties;
   }
+  
+  public Set<AlertDefinition> getAlertDefinitions(String stackName, String stackVersion,
+      String serviceName) throws AmbariException {
+    
+    ServiceInfo svc = getService(stackName, stackVersion, serviceName);
+
+    if (null == svc.getAlertsFile() || !svc.getAlertsFile().exists()) {
+      LOG.debug("Alerts file for " + stackName + "/" + stackVersion + "/" + serviceName + " not found.");
+      return null;
+    }
+    
+    
+    Map<String, List<AlertDefinition>> map = null;
+    
+    Type type = new TypeToken<Map<String, List<AlertDefinition>>>(){}.getType();
+    
+    Gson gson = new Gson();
+
+    try {
+      map = gson.fromJson(new FileReader(svc.getAlertsFile()), type);
+
+    } catch (Exception e) {
+      LOG.error ("Could not read the alert definition file", e);
+      throw new AmbariException("Could not read alert definition file", e);
+    }
+
+    Set<AlertDefinition> defs = new HashSet<AlertDefinition>();
+    
+    for (Entry<String, List<AlertDefinition>> entry : map.entrySet()) {
+      for (AlertDefinition ad : entry.getValue()) {
+        ad.setServiceName(serviceName);
+        if (!entry.getKey().equals("service")) {
+          ad.setComponentName(entry.getKey());
+        }
+      }
+      defs.addAll(entry.getValue());
+    }
+    
+    return defs;
+  }
 
 }

+ 9 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java

@@ -207,6 +207,15 @@ public class ClusterService extends BaseService {
                              (@PathParam ("clusterName") String clusterName) {
     return new RequestScheduleService(clusterName);
   }
+  
+  /**
+   * Gets the alert definition service
+   */
+  @Path("{clusterName}/alert_definitions")
+  public AlertDefinitionService getAlertDefinitionService(
+      @PathParam("clusterName") String clusterName) {
+    return new AlertDefinitionService(clusterName);
+  }
 
   /**
    * Create a cluster resource instance.

+ 11 - 1
ambari-server/src/main/java/org/apache/ambari/server/api/util/StackExtensionHelper.java

@@ -185,6 +185,10 @@ public class StackExtensionHelper {
     // metrics
     if (null == childService.getMetricsFile() && null != parentService.getMetricsFile())
       mergedServiceInfo.setMetricsFile(parentService.getMetricsFile());
+    
+    // alerts
+    if (null == childService.getAlertsFile() && null != parentService.getAlertsFile())
+      mergedServiceInfo.setAlertsFile(parentService.getAlertsFile());    
 
     populateComponents(mergedServiceInfo, parentService, childService);
 
@@ -407,6 +411,9 @@ public class StackExtensionHelper {
           // get metrics file, if it exists
           File metricsJson = new File(serviceFolder.getAbsolutePath()
             + File.separator + AmbariMetaInfo.SERVICE_METRIC_FILE_NAME);
+          
+          File alertsJson = new File(serviceFolder.getAbsolutePath() +
+              File.separator + AmbariMetaInfo.SERVICE_ALERT_FILE_NAME);
 
           //Reading v2 service metainfo (may contain multiple services)
           // Get services from metadata
@@ -426,7 +433,10 @@ public class StackExtensionHelper {
             // process metrics.json
             if (metricsJson.exists())
               serviceInfo.setMetricsFile(metricsJson);
-
+            
+            if (alertsJson.exists())
+              serviceInfo.setAlertsFile(alertsJson);
+            
             // Get all properties from all "configs/*-site.xml" files
             setPropertiesFromConfigs(serviceFolder, serviceInfo);
 

+ 3 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java

@@ -44,6 +44,7 @@ import org.apache.ambari.server.bootstrap.BootStrapImpl;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider;
+import org.apache.ambari.server.controller.internal.AlertDefinitionResourceProvider;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
 import org.apache.ambari.server.controller.internal.StackDefinedPropertyProvider;
@@ -51,6 +52,7 @@ import org.apache.ambari.server.controller.internal.StackDependencyResourceProvi
 import org.apache.ambari.server.controller.nagios.NagiosPropertyProvider;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.PersistenceType;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
 import org.apache.ambari.server.orm.dao.BlueprintDAO;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.dao.ViewDAO;
@@ -527,6 +529,7 @@ public class AmbariServer {
     StackDependencyResourceProvider.init(ambariMetaInfo);
     ClusterResourceProvider.init(injector.getInstance(BlueprintDAO.class), ambariMetaInfo);
     ViewRegistry.init(injector.getInstance(ViewDAO.class), injector.getInstance(ViewInstanceDAO.class));
+    AlertDefinitionResourceProvider.init(injector.getInstance(AlertDefinitionDAO.class));
   }
   
   /**

+ 2 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java

@@ -144,6 +144,8 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
         return new HostComponentProcessResourceProvider(propertyIds, keyPropertyIds, managementController);
       case Blueprint:
         return new BlueprintResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case AlertDefinition:
+        return new AlertDefinitionResourceProvider(propertyIds, keyPropertyIds, managementController);
       default:
         throw new IllegalArgumentException("Unknown type " + type);
     }

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

@@ -0,0 +1,178 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.alert.MetricAlert;
+
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+
+/**
+ * ResourceProvider for Alert Definitions
+ */
+public class AlertDefinitionResourceProvider extends AbstractControllerResourceProvider {
+
+  protected static final String ALERT_DEF_CLUSTER_NAME = "AlertDefinition/cluster_name";
+  protected static final String ALERT_DEF_ID = "AlertDefinition/id";
+  protected static final String ALERT_DEF_NAME = "AlertDefinition/name";
+  protected static final String ALERT_DEF_INTERVAL = "AlertDefinition/interval";
+  protected static final String ALERT_DEF_SOURCE_TYPE = "AlertDefinition/source";
+  protected static final String ALERT_DEF_SERVICE_NAME = "AlertDefinition/service_name";
+  protected static final String ALERT_DEF_COMPONENT_NAME = "AlertDefinition/component_name";
+  protected static final String ALERT_DEF_ENABLED = "AlertDefinition/enabled";
+  protected static final String ALERT_DEF_SCOPE = "AlertDefinition/scope";
+  
+  private static Set<String> pkPropertyIds = new HashSet<String>(
+      Arrays.asList(ALERT_DEF_ID, ALERT_DEF_NAME));
+  private static AlertDefinitionDAO alertDefinitionDAO = null;
+  
+  /**
+   * @param instance
+   */
+  @Inject
+  public static void init(AlertDefinitionDAO instance) {
+    alertDefinitionDAO = instance;
+  }
+  
+  AlertDefinitionResourceProvider(Set<String> propertyIds,
+      Map<Resource.Type, String> keyPropertyIds,
+      AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+  
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+  @Override
+  public RequestStatus createResources(Request request) throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+    
+    Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+    
+    Set<Resource> results = new HashSet<Resource>();
+    
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      String clusterName = (String) propertyMap.get(ALERT_DEF_CLUSTER_NAME);
+      
+      if (null == clusterName || clusterName.isEmpty())
+        throw new IllegalArgumentException("Invalid argument, cluster name is required");
+      
+      String id = (String) propertyMap.get(ALERT_DEF_ID);
+      if (null != id) {
+        AlertDefinitionEntity entity = alertDefinitionDAO.findById(Long.parseLong(id));
+        if (null != entity) {
+          results.add(toResource(false, clusterName, entity, requestPropertyIds));
+        }
+      } else {
+        
+        Cluster cluster = null;
+        try {
+          cluster = getManagementController().getClusters().getCluster(clusterName);
+        } catch (AmbariException e) {
+          throw new NoSuchResourceException("Parent Cluster resource doesn't exist", e);
+        }
+        
+        List<AlertDefinitionEntity> entities = alertDefinitionDAO.findAll(
+            cluster.getClusterId());
+
+        for (AlertDefinitionEntity entity : entities) {
+          results.add(toResource(true, clusterName, entity, requestPropertyIds));
+        }
+      }
+    }
+    
+    return results;
+  }
+
+  @Override
+  public RequestStatus updateResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+    throw new UnsupportedOperationException("Not currently supported.");
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+    throw new UnsupportedOperationException("Not currently supported.");
+  }
+
+  
+  private Resource toResource(boolean isCollection, String clusterName,
+      AlertDefinitionEntity entity, Set<String> requestedIds) {
+    Resource resource = new ResourceImpl(Resource.Type.AlertDefinition);
+    
+    setResourceProperty(resource, ALERT_DEF_CLUSTER_NAME, clusterName, requestedIds);
+    setResourceProperty(resource, ALERT_DEF_ID, entity.getDefinitionId(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_NAME, entity.getDefinitionName(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_INTERVAL, entity.getScheduleInterval(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_SOURCE_TYPE, entity.getSourceType(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_SERVICE_NAME, entity.getServiceName(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_COMPONENT_NAME, entity.getComponentName(), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_ENABLED, Boolean.valueOf(entity.getEnabled()), requestedIds);
+    setResourceProperty(resource, ALERT_DEF_SCOPE, entity.getScope(), requestedIds);
+    
+    if (!isCollection && null != resource.getPropertyValue(ALERT_DEF_SOURCE_TYPE)) {
+      Gson gson = new Gson();
+      
+      if (entity.getSourceType().equals("metric")) {
+        try {
+          MetricAlert ma = gson.fromJson(entity.getSource(), MetricAlert.class);
+          resource.setProperty("AlertDefinition/metric", ma);
+        } catch (Exception e) {
+          LOG.error("Could not coerce alert source into a type");
+        }
+      }
+    }
+    
+    return resource;
+  }
+  
+}

+ 5 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java

@@ -22,6 +22,8 @@ package org.apache.ambari.server.controller.spi;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.apache.ambari.server.controller.spi.Resource.Type;
+
 /**
  * The resource object represents a requested resource.  The resource
  * contains a collection of values for the requested properties.
@@ -110,7 +112,8 @@ public interface Resource {
     ViewInstance,
     Blueprint,
     HostComponentProcess,
-    Permission;
+    Permission,
+    AlertDefinition;
 
     /**
      * Get the {@link Type} that corresponds to this InternalType.
@@ -182,6 +185,7 @@ public interface Resource {
     public static final Type Blueprint = InternalType.Blueprint.getType();
     public static final Type HostComponentProcess = InternalType.HostComponentProcess.getType();
     public static final Type Permission = InternalType.Permission.getType();
+    public static final Type AlertDefinition = InternalType.AlertDefinition.getType();
 
     /**
      * The type name.

+ 24 - 3
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDefinitionDAO.java

@@ -62,17 +62,21 @@ public class AlertDefinitionDAO {
   }
 
   /**
-   * Gets an alert definition with the specified name.
+   * Gets an alert definition with the specified name. Alert definition names
+   * are unique within a cluster.
    * 
+   * @param clusterId
+   *          the ID of the cluster.
    * @param definitionName
    *          the name of the definition (not {@code null}).
    * @return the alert definition or {@code null} if none exists.
    */
   @RequiresSession
-  public AlertDefinitionEntity findByName(String definitionName) {
+  public AlertDefinitionEntity findByName(long clusterId, String definitionName) {
     TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
         "AlertDefinitionEntity.findByName", AlertDefinitionEntity.class);
 
+    query.setParameter("clusterId", clusterId);
     query.setParameter("definitionName", definitionName);
 
     return daoUtils.selectSingle(query);
@@ -81,7 +85,8 @@ public class AlertDefinitionDAO {
   /**
    * Gets all alert definitions stored in the database.
    * 
-   * @return all alert definitions or {@code null} if none exist.
+   * @return all alert definitions or an empty list if none exist (never
+   *         {@code null}).
    */
   @RequiresSession
   public List<AlertDefinitionEntity> findAll() {
@@ -91,6 +96,22 @@ public class AlertDefinitionDAO {
     return daoUtils.selectList(query);
   }
 
+  /**
+   * Gets all alert definitions stored in the database.
+   * 
+   * @return all alert definitions or empty list if none exist (never
+   *         {@code null}).
+   */
+  @RequiresSession
+  public List<AlertDefinitionEntity> findAll(long clusterId) {
+    TypedQuery<AlertDefinitionEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertDefinitionEntity.findAllInCluster", AlertDefinitionEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+
+    return daoUtils.selectList(query);
+  }
+
   /**
    * Persists a new alert definition.
    * 

+ 229 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java

@@ -17,14 +17,23 @@
  */
 package org.apache.ambari.server.orm.dao;
 
+import java.util.List;
+
 import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.AlertGroupEntity;
+import org.apache.ambari.server.orm.entities.AlertTargetEntity;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
 
 /**
- * 
+ * The {@link AlertDispatchDAO} class manages the {@link AlertTargetEntity},
+ * {@link AlertGroupEntity}, and the associations between them.
  */
 @Singleton
 public class AlertDispatchDAO {
@@ -33,4 +42,223 @@ public class AlertDispatchDAO {
    */
   @Inject
   Provider<EntityManager> entityManagerProvider;
+
+  /**
+   * DAO utilities for dealing mostly with {@link TypedQuery} results.
+   */
+  @Inject
+  DaoUtils daoUtils;
+
+  /**
+   * Gets an alert group with the specified ID.
+   * 
+   * @param groupId
+   *          the ID of the group to retrieve.
+   * @return the group or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertGroupEntity findGroupById(long groupId) {
+    return entityManagerProvider.get().find(AlertGroupEntity.class, groupId);
+  }
+
+  /**
+   * Gets an alert target with the specified ID.
+   * 
+   * @param targetId
+   *          the ID of the target to retrieve.
+   * @return the target or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertTargetEntity findTargetById(long targetId) {
+    return entityManagerProvider.get().find(AlertTargetEntity.class, targetId);
+  }
+
+  /**
+   * Gets an alert group with the specified name across all clusters. Alert
+   * group names are unique within a cluster.
+   * 
+   * @param groupName
+   *          the name of the group (not {@code null}).
+   * @return the alert group or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertGroupEntity findGroupByName(String groupName) {
+    TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroup.findByName", AlertGroupEntity.class);
+
+    query.setParameter("groupName", groupName);
+
+    return daoUtils.selectSingle(query);
+  }
+
+  /**
+   * Gets an alert group with the specified name for the given cluster. Alert
+   * group names are unique within a cluster.
+   * 
+   * @param clusterId
+   *          the ID of the cluster.
+   * @param groupName
+   *          the name of the group (not {@code null}).
+   * @return the alert group or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertGroupEntity findGroupByName(long clusterId, String groupName) {
+    TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroup.findByNameInCluster", AlertGroupEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("groupName", groupName);
+
+    return daoUtils.selectSingle(query);
+  }
+
+  /**
+   * Gets an alert target with the specified name. Alert target names are unique
+   * across all clusters.
+   * 
+   * @param targetName
+   *          the name of the target (not {@code null}).
+   * @return the alert target or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertTargetEntity findTargetByName(String targetName) {
+    TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroup.findByName", AlertTargetEntity.class);
+
+    query.setParameter("targetName", targetName);
+
+    return daoUtils.selectSingle(query);
+  }
+
+  /**
+   * Gets all alert groups stored in the database across all clusters.
+   * 
+   * @return all alert groups or empty list if none exist (never {@code null}).
+   */
+  @RequiresSession
+  public List<AlertGroupEntity> findAllGroups() {
+    TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroupEntity.findAll", AlertGroupEntity.class);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets all alert groups stored in the database for the specified cluster.
+   * 
+   * @return all alert groups in the specified cluster or empty list if none
+   *         exist (never {@code null}).
+   */
+  @RequiresSession
+  public List<AlertGroupEntity> findAllGroups(long clusterId) {
+    TypedQuery<AlertGroupEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertGroupEntity.findAllInCluster", AlertGroupEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets all alert targets stored in the database.
+   * 
+   * @return all alert targets or empty list if none exist (never {@code null}).
+   */
+  @RequiresSession
+  public List<AlertTargetEntity> findAllTargets() {
+    TypedQuery<AlertTargetEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertTargetEntity.findAll", AlertTargetEntity.class);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Persists a new alert group.
+   * 
+   * @param alertGroup
+   *          the group to persist (not {@code null}).
+   */
+  @Transactional
+  public void create(AlertGroupEntity alertGroup) {
+    entityManagerProvider.get().persist(alertGroup);
+  }
+
+  /**
+   * Refresh the state of the alert group from the database.
+   * 
+   * @param alertGroup
+   *          the group to refresh (not {@code null}).
+   */
+  @Transactional
+  public void refresh(AlertGroupEntity alertGroup) {
+    entityManagerProvider.get().refresh(alertGroup);
+  }
+
+  /**
+   * Merge the speicified alert group with the existing group in the database.
+   * 
+   * @param alertGroup
+   *          the group to merge (not {@code null}).
+   * @return the updated group with merged content (never {@code null}).
+   */
+  @Transactional
+  public AlertGroupEntity merge(AlertGroupEntity alertGroup) {
+    return entityManagerProvider.get().merge(alertGroup);
+  }
+
+  /**
+   * Removes the specified alert group from the database.
+   * 
+   * @param alertGroup
+   *          the group to remove.
+   */
+  @Transactional
+  public void remove(AlertGroupEntity alertGroup) {
+    entityManagerProvider.get().remove(merge(alertGroup));
+  }
+
+  /**
+   * Persists a new alert target.
+   * 
+   * @param alertTarget
+   *          the target to persist (not {@code null}).
+   */
+  @Transactional
+  public void create(AlertTargetEntity alertTarget) {
+    entityManagerProvider.get().persist(alertTarget);
+  }
+
+  /**
+   * Refresh the state of the alert target from the database.
+   * 
+   * @param alertTarget
+   *          the target to refresh (not {@code null}).
+   */
+  @Transactional
+  public void refresh(AlertTargetEntity alertTarget) {
+    entityManagerProvider.get().refresh(alertTarget);
+  }
+
+  /**
+   * Merge the speicified alert target with the existing target in the database.
+   * 
+   * @param alertTarget
+   *          the target to merge (not {@code null}).
+   * @return the updated target with merged content (never {@code null}).
+   */
+  @Transactional
+  public AlertTargetEntity merge(AlertTargetEntity alertTarget) {
+    return entityManagerProvider.get().merge(alertTarget);
+  }
+
+  /**
+   * Removes the specified alert target from the database.
+   * 
+   * @param alertTarget
+   *          the target to remove.
+   */
+  @Transactional
+  public void remove(AlertTargetEntity alertTarget) {
+    entityManagerProvider.get().remove(merge(alertTarget));
+  }
 }

+ 138 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertsDAO.java

@@ -0,0 +1,138 @@
+/**
+ * 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.orm.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
+import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+
+/**
+ * The {@link AlertsDAO} class manages the {@link AlertHistoryEntity} and
+ * {@link AlertCurrentEntity} instances.Each {@link AlertHistoryEntity} is known
+ * as an "alert" that has been triggered and received.
+ */
+@Singleton
+public class AlertsDAO {
+  /**
+   * JPA entity manager
+   */
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
+
+  /**
+   * DAO utilities for dealing mostly with {@link TypedQuery} results.
+   */
+  @Inject
+  DaoUtils daoUtils;
+
+  /**
+   * Gets an alert with the specified ID.
+   * 
+   * @param alertId
+   *          the ID of the alert to retrieve.
+   * @return the alert or {@code null} if none exists.
+   */
+  @RequiresSession
+  public AlertHistoryEntity findById(long alertId) {
+    return entityManagerProvider.get().find(AlertHistoryEntity.class, alertId);
+  }
+
+  /**
+   * Gets all alerts stored in the database across all clusters.
+   * 
+   * @return all alerts or empty list if none exist (never {@code null}).
+   */
+  @RequiresSession
+  public List<AlertHistoryEntity> findAll() {
+    TypedQuery<AlertHistoryEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertHistoryEntity.findAll", AlertHistoryEntity.class);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Gets all alerts stored in the database for the given cluster.
+   * 
+   * @return all alerts in the specified cluster or empty list if none exist
+   *         (never {@code null}).
+   */
+  @RequiresSession
+  public List<AlertHistoryEntity> findAll(long clusterId) {
+    TypedQuery<AlertHistoryEntity> query = entityManagerProvider.get().createNamedQuery(
+        "AlertHistoryEntity.findAllInCluster", AlertHistoryEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+
+    return daoUtils.selectList(query);
+  }
+
+  /**
+   * Persists a new alert.
+   * 
+   * @param alert
+   *          the alert to persist (not {@code null}).
+   */
+  @Transactional
+  public void create(AlertHistoryEntity alert) {
+    entityManagerProvider.get().persist(alert);
+  }
+
+  /**
+   * Refresh the state of the alert from the database.
+   * 
+   * @param alert
+   *          the alert to refresh (not {@code null}).
+   */
+  @Transactional
+  public void refresh(AlertHistoryEntity alert) {
+    entityManagerProvider.get().refresh(alert);
+  }
+
+  /**
+   * Merge the speicified alert with the existing alert in the database.
+   * 
+   * @param alert
+   *          the alert to merge (not {@code null}).
+   * @return the updated alert with merged content (never {@code null}).
+   */
+  @Transactional
+  public AlertHistoryEntity merge(AlertHistoryEntity alert) {
+    return entityManagerProvider.get().merge(alert);
+  }
+
+  /**
+   * Removes the specified alert from the database.
+   * 
+   * @param alert
+   *          the alert to remove.
+   */
+  @Transactional
+  public void remove(AlertHistoryEntity alert) {
+    entityManagerProvider.get().remove(merge(alert));
+  }
+}

+ 27 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java

@@ -175,4 +175,31 @@ public class AlertCurrentEntity {
     this.alertHistory = alertHistory;
   }
 
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertCurrentEntity that = (AlertCurrentEntity) object;
+
+    if (alertId != null ? !alertId.equals(that.alertId) : that.alertId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != alertId ? alertId.hashCode() : 0;
+    return result;
+  }
 }

+ 66 - 12
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java

@@ -17,7 +17,7 @@
  */
 package org.apache.ambari.server.orm.entities;
 
-import java.util.List;
+import java.util.Set;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -40,7 +40,8 @@ import javax.persistence.UniqueConstraint;
     "cluster_id", "definition_name" }))
 @NamedQueries({
     @NamedQuery(name = "AlertDefinitionEntity.findAll", query = "SELECT alertDefinition FROM AlertDefinitionEntity alertDefinition"),
-    @NamedQuery(name = "AlertDefinitionEntity.findByName", query = "SELECT alertDefinition FROM AlertDefinitionEntity alertDefinition WHERE alertDefinition.definitionName = :definitionName"), })
+    @NamedQuery(name = "AlertDefinitionEntity.findAllInCluster", query = "SELECT alertDefinition FROM AlertDefinitionEntity alertDefinition WHERE alertDefinition.clusterId = :clusterId"),
+    @NamedQuery(name = "AlertDefinitionEntity.findByName", query = "SELECT alertDefinition FROM AlertDefinitionEntity alertDefinition WHERE alertDefinition.definitionName = :definitionName AND alertDefinition.clusterId = :clusterId"), })
 public class AlertDefinitionEntity {
 
   @Id
@@ -49,7 +50,7 @@ public class AlertDefinitionEntity {
   private Long definitionId;
 
   @Column(name = "alert_source", nullable = false, length = 2147483647)
-  private String alertSource;
+  private String source;
 
   @Column(name = "cluster_id", nullable = false)
   private Long clusterId;
@@ -59,6 +60,9 @@ public class AlertDefinitionEntity {
 
   @Column(name = "definition_name", nullable = false, length = 255)
   private String definitionName;
+  
+  @Column(name = "scope", length = 255)
+  private String scope;
 
   @Column(nullable = false)
   private Integer enabled = Integer.valueOf(1);
@@ -79,7 +83,7 @@ public class AlertDefinitionEntity {
    * Bi-directional many-to-many association to {@link AlertGroupEntity}
    */
   @ManyToMany(mappedBy = "alertDefinitions")
-  private List<AlertGroupEntity> alertGroups;
+  private Set<AlertGroupEntity> alertGroups;
 
   /**
    * Constructor.
@@ -113,8 +117,8 @@ public class AlertDefinitionEntity {
    * 
    * @return the alert source (never {@code null}).
    */
-  public String getAlertSource() {
-    return alertSource;
+  public String getSource() {
+    return source;
   }
 
   /**
@@ -124,8 +128,8 @@ public class AlertDefinitionEntity {
    * @param alertSource
    *          the alert source (not {@code null}).
    */
-  public void setAlertSource(String alertSource) {
-    this.alertSource = alertSource;
+  public void setSource(String alertSource) {
+    this.source = alertSource;
   }
 
   /**
@@ -172,6 +176,27 @@ public class AlertDefinitionEntity {
     this.componentName = componentName;
   }
 
+  /**
+   * Gets the scope of the alert definition. The scope is defined as either
+   * being for a SERVICE or a HOST.
+   * 
+   * @return the scope, or {@code null} if not defined.
+   */
+  public String getScope() {
+    return scope;
+  }
+
+  /**
+   * Sets the scope of the alert definition. The scope is defined as either
+   * being for a SERVICE or a HOST.
+   * 
+   * @param scope
+   *          the scope to set, or {@code null} for none.
+   */
+  public void setScope(String scope) {
+    this.scope = scope;
+  }
+
   /**
    * Gets the name of this alert definition. Alert definition names are unique
    * within a cluster.
@@ -201,8 +226,8 @@ public class AlertDefinitionEntity {
    * @return {@code true} if this alert definition is enabled, {@code false}
    *         otherwise.
    */
-  public Integer getEnabled() {
-    return enabled;
+  public boolean getEnabled() {
+    return enabled == 0 ? false : true;
   }
 
   /**
@@ -298,7 +323,7 @@ public class AlertDefinitionEntity {
    * 
    * @return the groups, or {@code null} if none.
    */
-  public List<AlertGroupEntity> getAlertGroups() {
+  public Set<AlertGroupEntity> getAlertGroups() {
     return alertGroups;
   }
 
@@ -308,7 +333,36 @@ public class AlertDefinitionEntity {
    * @param alertGroups
    *          the groups, or {@code null} for none.
    */
-  public void setAlertGroups(List<AlertGroupEntity> alertGroups) {
+  public void setAlertGroups(Set<AlertGroupEntity> alertGroups) {
     this.alertGroups = alertGroups;
   }
+
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertDefinitionEntity that = (AlertDefinitionEntity) object;
+
+    if (definitionId != null ? !definitionId.equals(that.definitionId)
+        : that.definitionId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != definitionId ? definitionId.hashCode() : 0;
+    return result;
+  }
 }

+ 38 - 8
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertGroupEntity.java

@@ -17,7 +17,7 @@
  */
 package org.apache.ambari.server.orm.entities;
 
-import java.util.List;
+import java.util.Set;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -42,7 +42,9 @@ import javax.persistence.UniqueConstraint;
     "cluster_id", "group_name" }))
 @NamedQueries({
     @NamedQuery(name = "AlertGroupEntity.findAll", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup"),
-    @NamedQuery(name = "AlertGroupEntity.findByName", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName"), })
+    @NamedQuery(name = "AlertGroupEntity.findAllInCluster", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.clusterId = :clusterId"),
+    @NamedQuery(name = "AlertGroupEntity.findByName", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName"),
+    @NamedQuery(name = "AlertGroupEntity.findByNameInCluster", query = "SELECT alertGroup FROM AlertGroupEntity alertGroup WHERE alertGroup.groupName = :groupName AND alertGroup.clusterId = :clusterId"), })
 public class AlertGroupEntity {
 
   @Id
@@ -64,13 +66,13 @@ public class AlertGroupEntity {
    */
   @ManyToMany
   @JoinTable(name = "alert_grouping", joinColumns = { @JoinColumn(name = "group_id", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "definition_id", nullable = false) })
-  private List<AlertDefinitionEntity> alertDefinitions;
+  private Set<AlertDefinitionEntity> alertDefinitions;
 
   /**
    * Bi-directional many-to-many association to {@link AlertTargetEntity}
    */
   @ManyToMany(mappedBy = "alertGroups")
-  private List<AlertTargetEntity> alertTargets;
+  private Set<AlertTargetEntity> alertTargets;
 
   /**
    * Constructor.
@@ -164,7 +166,7 @@ public class AlertGroupEntity {
    * 
    * @return the alert definitions or {@code null} if none.
    */
-  public List<AlertDefinitionEntity> getAlertDefinitions() {
+  public Set<AlertDefinitionEntity> getAlertDefinitions() {
     return alertDefinitions;
   }
 
@@ -174,7 +176,7 @@ public class AlertGroupEntity {
    * @param alertDefinitions
    *          the definitions, or {@code null} for none.
    */
-  public void setAlertDefinitions(List<AlertDefinitionEntity> alertDefinitions) {
+  public void setAlertDefinitions(Set<AlertDefinitionEntity> alertDefinitions) {
     this.alertDefinitions = alertDefinitions;
   }
 
@@ -184,7 +186,7 @@ public class AlertGroupEntity {
    * 
    * @return the targets, or {@code null} if there are none.
    */
-  public List<AlertTargetEntity> getAlertTargets() {
+  public Set<AlertTargetEntity> getAlertTargets() {
     return alertTargets;
   }
 
@@ -195,7 +197,35 @@ public class AlertGroupEntity {
    * @param alertTargets
    *          the targets, or {@code null} if there are none.
    */
-  public void setAlertTargets(List<AlertTargetEntity> alertTargets) {
+  public void setAlertTargets(Set<AlertTargetEntity> alertTargets) {
     this.alertTargets = alertTargets;
   }
+
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertGroupEntity that = (AlertGroupEntity) object;
+
+    if (groupId != null ? !groupId.equals(that.groupId) : that.groupId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != groupId ? groupId.hashCode() : 0;
+    return result;
+  }
 }

+ 34 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java

@@ -26,6 +26,7 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.OneToOne;
 import javax.persistence.Table;
@@ -41,7 +42,9 @@ import org.apache.ambari.server.state.AlertState;
  */
 @Entity
 @Table(name = "alert_history")
-@NamedQuery(name = "AlertHistoryEntity.findAll", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory")
+@NamedQueries({
+    @NamedQuery(name = "AlertHistoryEntity.findAll", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory"),
+    @NamedQuery(name = "AlertHistoryEntity.findAllInCluster", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId") })
 public class AlertHistoryEntity {
 
   @Id
@@ -80,7 +83,7 @@ public class AlertHistoryEntity {
   /**
    * Bi-directional one-to-one association to {@link AlertCurrentEntity}.
    */
-  @OneToOne(mappedBy = "alertHistory")
+  @OneToOne(mappedBy = "alertHistory", orphanRemoval = true)
   private AlertCurrentEntity alertCurrent;
 
   /**
@@ -341,4 +344,33 @@ public class AlertHistoryEntity {
   public void setAlertDefinition(AlertDefinitionEntity alertDefinition) {
     this.alertDefinition = alertDefinition;
   }
+
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertHistoryEntity that = (AlertHistoryEntity) object;
+
+    if (alertId != null ? !alertId.equals(that.alertId) : that.alertId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != alertId ? alertId.hashCode() : 0;
+    return result;
+  }
+
 }

+ 29 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertNoticeEntity.java

@@ -152,4 +152,33 @@ public class AlertNoticeEntity {
     this.alertTarget = alertTarget;
   }
 
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertNoticeEntity that = (AlertNoticeEntity) object;
+
+    if (notificationId != null ? !notificationId.equals(that.notificationId)
+        : that.notificationId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != notificationId ? notificationId.hashCode() : 0;
+    return result;
+  }
+
 }

+ 34 - 4
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertTargetEntity.java

@@ -17,7 +17,7 @@
  */
 package org.apache.ambari.server.orm.entities;
 
-import java.util.List;
+import java.util.Set;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -64,7 +64,7 @@ public class AlertTargetEntity {
    */
   @ManyToMany
   @JoinTable(name = "alert_group_target", joinColumns = { @JoinColumn(name = "target_id", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "group_id", nullable = false) })
-  private List<AlertGroupEntity> alertGroups;
+  private Set<AlertGroupEntity> alertGroups;
 
   /**
    * Constructor.
@@ -163,7 +163,7 @@ public class AlertTargetEntity {
    * @return the groups that will send to this target when an alert in that
    *         group is received, or {@code null} for none.
    */
-  public List<AlertGroupEntity> getAlertGroups() {
+  public Set<AlertGroupEntity> getAlertGroups() {
     return alertGroups;
   }
 
@@ -174,7 +174,37 @@ public class AlertTargetEntity {
    *          the groups that will send to this target when an alert in that
    *          group is received, or {@code null} for none.
    */
-  public void setAlertGroups(List<AlertGroupEntity> alertGroups) {
+  public void setAlertGroups(Set<AlertGroupEntity> alertGroups) {
     this.alertGroups = alertGroups;
   }
+
+  /**
+   *
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (this == object)
+      return true;
+
+    if (object == null || getClass() != object.getClass())
+      return false;
+
+    AlertTargetEntity that = (AlertTargetEntity) object;
+
+    if (targetId != null ? !targetId.equals(that.targetId)
+        : that.targetId != null)
+      return false;
+
+    return true;
+  }
+
+  /**
+   *
+   */
+  @Override
+  public int hashCode() {
+    int result = null != targetId ? targetId.hashCode() : 0;
+    return result;
+  }
+
 }

+ 17 - 1
ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java

@@ -81,7 +81,9 @@ public class ServiceInfo {
   private File metricsFile = null;
   @XmlTransient
   private Map<String, Map<String, List<MetricDefinition>>> metrics = null;
-
+  
+  @XmlTransient
+  private File alertsFile = null;
 
   /**
    * Internal list of os-specific details (loaded from xml). Added at schema ver 2
@@ -405,4 +407,18 @@ public class ServiceInfo {
   public void setMonitoringService(Boolean monitoringService) {
     this.monitoringService = monitoringService;
   }
+
+  /**
+   * @param file the file containing the alert definitions
+   */
+  public void setAlertsFile(File file) {
+    alertsFile = file;
+  }
+  
+  /**
+   * @return the alerts file, or <code>null</code> if none exists
+   */
+  public File getAlertsFile() {
+    return alertsFile;
+  }
 }

+ 117 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertDefinition.java

@@ -0,0 +1,117 @@
+/**
+® * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.alert;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * @author ncole
+ *
+ */
+public class AlertDefinition {
+
+  private String serviceName = null;
+  private String componentName = null;
+  
+  private String name = null;
+  private String label = null;
+  private String scope = null;
+  private String source = null;
+  private int interval = 1;
+  private boolean enabled = true;
+  
+  @SerializedName("metric")
+  private MetricAlert metricAlert = null;
+  
+  public String getServiceName() {
+    return serviceName;
+  }
+  
+  public void setServiceName(String name) {
+    serviceName = name;
+  }
+  
+  public String getComponentName() {
+    return componentName;
+  }
+  
+  public void setComponentName(String name) {
+    componentName = name;
+  }
+  
+  /**
+   * @return the name
+   */
+  public String getName() {
+    return name;
+  }
+  
+  /**
+   * @return the label
+   */
+  public String getLabel() {
+    return label;
+  }
+  
+  /**
+   * @return the scope
+   */
+  public String getScope() {
+    return scope;
+  }
+  
+  /**
+   * @return the source
+   */
+  public String getSource() {
+    return source;
+  }
+  
+  /**
+  * @return the interval
+   */
+  public int getInterval() {
+    return interval;
+  }
+  
+  /**
+   * @return <code>true</code> if enabled
+   */
+  public boolean isEnabled() {
+    return enabled;
+  }
+  
+  @Override
+  public boolean equals(Object obj) {
+    if (null == obj || !obj.getClass().equals(AlertDefinition.class))
+      return false;
+    
+    return name.equals(((AlertDefinition) obj).name);
+  }
+  
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+  
+  @Override
+  public String toString() {
+    return name;
+  }
+  
+}

+ 57 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/alert/MetricAlert.java

@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.alert;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Alert when the source type is defined as {@link SourceType#METRIC}
+ */
+public class MetricAlert {
+  
+  private String host = null;
+  
+  @SerializedName("jmx")
+  private String jmxInfo = null;
+  
+  @SerializedName("ganglia")
+  private String gangliaInfo = null;
+  
+  /**
+   * @return the jmx info, if this metric is jmx-based
+   */
+  public String getJmxInfo() {
+    return jmxInfo;
+  }
+  
+  /**
+   * @return the ganglia info, if this metric is ganglia-based
+   */
+  public String getGangliaInfo() {
+    return gangliaInfo;
+  }
+  
+  /**
+   * @return the host info, which may include port information
+   */
+  public String getHost() {
+    return host;
+  }
+  
+  
+}

+ 33 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/alert/Scope.java

@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.alert;
+
+/**
+ * Assigns a scope to an alert.
+ * 
+ */
+public enum Scope {
+  /**
+   * Definition is scoped to a host only
+   */
+  HOST,
+  /**
+   * Definition is scoped to a service only
+   */
+  SERVICE
+}

+ 40 - 0
ambari-server/src/main/java/org/apache/ambari/server/state/alert/SourceType.java

@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.alert;
+
+/**
+ * Source type refers to how the alert is to be collected. 
+ */
+public enum SourceType {
+  /**
+   * Source is from metric data.
+   */
+  METRIC,
+  /**
+   * Source is generated using of a script
+   */
+  SCRIPT,
+  /**
+   * Source is a simple port check
+   */
+  PORT,
+  /**
+   * Source is an aggregate of a collection of other alert states
+   */
+  AGGREGATE
+}

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

@@ -150,6 +150,7 @@ CREATE TABLE alert_definition (
   definition_name VARCHAR(255) NOT NULL,
   service_name VARCHAR(255) NOT NULL,
   component_name VARCHAR(255),
+  scope VARCHAR(255),
   enabled SMALLINT DEFAULT 1 NOT NULL,
   schedule_interval BIGINT NOT NULL,
   source_type VARCHAR(255) NOT NULL,

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

@@ -207,6 +207,7 @@ CREATE TABLE ambari.alert_definition (
   definition_name VARCHAR(255) NOT NULL,
   service_name VARCHAR(255) NOT NULL,
   component_name VARCHAR(255),
+  scope VARCHAR(255),
   enabled SMALLINT DEFAULT 1 NOT NULL,
   schedule_interval BIGINT NOT NULL,
   source_type VARCHAR(255) NOT NULL,

+ 4 - 0
ambari-server/src/main/resources/key_properties.json

@@ -130,5 +130,9 @@
     "Component": "HostComponentProcess/component_name",
     "HostComponent": "HostComponentProcess/component_name",
     "HostComponentProcess": "HostComponentProcess/name"
+  },
+  "AlertDefinition": {
+    "Cluster": "AlertDefinition/cluster_name",
+    "AlertDefinition": "AlertDefinition/id"
   }
 }

+ 11 - 0
ambari-server/src/main/resources/properties.json

@@ -355,5 +355,16 @@
       "HostComponentProcess/component_name",
       "HostComponentProcess/name",
       "HostComponentProcess/status"
+    ],
+    "AlertDefinition": [
+      "AlertDefinition/cluster_name",
+      "AlertDefinition/service_name",
+      "AlertDefinition/component_name",
+      "AlertDefinition/id",
+      "AlertDefinition/name",
+      "AlertDefinition/interval",
+      "AlertDefinition/source",
+      "AlertDefinition/enabled",
+      "AlertDefinition/scope"      
     ]
 }

+ 59 - 0
ambari-server/src/main/resources/stacks/HDP/2.0.6/services/HDFS/alerts.json

@@ -0,0 +1,59 @@
+{
+  "service": [
+    // datanode space aggregate
+    // datanode process aggregate
+  ],
+  "SECONDARY_NAMENODE": [
+    {
+      "name": "secondary_namenode_process",
+      "label": "Secondary NameNode process",
+      "interval": 1,
+      "scope": "service",
+      "source": "port",
+      "port": {
+        "config": "{{hdfs-site/dfs.namenode.secondary.http-address}}:50071",
+        "default": 50071
+      }
+    }
+  ],
+  "NAMENODE": [
+    // name node cpu utilization (metric)
+    {
+      "name": "namenode_cpu",
+      "label": "NameNode host CPU Utilization",
+      "scope": "host",
+      "source": "metric",
+      "metric": {
+        "jmx_object": "java.lang:type=OperatingSystem",
+        "jmx_attribute": "SystemCpuLoad",
+        "host": "{{hdfs-site/dfs.namenode.secondary.http-address}}"
+      }
+    },
+    // namenode process (port check)
+    {
+      "name": "namenode_process",
+      "label": "NameNode process",
+      "interval": 1,
+      "scope": "host",
+      "source": "port",
+      "port": {
+        "uri": "{{hdfs-site/dfs.namenode.http-address}}:50070"
+       }
+    },
+    {
+      "name": "hdfs_last_checkpoint",
+      "label": "Last Checkpoint Time",
+      "interval": 1,
+      "scope": "service",
+      "source": "script",
+      "enabled": false
+      "script": {
+        "path": "scripts/alerts/last_checkpoint.py"
+      }
+    }
+  ],
+  "DATANODE": [
+    // datanode process (port check)
+    // datanode space
+  ]
+}

+ 2 - 1
ambari-server/src/test/java/org/apache/ambari/server/api/resources/ClusterResourceDefinitionTest.java

@@ -50,13 +50,14 @@ public class ClusterResourceDefinitionTest {
     ResourceDefinition resource = new ClusterResourceDefinition();
     Set<SubResourceDefinition> subResources = resource.getSubResourceDefinitions();
 
-    assertEquals(6, subResources.size());
+    assertEquals(7, subResources.size());
     assertTrue(includesType(subResources, Resource.Type.Service));
     assertTrue(includesType(subResources, Resource.Type.Host));
     assertTrue(includesType(subResources, Resource.Type.Configuration));
     assertTrue(includesType(subResources, Resource.Type.Request));
     assertTrue(includesType(subResources, Resource.Type.Workflow));
     assertTrue(includesType(subResources, Resource.Type.ConfigGroup));
+    assertTrue(includesType(subResources, Resource.Type.AlertDefinition));
   }
 
   @Test

+ 22 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java

@@ -61,6 +61,7 @@ import org.apache.ambari.server.state.RepositoryInfo;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.Stack;
 import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.alert.AlertDefinition;
 import org.apache.ambari.server.state.stack.MetricDefinition;
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
@@ -1382,4 +1383,25 @@ public class AmbariMetaInfoTest {
     Assert.assertNotNull(passwordProperty);
     Assert.assertEquals("javax.jdo.option.ConnectionPassword", passwordProperty.getName());
   }
+  
+  @Test
+  public void testAlertsJson() throws Exception {
+    ServiceInfo svc = metaInfo.getService(STACK_NAME_HDP, "2.0.5", "HDFS");
+    Assert.assertNotNull(svc);
+    Assert.assertNotNull(svc.getAlertsFile());
+
+    svc = metaInfo.getService(STACK_NAME_HDP, "2.0.6", "HDFS");
+    Assert.assertNotNull(svc);
+    Assert.assertNotNull(svc.getAlertsFile());
+
+    svc = metaInfo.getService(STACK_NAME_HDP, "1.3.4", "HDFS");
+    Assert.assertNotNull(svc);
+    Assert.assertNull(svc.getAlertsFile());
+    
+    Set<AlertDefinition> set = metaInfo.getAlertDefinitions(STACK_NAME_HDP,
+        "2.0.5", "HDFS");
+    Assert.assertNotNull(set);
+    Assert.assertTrue(set.size() > 0);
+    
+  }  
 }

+ 162 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertDefinitionResourceProviderTest.java

@@ -0,0 +1,162 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.alert.MetricAlert;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * AlertDefinition tests
+ */
+public class AlertDefinitionResourceProviderTest {
+
+  AlertDefinitionDAO dao = null;
+  
+  @Before
+  public void before() {
+    dao = EasyMock.createStrictMock(AlertDefinitionDAO.class);
+    
+    AlertDefinitionResourceProvider.init(dao);
+  }
+  
+  @Test
+  public void testGetResourcesNoPredicate() throws Exception {
+    AlertDefinitionResourceProvider provider = createProvider(null);
+    
+    Request request = PropertyHelper.getReadRequest("AlertDefinition/cluster_name",
+        "AlertDefinition/id");
+    
+    EasyMock.expect(dao.findAll()).andReturn(getMockEntities());
+
+    EasyMock.replay(dao);
+    
+    Set<Resource> results = provider.getResources(request, null);
+    
+    assertEquals(0, results.size());
+  }  
+
+  @Test
+  public void testGetResourcesClusterPredicate() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertDefinitionResourceProvider.ALERT_DEF_CLUSTER_NAME,
+        AlertDefinitionResourceProvider.ALERT_DEF_ID,
+        AlertDefinitionResourceProvider.ALERT_DEF_NAME);
+    
+    AmbariManagementController amc = EasyMock.createMock(AmbariManagementController.class);
+    Clusters clusters = EasyMock.createMock(Clusters.class);
+    Cluster cluster = EasyMock.createMock(Cluster.class);
+    EasyMock.expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+    EasyMock.expect(clusters.getCluster(EasyMock.<String>anyObject())).andReturn(cluster).atLeastOnce();
+    EasyMock.expect(cluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
+    
+    Predicate predicate = new PredicateBuilder().property(
+        AlertDefinitionResourceProvider.ALERT_DEF_CLUSTER_NAME).equals("c1").toPredicate();    
+    
+    EasyMock.expect(dao.findAll(1L)).andReturn(getMockEntities());
+
+    EasyMock.replay(amc, clusters, cluster, dao);
+    
+    AlertDefinitionResourceProvider provider = createProvider(amc);    
+    Set<Resource> results = provider.getResources(request, predicate);
+    
+    assertEquals(1, results.size());
+    
+    Resource r = results.iterator().next();
+    
+    Assert.assertEquals("my_def", r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_NAME));
+  }
+  
+  @Test
+  public void testGetSingleResource() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertDefinitionResourceProvider.ALERT_DEF_CLUSTER_NAME,
+        AlertDefinitionResourceProvider.ALERT_DEF_ID,
+        AlertDefinitionResourceProvider.ALERT_DEF_NAME,
+        AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE);
+    
+    AmbariManagementController amc = EasyMock.createMock(AmbariManagementController.class);
+    Clusters clusters = EasyMock.createMock(Clusters.class);
+    Cluster cluster = EasyMock.createMock(Cluster.class);
+    EasyMock.expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+    EasyMock.expect(clusters.getCluster(EasyMock.<String>anyObject())).andReturn(cluster).atLeastOnce();
+    EasyMock.expect(cluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
+    
+    Predicate predicate = new PredicateBuilder().property(
+        AlertDefinitionResourceProvider.ALERT_DEF_CLUSTER_NAME).equals("c1")
+          .and().property(AlertDefinitionResourceProvider.ALERT_DEF_ID).equals("1").toPredicate();    
+    
+    EasyMock.expect(dao.findById(1L)).andReturn(getMockEntities().get(0));
+
+    EasyMock.replay(amc, clusters, cluster, dao);
+    
+    AlertDefinitionResourceProvider provider = createProvider(amc);    
+    Set<Resource> results = provider.getResources(request, predicate);
+    
+    assertEquals(1, results.size());
+    
+    Resource r = results.iterator().next();
+    
+    Assert.assertEquals("my_def", r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_NAME));
+    Assert.assertEquals("metric", r.getPropertyValue(AlertDefinitionResourceProvider.ALERT_DEF_SOURCE_TYPE));
+    Assert.assertNotNull(r.getPropertyValue("AlertDefinition/metric"));
+    Assert.assertEquals(MetricAlert.class, r.getPropertyValue("AlertDefinition/metric").getClass());
+  }
+  
+  private AlertDefinitionResourceProvider createProvider(AmbariManagementController amc) {
+    return new AlertDefinitionResourceProvider(
+        PropertyHelper.getPropertyIds(Resource.Type.AlertDefinition),
+        PropertyHelper.getKeyPropertyIds(Resource.Type.AlertDefinition),
+        amc);
+  }
+  
+  private List<AlertDefinitionEntity> getMockEntities() {
+    AlertDefinitionEntity entity = new AlertDefinitionEntity();
+    entity.setClusterId(Long.valueOf(1L));
+    entity.setComponentName(null);
+    entity.setDefinitionId(Long.valueOf(1L));
+    entity.setDefinitionName("my_def");
+    entity.setEnabled(true);
+    entity.setHash("tmphash");
+    entity.setScheduleInterval(Long.valueOf(2L));
+    entity.setServiceName(null);
+    entity.setSourceType("metric");
+    entity.setSource("{'jmx': 'beanName/attributeName', 'host': '{{aa:123445}}'}");
+    
+    return Arrays.asList(entity);
+  }
+  
+}

+ 4 - 2
ambari-server/src/test/java/org/apache/ambari/server/orm/dao/AlertDefinitionDAOTest.java

@@ -81,7 +81,9 @@ public class AlertDefinitionDAOTest {
   public void testFindByName() {
     AlertDefinitionEntity entity = new AlertDefinitionEntity();
     TypedQuery<AlertDefinitionEntity> query = createStrictMock(TypedQuery.class);
-    
+
+    expect(query.setParameter("clusterId", 12345L)).andReturn(query);
+
     expect(query.setParameter("definitionName", "alert-definition-1")).andReturn(
         query);
 
@@ -93,7 +95,7 @@ public class AlertDefinitionDAOTest {
 
     replay(query, entityManager);
 
-    AlertDefinitionEntity result = dao.findByName("alert-definition-1");
+    AlertDefinitionEntity result = dao.findByName(12345L, "alert-definition-1");
 
     assertSame(result, entity);
     verify(entityManagerProvider, entityManager);

+ 52 - 0
ambari-server/src/test/resources/stacks/HDP/2.0.5/services/HDFS/alerts.json

@@ -0,0 +1,52 @@
+{
+  "service": [
+  ],
+  "SECONDARY_NAMENODE": [
+    {
+      "name": "secondary_namenode_process",
+      "label": "Secondary NameNode process",
+      "interval": 1,
+      "scope": "service",
+      "source": "port",
+      "port": {
+        "config": "{{hdfs-site/dfs.namenode.secondary.http-address}}:50071",
+        "default": 50071
+      }
+    }
+  ],
+  "NAMENODE": [
+    {
+      "name": "namenode_cpu",
+      "label": "NameNode host CPU Utilization",
+      "scope": "host",
+      "source": "metric",
+      "metric": {
+        "jmx": "java.lang:type=OperatingSystem/SystemCpuLoad",
+        "host": "{{hdfs-site/dfs.namenode.secondary.http-address}}"
+      }
+    },
+    {
+      "name": "namenode_process",
+      "label": "NameNode process",
+      "interval": 1,
+      "scope": "host",
+      "source": "port",
+      "port": {
+        "uri": "{{hdfs-site/dfs.namenode.http-address}}:50070"
+       }
+    },
+    {
+      "name": "hdfs_last_checkpoint",
+      "label": "Last Checkpoint Time",
+      "interval": 1,
+      "scope": "service",
+      "source": "script",
+      "enabled": false,
+      "script": {
+        "path": "scripts/alerts/last_checkpoint.py"
+      }
+    }
+  ],
+  "DATANODE": [
+  ]
+}