AMBARI-7021 - Alerts: Create Group and Target REST Endpoints (jonathanhurley)


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

Branch: refs/heads/trunk
Commit: 4a4644b882c9a0d39b249db5a106f7075cb0c93d
Parents: 6ba11a2
Author: Jonathan Hurley <jhur...@hortonworks.com>
Authored: Tue Aug 26 14:21:31 2014 -0400
Committer: Jonathan Hurley <jhur...@hortonworks.com>
Committed: Wed Aug 27 08:51:49 2014 -0400

----------------------------------------------------------------------
 .../python/ambari_agent/alerts/base_alert.py    |   4 +-
 .../python/ambari_agent/alerts/port_alert.py    |  11 +-
 .../ambari/server/agent/HeartBeatHandler.java   |  16 +-
 .../resources/AlertGroupResourceDefinition.java |  50 +++
 .../AlertTargetResourceDefinition.java          |  50 +++
 .../resources/ResourceInstanceFactoryImpl.java  |   8 +
 .../server/api/services/AlertGroupService.java  | 111 ++++++
 .../server/api/services/AlertTargetService.java |  96 ++++++
 .../server/api/services/ClusterService.java     |  66 +++-
 .../ambari/server/controller/AmbariServer.java  |   4 +
 .../AbstractControllerResourceProvider.java     |  10 +-
 .../internal/AlertGroupResourceProvider.java    | 268 +++++++++++++++
 .../internal/AlertTargetResourceProvider.java   | 258 ++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |  13 +-
 .../ambari/server/orm/dao/AlertDispatchDAO.java |  78 +++--
 .../ambari/server/state/alert/AlertGroup.java   | 124 +++++++
 .../ambari/server/state/alert/AlertTarget.java  | 123 +++++++
 .../ambari/server/state/alert/TargetType.java   |  37 ++
 .../svccomphost/ServiceComponentHostImpl.java   |   2 +
 .../src/main/resources/key_properties.json      |   7 +
 .../src/main/resources/properties.json          |  18 +-
 .../AlertGroupResourceProviderTest.java         | 335 +++++++++++++++++++
 .../AlertTargetResourceProviderTest.java        | 290 ++++++++++++++++
 .../svccomphost/ServiceComponentHostTest.java   | 242 +++++++-------
 24 files changed, 2045 insertions(+), 176 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-agent/src/main/python/ambari_agent/alerts/base_alert.py
----------------------------------------------------------------------
diff --git a/ambari-agent/src/main/python/ambari_agent/alerts/base_alert.py 
b/ambari-agent/src/main/python/ambari_agent/alerts/base_alert.py
index 07987d9..88c7f92 100644
--- a/ambari-agent/src/main/python/ambari_agent/alerts/base_alert.py
+++ b/ambari-agent/src/main/python/ambari_agent/alerts/base_alert.py
@@ -34,7 +34,7 @@ class BaseAlert(object):
     self.alert_meta = alert_meta
     self.alert_source_meta = alert_source_meta
     self.cluster = ''
-    self.hostname = ''
+    self.hostName = ''
     self._lookup_keys = []
     
     
@@ -54,7 +54,7 @@ class BaseAlert(object):
   def set_cluster(self, cluster, host):
     ''' sets cluster information for the alert '''
     self.cluster = cluster
-    self.hostname = host
+    self.hostName = host
   
   def collect(self):
     ''' method used for collection.  defers to _collect() '''

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-agent/src/main/python/ambari_agent/alerts/port_alert.py
----------------------------------------------------------------------
diff --git a/ambari-agent/src/main/python/ambari_agent/alerts/port_alert.py 
b/ambari-agent/src/main/python/ambari_agent/alerts/port_alert.py
index d06d1f4..eaf744a 100644
--- a/ambari-agent/src/main/python/ambari_agent/alerts/port_alert.py
+++ b/ambari-agent/src/main/python/ambari_agent/alerts/port_alert.py
@@ -22,7 +22,6 @@ import logging
 import re
 import socket
 import time
-import traceback
 from alerts.base_alert import BaseAlert
 from resource_management.libraries.functions.get_port_from_url import 
get_port_from_url
 
@@ -40,7 +39,7 @@ class PortAlert(BaseAlert):
   def _collect(self):
     urivalue = self._lookup_property_value(self.uri)
 
-    host = get_host_from_url(urivalue)
+    host = get_host_from_url(self, urivalue)
     port = self.port
     
     try:
@@ -76,9 +75,9 @@ Tested on the following cases:
   "hdfs://192.168.54.3/foo/bar"
   "ftp://192.168.54.4:7842/foo/bar";
 '''    
-def get_host_from_url(uri):
+def get_host_from_url(self, uri):
   # RFC3986, Appendix B
-  parts = 
re.findall('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?' , uri)
+  parts = 
re.findall('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?', uri)
 
   # index of parts
   # scheme    = 1
@@ -96,6 +95,10 @@ def get_host_from_url(uri):
     host_and_port = parts[0][3]
 
   if -1 == host_and_port.find(':'):
+    # if no : then it might only be a port; if it's a port, return this host
+    if host_and_port.isdigit():
+      return self.hostName
+
     return host_and_port
   else:
     return host_and_port.split(':')[0]

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
index dca3bd9..d0d5a13 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
@@ -229,11 +229,13 @@ public class HeartBeatHandler {
 
   protected void calculateHostAlerts(HeartBeat heartbeat, String hostname)
           throws AmbariException {
-      if (heartbeat != null && hostname != null) {
-        for (Cluster cluster : clusterFsm.getClustersForHost(hostname)) {
-          cluster.addAlerts(heartbeat.getNodeStatus().getAlerts());
-        }
-      }
+    if (null == hostname || null == heartbeat) {
+      return;
+    }
+
+    for (Cluster cluster : clusterFsm.getClustersForHost(hostname)) {
+      cluster.addAlerts(heartbeat.getNodeStatus().getAlerts());
+    }
   }
 
   protected void processHostStatus(HeartBeat heartbeat, String hostname) 
throws AmbariException {
@@ -710,7 +712,7 @@ public class HeartBeatHandler {
     response.setResponseStatus(RegistrationStatus.OK);
 
     // force the registering agent host to receive its list of alert 
definitions
-    List<AlertDefinitionCommand> alertDefinitionCommands = 
getAlertDefinitionCommands(hostname);
+    List<AlertDefinitionCommand> alertDefinitionCommands = 
getRegistrationAlertDefinitionCommands(hostname);
     response.setAlertDefinitionCommands(alertDefinitionCommands);
 
     Long requestId = 0L;
@@ -787,7 +789,7 @@ public class HeartBeatHandler {
    * @return
    * @throws AmbariException
    */
-  private List<AlertDefinitionCommand> getAlertDefinitionCommands(
+  private List<AlertDefinitionCommand> getRegistrationAlertDefinitionCommands(
       String hostname) throws AmbariException {
 
     Set<Cluster> hostClusters = clusterFsm.getClustersForHost(hostname);

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertGroupResourceDefinition.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertGroupResourceDefinition.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertGroupResourceDefinition.java
new file mode 100644
index 0000000..67e5d60
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertGroupResourceDefinition.java
@@ -0,0 +1,50 @@
+/**
+ * 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;
+
+/**
+ * The {@link AlertGroupResourceDefinition} class is used to register alert
+ * groups to be returned via the REST API.
+ */
+public class AlertGroupResourceDefinition extends BaseResourceDefinition {
+
+  /**
+   * Constructor.
+   */
+  public AlertGroupResourceDefinition() {
+    super(Resource.Type.AlertGroup);
+  }
+
+  /**
+   *
+   */
+  @Override
+  public String getPluralName() {
+    return "alert_groups";
+  }
+
+  /**
+   *
+   */
+  @Override
+  public String getSingularName() {
+    return "alert_group";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertTargetResourceDefinition.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertTargetResourceDefinition.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertTargetResourceDefinition.java
new file mode 100644
index 0000000..bc0d81d
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/AlertTargetResourceDefinition.java
@@ -0,0 +1,50 @@
+/**
+ * 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;
+
+/**
+ * The {@link AlertTargetResourceDefinition} class is used to register alert
+ * targets to be returned via the REST API.
+ */
+public class AlertTargetResourceDefinition extends BaseResourceDefinition {
+
+  /**
+   * Constructor.
+   */
+  public AlertTargetResourceDefinition() {
+    super(Resource.Type.AlertTarget);
+  }
+
+  /**
+   *
+   */
+  @Override
+  public String getPluralName() {
+    return "alert_targets";
+  }
+
+  /**
+   *
+   */
+  @Override
+  public String getSingularName() {
+    return "alert_target";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index 13fff1d..7faf365 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -251,6 +251,14 @@ public class ResourceInstanceFactoryImpl implements 
ResourceInstanceFactory {
         resourceDefinition = new AlertDefResourceDefinition();
         break;
 
+      case AlertGroup:
+        resourceDefinition = new AlertGroupResourceDefinition();
+        break;
+
+      case AlertTarget:
+        resourceDefinition = new AlertTargetResourceDefinition();
+        break;
+
       case AmbariPrivilege:
         resourceDefinition = new 
PrivilegeResourceDefinition(Resource.Type.AmbariPrivilege);
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java
new file mode 100644
index 0000000..c2e5048
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertGroupService.java
@@ -0,0 +1,111 @@
+/**
+ * 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.DELETE;
+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;
+import org.apache.ambari.server.state.alert.AlertGroup;
+
+/**
+ * The {@link AlertGroupService} handles CRUD operations for a cluster's alert
+ * groups.
+ */
+public class AlertGroupService extends BaseService {
+
+  /**
+   * Cluster name for cluster-based requests
+   */
+  private String m_clusterName = null;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterName
+   */
+  AlertGroupService(String clusterName) {
+    m_clusterName = clusterName;
+  }
+
+  @GET
+  @Produces("text/plain")
+  public Response getGroups(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createAlertGroupResource(m_clusterName, null));
+  }
+
+  @GET
+  @Produces("text/plain")
+  @Path("{groupId}")
+  public Response getGroup(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("groupId") Long groupId) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createAlertGroupResource(m_clusterName, groupId));
+  }
+
+  @POST
+  @Produces("text/plain")
+  public Response createGroup(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createAlertGroupResource(m_clusterName, null));
+  }
+
+  @DELETE
+  @Produces("text/plain")
+  @Path("{groupId}")
+  public Response deleteGroup(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("groupId") Long groupId) {
+    return handleRequest(headers, body, ui, Request.Type.DELETE,
+        createAlertGroupResource(m_clusterName, groupId));
+  }
+
+  /**
+   * Create a request capturing the group ID and resource type for an
+   * {@link AlertGroup}.
+   *
+   * @param groupId
+   *          the unique ID of the group to create the query for (not
+   *          {@code null}).
+   * @return the instance of the query.
+   */
+  private ResourceInstance createAlertGroupResource(String clusterName,
+      Long groupId) {
+
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Cluster, m_clusterName);
+    mapIds.put(Resource.Type.AlertGroup,
+        null == groupId ? null : groupId.toString());
+
+    return createResource(Resource.Type.AlertGroup, mapIds);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertTargetService.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertTargetService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertTargetService.java
new file mode 100644
index 0000000..86281b3
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AlertTargetService.java
@@ -0,0 +1,96 @@
+/**
+ * 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.DELETE;
+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;
+import org.apache.ambari.server.state.alert.AlertTarget;
+
+/**
+ * The {@link AlertTargetService} handles CRUD operation requests for alert
+ * targets.
+ */
+@Path("/alert_targets/")
+public class AlertTargetService extends BaseService {
+
+  @GET
+  @Produces("text/plain")
+  public Response getTargets(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createAlertTargetResource(null));
+  }
+
+  @GET
+  @Produces("text/plain")
+  @Path("{targetId}")
+  public Response getTargets(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("targetId") Long targetId) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createAlertTargetResource(targetId));
+  }
+
+  @POST
+  @Produces("text/plain")
+  public Response createTarget(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createAlertTargetResource(null));
+  }
+
+  @DELETE
+  @Produces("text/plain")
+  @Path("{targetId}")
+  public Response deleteTarget(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("targetId") Long targetId) {
+    return handleRequest(headers, body, ui, Request.Type.DELETE,
+        createAlertTargetResource(targetId));
+  }
+
+  /**
+   * Create a request capturing the target ID and resource type for an
+   * {@link AlertTarget}.
+   *
+   * @param targetId
+   *          the unique ID of the target to create the query for (not
+   *          {@code null}).
+   * @return the instance of the query.
+   */
+  private ResourceInstance createAlertTargetResource(Long targetId) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+
+    mapIds.put(Resource.Type.AlertTarget,
+        null == targetId ? null : targetId.toString());
+
+    return createResource(Resource.Type.AlertTarget, mapIds);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
index 8043b3f..3f1ce36 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
@@ -18,15 +18,26 @@
 
 package org.apache.ambari.server.api.services;
 
+import java.util.Collections;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+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.AmbariServer;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.state.Clusters;
 
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
-import java.util.Collections;
-
 
 /**
  * Service responsible for cluster resource requests.
@@ -46,7 +57,7 @@ public class ClusterService extends BaseService {
    * Construct a ClusterService.
    */
   public ClusterService() {
-    this.clusters = AmbariServer.getController().getClusters();
+    clusters = AmbariServer.getController().getClusters();
   }
 
   /**
@@ -187,7 +198,7 @@ public class ClusterService extends BaseService {
     hasPermission(Request.Type.valueOf(request.getMethod()), clusterName);
     return new ServiceService(clusterName);
   }
-  
+
   /**
    * Gets the configurations sub-resource.
    *
@@ -295,7 +306,7 @@ public class ClusterService extends BaseService {
     hasPermission(Request.Type.valueOf(request.getMethod()), clusterName);
     return new RequestScheduleService(clusterName);
   }
-  
+
   /**
    * Gets the alert definition service
    *
@@ -313,12 +324,32 @@ public class ClusterService extends BaseService {
   }
 
   /**
+   * Gets the alert group service.
+   *
+   * @param request
+   *          the request.
+   * @param clusterName
+   *          the cluster name.
+   * @return the alert group service.
+   */
+  @Path("{clusterName}/alert_groups")
+  public AlertGroupService getAlertGroups(
+      @Context javax.ws.rs.core.Request request,
+      @PathParam("clusterName") String clusterName) {
+
+    hasPermission(Request.Type.valueOf(request.getMethod()), clusterName);
+    return new AlertGroupService(clusterName);
+  }
+
+  /**
    * Gets the privilege service
    *
-   * @param request      the request
-   * @param clusterName  the cluster name
+   * @param request
+   *          the request
+   * @param clusterName
+   *          the cluster name
    *
-   * @return  the privileges service
+   * @return the privileges service
    */
   @Path("{clusterName}/privileges")
   public PrivilegeService getPrivilegeService(@Context 
javax.ws.rs.core.Request request, @PathParam ("clusterName") String 
clusterName) {
@@ -343,14 +374,17 @@ public class ClusterService extends BaseService {
   }
 
   /**
-   * Determine whether or not the access specified by the given request type
-   * is permitted for the current user on the cluster resource identified by
-   * the given cluster name.
+   * Determine whether or not the access specified by the given request type is
+   * permitted for the current user on the cluster resource identified by the
+   * given cluster name.
    *
-   * @param requestType  the request method type
-   * @param clusterName  the name of the cluster resource
+   * @param requestType
+   *          the request method type
+   * @param clusterName
+   *          the name of the cluster resource
    *
-   * @throws WebApplicationException if access is forbidden
+   * @throws WebApplicationException
+   *           if access is forbidden
    */
   private void hasPermission(Request.Type requestType, String clusterName) 
throws WebApplicationException {
     if (!clusters.checkPermission(clusterName, requestType == 
Request.Type.GET)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
index 21c9d80..c75a7bb 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
@@ -46,6 +46,8 @@ 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.AlertGroupResourceProvider;
+import 
org.apache.ambari.server.controller.internal.AlertTargetResourceProvider;
 import 
org.apache.ambari.server.controller.internal.AmbariPrivilegeResourceProvider;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import 
org.apache.ambari.server.controller.internal.ClusterPrivilegeResourceProvider;
@@ -538,6 +540,8 @@ public class AmbariServer {
     StackDependencyResourceProvider.init(ambariMetaInfo);
     ClusterResourceProvider.init(injector.getInstance(BlueprintDAO.class), 
ambariMetaInfo, injector.getInstance(ConfigHelper.class));
     AlertDefinitionResourceProvider.init(injector);
+    AlertGroupResourceProvider.init(injector);
+    AlertTargetResourceProvider.init(injector);
     PermissionResourceProvider.init(injector.getInstance(PermissionDAO.class));
     
ViewPermissionResourceProvider.init(injector.getInstance(PermissionDAO.class));
     PrivilegeResourceProvider.init(injector.getInstance(PrivilegeDAO.class), 
injector.getInstance(UserDAO.class),

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
index 71ddc8d..c10d300 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
@@ -18,15 +18,15 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.ResourceProviderFactory;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
 
-import java.util.Map;
-import java.util.Set;
-
 /**
  * Abstract resource provider implementation that maps to an Ambari management 
controller.
  */
@@ -149,6 +149,10 @@ public abstract class AbstractControllerResourceProvider 
extends AbstractResourc
         return new ValidationResourceProvider(propertyIds, keyPropertyIds, 
managementController);
       case AlertDefinition:
         return new AlertDefinitionResourceProvider(propertyIds, 
keyPropertyIds, managementController);
+      case AlertGroup:
+        return new AlertGroupResourceProvider(propertyIds, keyPropertyIds, 
managementController);
+      case AlertTarget:
+        return new AlertTargetResourceProvider(propertyIds, keyPropertyIds, 
managementController);
       case Controller:
         return new ControllerResourceProvider(propertyIds, keyPropertyIds, 
managementController);
       case ClientConfig:

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java
new file mode 100644
index 0000000..f14e76f
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProvider.java
@@ -0,0 +1,268 @@
+/**
+ * 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.ArrayList;
+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.Resource.Type;
+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.AlertDispatchDAO;
+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.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+/**
+ * The {@link AlertGroupResourceProvider} class deals with managing the CRUD
+ * operations for {@link AlertGroup}, including property coercion to and from
+ * {@link AlertGroupEntity}.
+ */
+public class AlertGroupResourceProvider extends
+    AbstractControllerResourceProvider {
+
+  protected static final String ALERT_GROUP = "AlertGroup";
+  protected static final String ALERT_GROUP_ID = "AlertGroup/id";
+  protected static final String ALERT_GROUP_CLUSTER_NAME = 
"AlertGroup/cluster_name";
+  protected static final String ALERT_GROUP_NAME = "AlertGroup/name";
+  protected static final String ALERT_GROUP_DEFAULT = "AlertGroup/default";
+  protected static final String ALERT_GROUP_DEFINITIONS = 
"AlertGroup/definitions";
+  protected static final String ALERT_GROUP_TARGETS = "AlertGroup/targets";
+
+  private static final Set<String> PK_PROPERTY_IDS = new HashSet<String>(
+      Arrays.asList(ALERT_GROUP_ID, ALERT_GROUP_CLUSTER_NAME));
+
+  /**
+   * Group DAO
+   */
+  @Inject
+  private static AlertDispatchDAO s_dao;
+
+  /**
+   * Initializes the injectable members of this class with the specified
+   * injector.
+   *
+   * @param injector
+   *          the injector (not {@code null}).
+   */
+  @Inject
+  public static void init(Injector injector) {
+    s_dao = injector.getInstance(AlertDispatchDAO.class);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param propertyIds
+   * @param keyPropertyIds
+   * @param managementController
+   */
+  AlertGroupResourceProvider(Set<String> propertyIds,
+      Map<Type, String> keyPropertyIds,
+      AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(final Request request)
+      throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+
+    createResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        createAlertGroups(request.getProperties());
+        return null;
+      }
+    });
+
+    notifyCreate(Resource.Type.AlertGroup, request);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<Resource> results = new HashSet<Resource>();
+    Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      String clusterName = (String) propertyMap.get(ALERT_GROUP_CLUSTER_NAME);
+
+      if (null == clusterName || clusterName.isEmpty()) {
+        throw new IllegalArgumentException("The cluster name is required when 
retrieving alert groups");
+      }
+
+      String id = (String) propertyMap.get(ALERT_GROUP_ID);
+      if (null != id) {
+        AlertGroupEntity entity = s_dao.findGroupById(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 ae) {
+          throw new NoSuchResourceException("Parent Cluster resource doesn't 
exist", ae);
+        }
+
+        List<AlertGroupEntity> entities = 
s_dao.findAllGroups(cluster.getClusterId());
+
+        for (AlertGroupEntity 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();
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<Resource> resources = getResources(new RequestImpl(null, null, null,
+        null), predicate);
+
+    Set<Long> groupIds = new HashSet<Long>();
+
+    for (final Resource resource : resources) {
+      Long id = (Long) resource.getPropertyValue(ALERT_GROUP_ID);
+      groupIds.add(id);
+    }
+
+    for (Long groupId : groupIds) {
+      LOG.info("Deleting alert target {}", groupId);
+
+      final AlertGroupEntity entity = s_dao.findGroupById(groupId.longValue());
+
+      modifyResources(new Command<Void>() {
+        @Override
+        public Void invoke() throws AmbariException {
+          s_dao.remove(entity);
+          return null;
+        }
+      });
+    }
+
+    notifyDelete(Resource.Type.AlertGroup, predicate);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return PK_PROPERTY_IDS;
+  }
+
+  /**
+   * Create and persist {@link AlertTargetEntity} from the map of properties.
+   *
+   * @param requestMaps
+   * @throws AmbariException
+   */
+  private void createAlertGroups(Set<Map<String, Object>> requestMaps)
+      throws AmbariException {
+
+    List<AlertGroupEntity> entities = new ArrayList<AlertGroupEntity>();
+    for (Map<String, Object> requestMap : requestMaps) {
+      AlertGroupEntity entity = new AlertGroupEntity();
+
+      String name = (String) requestMap.get(ALERT_GROUP_NAME);
+      String clusterName = (String) requestMap.get(ALERT_GROUP_CLUSTER_NAME);
+
+      if (StringUtils.isEmpty(name)) {
+        throw new IllegalArgumentException(
+            "The name of the alert group is required.");
+      }
+
+      if (StringUtils.isEmpty(clusterName)) {
+        throw new IllegalArgumentException(
+            "The name of the cluster is required when creating an alert 
group.");
+      }
+
+      Cluster cluster = getManagementController().getClusters().getCluster(
+          clusterName);
+
+      entity.setClusterId(cluster.getClusterId());
+      entity.setDefault(false);
+      entity.setGroupName(name);
+
+      entities.add(entity);
+    }
+
+    s_dao.createGroups(entities);
+  }
+
+  /**
+   * 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,
+      Set<String> requestedIds) {
+
+    Resource resource = new ResourceImpl(Resource.Type.AlertGroup);
+    resource.setProperty(ALERT_GROUP_ID, entity.getGroupId());
+    resource.setProperty(ALERT_GROUP_NAME, entity.getGroupName());
+    resource.setProperty(ALERT_GROUP_CLUSTER_NAME, clusterName);
+
+    setResourceProperty(resource, ALERT_GROUP_DEFAULT,
+        entity.isDefault(), requestedIds);
+
+    return resource;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java
new file mode 100644
index 0000000..e00f60f
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProvider.java
@@ -0,0 +1,258 @@
+/**
+ * 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.ArrayList;
+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.Resource.Type;
+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.AlertDispatchDAO;
+import org.apache.ambari.server.orm.entities.AlertTargetEntity;
+import org.apache.ambari.server.state.alert.AlertTarget;
+import org.apache.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+/**
+ * The {@link AlertTargetResourceProvider} class deals with managing the CRUD
+ * operations for {@link AlertTarget}, including property coercion to and from
+ * {@link AlertTargetEntity}.
+ */
+public class AlertTargetResourceProvider extends
+    AbstractControllerResourceProvider {
+
+  protected static final String ALERT_TARGET = "AlertTarget";
+  protected static final String ALERT_TARGET_ID = "AlertTarget/id";
+  protected static final String ALERT_TARGET_NAME = "AlertTarget/name";
+  protected static final String ALERT_TARGET_DESCRIPTION = 
"AlertTarget/description";
+  protected static final String ALERT_TARGET_NOTIFICATION_TYPE = 
"AlertTarget/notification_type";
+  protected static final String ALERT_TARGET_PROPERTIES = 
"AlertTarget/properties";
+  protected static final String ALERT_TARGET_GROUPS = "AlertTarget/groups";
+
+  private static final Set<String> PK_PROPERTY_IDS = new HashSet<String>(
+      Arrays.asList(ALERT_TARGET_ID, ALERT_TARGET_NAME));
+
+  /**
+   * Target DAO
+   */
+  @Inject
+  private static AlertDispatchDAO s_dao;
+
+  /**
+   * Initializes the injectable members of this class with the specified
+   * injector.
+   *
+   * @param injector
+   *          the injector (not {@code null}).
+   */
+  @Inject
+  public static void init(Injector injector) {
+    s_dao = injector.getInstance(AlertDispatchDAO.class);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param propertyIds
+   * @param keyPropertyIds
+   * @param managementController
+   */
+  AlertTargetResourceProvider(Set<String> propertyIds,
+      Map<Type, String> keyPropertyIds,
+      AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(final Request request)
+      throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+
+    createResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        createAlertTargets(request.getProperties());
+        return null;
+      }
+    });
+
+    notifyCreate(Resource.Type.AlertTarget, request);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<Resource> results = new HashSet<Resource>();
+    Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+
+    if( null == predicate ){
+      List<AlertTargetEntity> entities = s_dao.findAllTargets();
+      for (AlertTargetEntity entity : entities) {
+        results.add(toResource(true, entity, requestPropertyIds));
+      }
+    } else {
+      for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+        String id = (String) propertyMap.get(ALERT_TARGET_ID);
+        if (null == id) {
+          continue;
+        }
+
+        AlertTargetEntity entity = s_dao.findTargetById(Long.parseLong(id));
+        if (null != entity) {
+          results.add(toResource(false, entity, requestPropertyIds));
+        }
+      }
+    }
+
+    return results;
+  }
+
+  @Override
+  public RequestStatus updateResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<Resource> resources = getResources(new RequestImpl(null, null, null,
+        null), predicate);
+
+    Set<Long> targetIds = new HashSet<Long>();
+
+    for (final Resource resource : resources) {
+      Long id = (Long) resource.getPropertyValue(ALERT_TARGET_ID);
+      targetIds.add(id);
+    }
+
+    for (Long targetId : targetIds) {
+      LOG.info("Deleting alert target {}", targetId);
+
+      final AlertTargetEntity entity = 
s_dao.findTargetById(targetId.longValue());
+
+      modifyResources(new Command<Void>() {
+        @Override
+        public Void invoke() throws AmbariException {
+          s_dao.remove(entity);
+          return null;
+        }
+      });
+    }
+
+    notifyDelete(Resource.Type.AlertTarget, predicate);
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return PK_PROPERTY_IDS;
+  }
+
+  /**
+   * Create and persist {@link AlertTargetEntity} from the map of properties.
+   *
+   * @param requestMaps
+   * @throws AmbariException
+   */
+  private void createAlertTargets(Set<Map<String, Object>> requestMaps)
+      throws AmbariException {
+    List<AlertTargetEntity> entities = new ArrayList<AlertTargetEntity>();
+    for (Map<String, Object> requestMap : requestMaps) {
+      AlertTargetEntity entity = new AlertTargetEntity();
+
+      String name = (String) requestMap.get(ALERT_TARGET_NAME);
+      String description = (String) requestMap.get(ALERT_TARGET_DESCRIPTION);
+      String notificationType = (String) 
requestMap.get(ALERT_TARGET_NOTIFICATION_TYPE);
+      String properties = (String) requestMap.get(ALERT_TARGET_PROPERTIES);
+
+      if (StringUtils.isEmpty(name)) {
+        throw new IllegalArgumentException(
+            "The name of the alert target is required.");
+      }
+
+      if (StringUtils.isEmpty(notificationType)) {
+        throw new IllegalArgumentException(
+            "The type of the alert target is required.");
+      }
+
+      entity.setDescription(description);
+      entity.setNotificationType(notificationType);
+      entity.setProperties(properties);
+      entity.setTargetName(name);
+
+      entities.add(entity);
+    }
+
+    s_dao.createTargets(entities);
+  }
+
+  /**
+   * Convert the given {@link AlertTargetEntity} 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, AlertTargetEntity entity,
+      Set<String> requestedIds) {
+
+    Resource resource = new ResourceImpl(Resource.Type.AlertTarget);
+    resource.setProperty(ALERT_TARGET_ID, entity.getTargetId());
+    resource.setProperty(ALERT_TARGET_NAME, entity.getTargetName());
+
+    setResourceProperty(resource, ALERT_TARGET_DESCRIPTION,
+        entity.getDescription(), requestedIds);
+
+    setResourceProperty(resource, ALERT_TARGET_NOTIFICATION_TYPE,
+        entity.getNotificationType(), requestedIds);
+
+    setResourceProperty(resource, ALERT_TARGET_PROPERTIES,
+        entity.getProperties(), requestedIds);
+
+    return resource;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index b10b4bc..3114809 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -115,6 +115,8 @@ public interface Resource {
     HostComponentProcess,
     Permission,
     AlertDefinition,
+    AlertGroup,
+    AlertTarget,
     AmbariPrivilege,
     ClusterPrivilege,
     ViewPrivilege,
@@ -196,6 +198,8 @@ public interface Resource {
     public static final Type HostComponentProcess = 
InternalType.HostComponentProcess.getType();
     public static final Type Permission = InternalType.Permission.getType();
     public static final Type AlertDefinition = 
InternalType.AlertDefinition.getType();
+    public static final Type AlertGroup = InternalType.AlertGroup.getType();
+    public static final Type AlertTarget = InternalType.AlertTarget.getType();
     public static final Type AmbariPrivilege = 
InternalType.AmbariPrivilege.getType();
     public static final Type ClusterPrivilege = 
InternalType.ClusterPrivilege.getType();
     public static final Type ViewPrivilege = 
InternalType.ViewPrivilege.getType();
@@ -315,8 +319,13 @@ public interface Resource {
 
     @Override
     public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
+      if (this == o) {
+        return true;
+      }
+
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
 
       Type type = (Type) o;
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
index 2871e62..dfbe747 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AlertDispatchDAO.java
@@ -51,7 +51,7 @@ public class AlertDispatchDAO {
 
   /**
    * 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.
@@ -62,7 +62,7 @@ public class AlertDispatchDAO {
 
   /**
    * 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.
@@ -73,7 +73,7 @@ public class AlertDispatchDAO {
 
   /**
    * Gets a notification with the specified ID.
-   * 
+   *
    * @param noticeId
    *          the ID of the notification to retrieve.
    * @return the notification or {@code null} if none exists.
@@ -85,7 +85,7 @@ public class AlertDispatchDAO {
   /**
    * 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.
@@ -102,7 +102,7 @@ public class AlertDispatchDAO {
   /**
    * 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
@@ -122,7 +122,7 @@ public class AlertDispatchDAO {
   /**
    * 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.
@@ -138,7 +138,7 @@ public class AlertDispatchDAO {
 
   /**
    * Gets all alert groups stored in the database across all clusters.
-   * 
+   *
    * @return all alert groups or empty list if none exist (never {@code null}).
    */
   public List<AlertGroupEntity> findAllGroups() {
@@ -150,7 +150,7 @@ public class AlertDispatchDAO {
 
   /**
    * 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}).
    */
@@ -165,7 +165,7 @@ public class AlertDispatchDAO {
 
   /**
    * Gets all alert targets stored in the database.
-   * 
+   *
    * @return all alert targets or empty list if none exist (never {@code 
null}).
    */
   public List<AlertTargetEntity> findAllTargets() {
@@ -177,7 +177,7 @@ public class AlertDispatchDAO {
 
   /**
    * Gets all alert notifications stored in the database.
-   * 
+   *
    * @return all alert notifications or empty list if none exist (never
    *         {@code null}).
    */
@@ -189,6 +189,23 @@ public class AlertDispatchDAO {
   }
 
   /**
+   * Persists new alert groups.
+   *
+   * @param entities
+   *          the groups to persist (not {@code null}).
+   */
+  @Transactional
+  public void createGroups(List<AlertGroupEntity> entities) {
+    if (null == entities) {
+      return;
+    }
+
+    for (AlertGroupEntity entity : entities) {
+      create(entity);
+    }
+  }
+
+  /**
    * Persists a new alert group.
    * 
    * @param alertGroup
@@ -201,7 +218,7 @@ public class AlertDispatchDAO {
 
   /**
    * Refresh the state of the alert group from the database.
-   * 
+   *
    * @param alertGroup
    *          the group to refresh (not {@code null}).
    */
@@ -212,7 +229,7 @@ public class AlertDispatchDAO {
 
   /**
    * 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}).
@@ -224,7 +241,7 @@ public class AlertDispatchDAO {
 
   /**
    * Removes the specified alert group from the database.
-   * 
+   *
    * @param alertGroup
    *          the group to remove.
    */
@@ -234,8 +251,25 @@ public class AlertDispatchDAO {
   }
 
   /**
+   * Persists new alert targets.
+   *
+   * @param entities
+   *          the targets to persist (not {@code null}).
+   */
+  @Transactional
+  public void createTargets(List<AlertTargetEntity> entities) {
+    if (null == entities) {
+      return;
+    }
+
+    for (AlertTargetEntity entity : entities) {
+      create(entity);
+    }
+  }
+
+  /**
    * Persists a new alert target.
-   * 
+   *
    * @param alertTarget
    *          the target to persist (not {@code null}).
    */
@@ -246,7 +280,7 @@ public class AlertDispatchDAO {
 
   /**
    * Refresh the state of the alert target from the database.
-   * 
+   *
    * @param alertTarget
    *          the target to refresh (not {@code null}).
    */
@@ -257,7 +291,7 @@ public class AlertDispatchDAO {
 
   /**
    * 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}).
@@ -269,7 +303,7 @@ public class AlertDispatchDAO {
 
   /**
    * Removes the specified alert target from the database.
-   * 
+   *
    * @param alertTarget
    *          the target to remove.
    */
@@ -280,7 +314,7 @@ public class AlertDispatchDAO {
 
   /**
    * Persists a new notification.
-   * 
+   *
    * @param alertNotice
    *          the notification to persist (not {@code null}).
    */
@@ -291,7 +325,7 @@ public class AlertDispatchDAO {
 
   /**
    * Refresh the state of the notification from the database.
-   * 
+   *
    * @param alertNotice
    *          the notification to refresh (not {@code null}).
    */
@@ -302,7 +336,7 @@ public class AlertDispatchDAO {
 
   /**
    * Merge the specified notification with the existing target in the database.
-   * 
+   *
    * @param alertNotice
    *          the notification to merge (not {@code null}).
    * @return the updated notification with merged content (never {@code null}).
@@ -314,7 +348,7 @@ public class AlertDispatchDAO {
 
   /**
    * Removes the specified notification from the database.
-   * 
+   *
    * @param alertNotice
    *          the notification to remove.
    */
@@ -327,7 +361,7 @@ public class AlertDispatchDAO {
    * Removes notifications for the specified alert definition ID. This will
    * invoke {@link EntityManager#clear()} when completed since the JPQL
    * statement will remove entries without going through the EM.
-   * 
+   *
    * @param definitionId
    *          the ID of the definition to remove.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java
new file mode 100644
index 0000000..659efa9
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertGroup.java
@@ -0,0 +1,124 @@
+/**
+ * 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 java.util.List;
+
+/**
+ * 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.
+ */
+public class AlertGroup {
+  private String m_id;
+  private String m_name;
+  private String m_clusterName;
+  private boolean m_isDefault;
+  private List<AlertDefinition> m_definitions;
+  private List<AlertTarget> m_targets;
+
+  /**
+   * @return the id
+   */
+  public String getId() {
+    return m_id;
+  }
+
+  /**
+   * @param id
+   *          the id to set
+   */
+  public void setId(String id) {
+    m_id = id;
+  }
+
+  /**
+   * @return the name
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * @param name
+   *          the name to set
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  /**
+   * @return the clusterName
+   */
+  public String getClusterName() {
+    return m_clusterName;
+  }
+
+  /**
+   * @param clusterName
+   *          the clusterName to set
+   */
+  public void setClusterName(String clusterName) {
+    m_clusterName = clusterName;
+  }
+
+  /**
+   * @return the isDefault
+   */
+  public boolean isDefault() {
+    return m_isDefault;
+  }
+
+  /**
+   * @param isDefault
+   *          the isDefault to set
+   */
+  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;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java
new file mode 100644
index 0000000..21e83d9
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/AlertTarget.java
@@ -0,0 +1,123 @@
+/**
+ * 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 java.util.List;
+
+/**
+ * The {@link AlertTarget} class represents a dispatch mechanism and audience
+ * that will receive information about alerts int he system.
+ */
+public class AlertTarget {
+  private String m_id;
+  private String m_name;
+  private String m_description;
+  private String m_notificationType;
+  private String m_properties;
+  private List<AlertGroup> m_groups;
+
+  /**
+   * @return the id
+   */
+  public String getId() {
+    return m_id;
+  }
+
+  /**
+   * @param id
+   *          the id to set
+   */
+  public void setId(String id) {
+    m_id = id;
+  }
+
+  /**
+   * @return the name
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * @param name
+   *          the name to set
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  /**
+   * @return the description
+   */
+  public String getDescription() {
+    return m_description;
+  }
+
+  /**
+   * @param description
+   *          the description to set
+   */
+  public void setDescription(String description) {
+    m_description = description;
+  }
+
+  /**
+   * @return the notificationType
+   */
+  public String getNotificationType() {
+    return m_notificationType;
+  }
+
+  /**
+   * @param notificationType
+   *          the notificationType to set
+   */
+  public void setNotificationType(String notificationType) {
+    m_notificationType = notificationType;
+  }
+
+  /**
+   * @return the properties
+   */
+  public String getProperties() {
+    return m_properties;
+  }
+
+  /**
+   * @param properties
+   *          the properties to set
+   */
+  public void setProperties(String properties) {
+    m_properties = properties;
+  }
+
+  /**
+   * @return the groups
+   */
+  public List<AlertGroup> getGroups() {
+    return m_groups;
+  }
+
+  /**
+   * @param groups
+   *          the groups to set
+   */
+  public void setGroups(List<AlertGroup> groups) {
+    m_groups = groups;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/state/alert/TargetType.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/TargetType.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/TargetType.java
new file mode 100644
index 0000000..e2564cc
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/TargetType.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.alert;
+
+import org.apache.ambari.server.orm.dao.AlertsDAO;
+
+/**
+ * The {@link TargetType} enumeration is used to represent the built-in target
+ * dispatch mechanisms that are supported internally. {@link AlertTarget}
+ * instances may have other custom target types that are not listed here.
+ */
+public enum TargetType {
+  /**
+   * Alerts will be distributed via email.
+   */
+  EMAIL,
+
+  /**
+   * {@link AlertsDAO} will be distributed via SNMP.
+   */
+  SNMP;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
index cc5bb5b..75265b7 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
@@ -555,6 +555,8 @@ public class ServiceComponentHostImpl implements 
ServiceComponentHost {
       impl.alertDefinitionHash.invalidate(impl.getClusterName(), hostName);
       impl.alertDefinitionHash.enqueueAgentCommands(impl.getClusterName(),
           Collections.singleton(hostName));
+
+      impl.updateLastOpInfo(event.getType(), event.getOpTimestamp());
     }
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/resources/key_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/key_properties.json 
b/ambari-server/src/main/resources/key_properties.json
index 06ebb61..3007fee 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -149,6 +149,13 @@
     "Cluster": "AlertDefinition/cluster_name",
     "AlertDefinition": "AlertDefinition/id"
   },
+  "AlertTarget": {
+    "AlertTarget": "AlertTarget/id"
+  },
+  "AlertGroup": {
+    "Cluster": "AlertGroup/cluster_name",
+    "AlertGroup": "AlertGroup/id"
+  },  
   "Controller": {
     "Controller": "Controllers/name"
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/main/resources/properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/properties.json 
b/ambari-server/src/main/resources/properties.json
index bc2ad22..9574aa9 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -425,7 +425,23 @@
       "AlertDefinition/enabled",
       "AlertDefinition/scope",
       "AlertDefinition/source"
-    ],      
+    ],
+    "AlertGroup": [
+      "AlertGroup/id",
+      "AlertGroup/name",
+      "AlertGroup/cluster_name",
+      "AlertGroup/default",
+      "AlertGroup/definitions",
+      "AlertGroup/targets"
+    ],
+    "AlertTarget": [
+      "AlertTarget/id",
+      "AlertTarget/name",
+      "AlertTarget/description",      
+      "AlertTarget/notification_type",
+      "AlertTarget/properties",
+      "AlertTarget/groups"
+    ],
     "Controller":[
         "Controllers/name",
         "LDAP/configured",

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java
new file mode 100644
index 0000000..c9428b7
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertGroupResourceProviderTest.java
@@ -0,0 +1,335 @@
+/**
+ * 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.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.resetToStrict;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
+import org.apache.ambari.server.orm.entities.AlertGroupEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+
+/**
+ * {@link AlertGroupResourceProvider} tests.
+ */
+public class AlertGroupResourceProviderTest {
+
+  private static final Long ALERT_GROUP_ID = Long.valueOf(28);
+  private static final String ALERT_GROUP_NAME = "Important Alerts";
+  private static final long ALERT_GROUP_CLUSTER_ID = 1L;
+  private static final String ALERT_GROUP_CLUSTER_NAME = "c1";
+
+  private AlertDispatchDAO m_dao;
+  private Injector m_injector;
+
+  @Before
+  public void before() {
+    m_dao = createStrictMock(AlertDispatchDAO.class);
+
+    m_injector = Guice.createInjector(Modules.override(
+        new InMemoryDefaultTestModule()).with(new MockModule()));
+
+    AlertGroupResourceProvider.init(m_injector);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testGetResourcesNoPredicate() throws Exception {
+    AlertGroupResourceProvider provider = createProvider(null);
+
+    Request request = PropertyHelper.getReadRequest("AlertGroup/cluster_name",
+        "AlertGroup/id");
+
+    Set<Resource> results = provider.getResources(request, null);
+
+    assertEquals(0, results.size());
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testGetResourcesClusterPredicate() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertGroupResourceProvider.ALERT_GROUP_ID,
+        AlertGroupResourceProvider.ALERT_GROUP_NAME,
+        AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME,
+        AlertGroupResourceProvider.ALERT_GROUP_DEFAULT);
+
+    AmbariManagementController amc = 
createMock(AmbariManagementController.class);
+    Clusters clusters = createMock(Clusters.class);
+    Cluster cluster = createMock(Cluster.class);
+    expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+    expect(clusters.getCluster((String) 
anyObject())).andReturn(cluster).atLeastOnce();
+    expect(cluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
+
+    Predicate predicate = new PredicateBuilder().property(
+        
AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME).equals("c1").toPredicate();
+
+    expect(m_dao.findAllGroups(ALERT_GROUP_CLUSTER_ID)).andReturn(
+        getMockEntities());
+
+    replay(amc, clusters, cluster, m_dao);
+
+    AlertGroupResourceProvider provider = createProvider(amc);
+    Set<Resource> results = provider.getResources(request, predicate);
+
+    assertEquals(1, results.size());
+
+    Resource r = results.iterator().next();
+
+    Assert.assertEquals(ALERT_GROUP_NAME,
+        r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_NAME));
+
+    Assert.assertEquals(ALERT_GROUP_ID,
+        r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_ID));
+
+    Assert.assertEquals(ALERT_GROUP_CLUSTER_NAME,
+        
r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME));
+
+    verify(amc, clusters, cluster, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testGetSingleResource() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertGroupResourceProvider.ALERT_GROUP_ID,
+        AlertGroupResourceProvider.ALERT_GROUP_NAME,
+        AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME,
+        AlertGroupResourceProvider.ALERT_GROUP_DEFAULT);
+
+    AmbariManagementController amc = 
createMock(AmbariManagementController.class);
+
+    Predicate predicate = new PredicateBuilder().property(
+        AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME).equals(
+        ALERT_GROUP_CLUSTER_NAME).and().property(
+        AlertGroupResourceProvider.ALERT_GROUP_ID).equals(
+        ALERT_GROUP_ID.toString()).toPredicate();
+
+    expect(m_dao.findGroupById(ALERT_GROUP_ID.longValue())).andReturn(
+        getMockEntities().get(0));
+
+    replay(amc, m_dao);
+
+    AlertGroupResourceProvider provider = createProvider(amc);
+    Set<Resource> results = provider.getResources(request, predicate);
+
+    assertEquals(1, results.size());
+
+    Resource r = results.iterator().next();
+
+    Assert.assertEquals(ALERT_GROUP_NAME,
+        r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_NAME));
+
+    Assert.assertEquals(ALERT_GROUP_ID,
+        r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_ID));
+
+    Assert.assertEquals(ALERT_GROUP_CLUSTER_NAME,
+        
r.getPropertyValue(AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME));
+
+    verify(amc, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testCreateResources() throws Exception {
+    AmbariManagementController amc = 
createMock(AmbariManagementController.class);
+    Clusters clusters = createMock(Clusters.class);
+    Cluster cluster = createMock(Cluster.class);
+    expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+    expect(clusters.getCluster((String) 
anyObject())).andReturn(cluster).atLeastOnce();
+    expect(cluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
+
+    Capture<List<AlertGroupEntity>> listCapture = new 
Capture<List<AlertGroupEntity>>();
+
+    m_dao.createGroups(capture(listCapture));
+    expectLastCall();
+
+    replay(amc, clusters, cluster, m_dao);
+
+    AlertGroupResourceProvider provider = createProvider(amc);
+    Map<String, Object> requestProps = new HashMap<String, Object>();
+    requestProps.put(AlertGroupResourceProvider.ALERT_GROUP_NAME,
+        ALERT_GROUP_NAME);
+
+    requestProps.put(AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME,
+        ALERT_GROUP_CLUSTER_NAME);
+
+    Request request = 
PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    provider.createResources(request);
+
+    Assert.assertTrue(listCapture.hasCaptured());
+    AlertGroupEntity entity = listCapture.getValue().get(0);
+    Assert.assertNotNull(entity);
+
+    Assert.assertEquals(ALERT_GROUP_NAME, entity.getGroupName());
+    Assert.assertEquals(ALERT_GROUP_CLUSTER_ID,
+        entity.getClusterId().longValue());
+
+    verify(amc, clusters, cluster, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testUpdateResources() throws Exception {
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testDeleteResources() throws Exception {
+    AmbariManagementController amc = 
createMock(AmbariManagementController.class);
+    Clusters clusters = createMock(Clusters.class);
+    Cluster cluster = createMock(Cluster.class);
+    expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+    expect(clusters.getCluster((String) 
anyObject())).andReturn(cluster).atLeastOnce();
+    expect(cluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
+
+    Capture<AlertGroupEntity> entityCapture = new Capture<AlertGroupEntity>();
+    Capture<List<AlertGroupEntity>> listCapture = new 
Capture<List<AlertGroupEntity>>();
+
+    m_dao.createGroups(capture(listCapture));
+    expectLastCall();
+
+    replay(amc, clusters, cluster, m_dao);
+
+    AlertGroupResourceProvider provider = createProvider(amc);
+
+    Map<String, Object> requestProps = new HashMap<String, Object>();
+    requestProps.put(AlertGroupResourceProvider.ALERT_GROUP_NAME,
+        ALERT_GROUP_NAME);
+
+    requestProps.put(AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME,
+        ALERT_GROUP_CLUSTER_NAME);
+
+    Request request = 
PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    provider.createResources(request);
+
+    Assert.assertTrue(listCapture.hasCaptured());
+    AlertGroupEntity entity = listCapture.getValue().get(0);
+    Assert.assertNotNull(entity);
+
+    Predicate predicate = new PredicateBuilder().property(
+        AlertGroupResourceProvider.ALERT_GROUP_CLUSTER_NAME).equals(
+        ALERT_GROUP_CLUSTER_NAME).and().property(
+        AlertGroupResourceProvider.ALERT_GROUP_ID).equals(
+        ALERT_GROUP_ID.toString()).toPredicate();
+
+    // everything is mocked, there is no DB
+    entity.setGroupId(ALERT_GROUP_ID);
+
+    resetToStrict(m_dao);
+    
expect(m_dao.findGroupById(ALERT_GROUP_ID.longValue())).andReturn(entity).anyTimes();
+    m_dao.remove(capture(entityCapture));
+    expectLastCall();
+    replay(m_dao);
+
+    provider.deleteResources(predicate);
+
+    AlertGroupEntity entity1 = entityCapture.getValue();
+    Assert.assertEquals(ALERT_GROUP_ID, entity1.getGroupId());
+
+    verify(amc, clusters, cluster, m_dao);
+  }
+
+  /**
+   * @param amc
+   * @return
+   */
+  private AlertGroupResourceProvider createProvider(
+      AmbariManagementController amc) {
+    return new AlertGroupResourceProvider(
+        PropertyHelper.getPropertyIds(Resource.Type.AlertGroup),
+        PropertyHelper.getKeyPropertyIds(Resource.Type.AlertGroup), amc);
+  }
+
+  /**
+   * @return
+   */
+  private List<AlertGroupEntity> getMockEntities() throws Exception {
+    AlertGroupEntity entity = new AlertGroupEntity();
+    entity.setGroupId(ALERT_GROUP_ID);
+    entity.setGroupName(ALERT_GROUP_NAME);
+    entity.setClusterId(ALERT_GROUP_CLUSTER_ID);
+    entity.setDefault(false);
+    return Arrays.asList(entity);
+  }
+
+  /**
+  *
+  */
+  private class MockModule implements Module {
+    /**
+    *
+    */
+    @Override
+    public void configure(Binder binder) {
+      binder.bind(AlertDispatchDAO.class).toInstance(m_dao);
+      binder.bind(Clusters.class).toInstance(
+          EasyMock.createNiceMock(Clusters.class));
+      binder.bind(Cluster.class).toInstance(
+          EasyMock.createNiceMock(Cluster.class));
+      binder.bind(ActionMetadata.class);
+    }
+  }
+}

Reply via email to