You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2014/08/27 14:52:00 UTC

[1/2] AMBARI-7021 - Alerts: Create Group and Target REST Endpoints (jonathanhurley)

Repository: ambari
Updated Branches:
  refs/heads/branch-alerts-dev 6ba11a2a9 -> 4a4644b88


http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java
new file mode 100644
index 0000000..982dd4b
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AlertTargetResourceProviderTest.java
@@ -0,0 +1,290 @@
+/**
+ * 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.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.AlertTargetEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.alert.TargetType;
+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 AlertTargetResourceProvider} tests.
+ */
+public class AlertTargetResourceProviderTest {
+
+  private static final Long ALERT_TARGET_ID = Long.valueOf(28);
+  private static final String ALERT_TARGET_NAME = "The Administrators";
+  private static final String ALERT_TARGET_DESC = "Admins and Others";
+  private static final String ALERT_TARGET_TYPE = TargetType.EMAIL.name();
+
+  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()));
+
+    AlertTargetResourceProvider.init(m_injector);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testGetResourcesNoPredicate() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertTargetResourceProvider.ALERT_TARGET_DESCRIPTION,
+        AlertTargetResourceProvider.ALERT_TARGET_ID,
+        AlertTargetResourceProvider.ALERT_TARGET_NAME,
+        AlertTargetResourceProvider.ALERT_TARGET_NOTIFICATION_TYPE);
+
+    expect(m_dao.findAllTargets()).andReturn(getMockEntities());
+    replay(m_dao);
+
+    AmbariManagementController amc = createMock(AmbariManagementController.class);
+    AlertTargetResourceProvider provider = createProvider(amc);
+    Set<Resource> results = provider.getResources(request, null);
+
+    assertEquals(1, results.size());
+
+    Resource r = results.iterator().next();
+    Assert.assertEquals(ALERT_TARGET_NAME,
+        r.getPropertyValue(AlertTargetResourceProvider.ALERT_TARGET_NAME));
+
+    verify(m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testGetSingleResource() throws Exception {
+    Request request = PropertyHelper.getReadRequest(
+        AlertTargetResourceProvider.ALERT_TARGET_DESCRIPTION,
+        AlertTargetResourceProvider.ALERT_TARGET_ID,
+        AlertTargetResourceProvider.ALERT_TARGET_NAME,
+        AlertTargetResourceProvider.ALERT_TARGET_NOTIFICATION_TYPE);
+
+    AmbariManagementController amc = createMock(AmbariManagementController.class);
+
+    Predicate predicate = new PredicateBuilder().property(
+        AlertTargetResourceProvider.ALERT_TARGET_ID).equals(
+        ALERT_TARGET_ID.toString()).toPredicate();
+
+    expect(m_dao.findTargetById(ALERT_TARGET_ID.longValue())).andReturn(
+        getMockEntities().get(0));
+
+    replay(amc, m_dao);
+
+    AlertTargetResourceProvider provider = createProvider(amc);
+    Set<Resource> results = provider.getResources(request, predicate);
+
+    assertEquals(1, results.size());
+
+    Resource r = results.iterator().next();
+    Assert.assertEquals(ALERT_TARGET_ID,
+        r.getPropertyValue(AlertTargetResourceProvider.ALERT_TARGET_ID));
+
+    Assert.assertEquals(ALERT_TARGET_NAME,
+        r.getPropertyValue(AlertTargetResourceProvider.ALERT_TARGET_NAME));
+
+    verify(amc, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testCreateResources() throws Exception {
+    AmbariManagementController amc = createMock(AmbariManagementController.class);
+    Capture<List<AlertTargetEntity>> listCapture = new Capture<List<AlertTargetEntity>>();
+
+    m_dao.createTargets(capture(listCapture));
+    expectLastCall();
+
+    replay(amc, m_dao);
+
+    AlertTargetResourceProvider provider = createProvider(amc);
+    Map<String, Object> requestProps = new HashMap<String, Object>();
+    requestProps.put(AlertTargetResourceProvider.ALERT_TARGET_NAME,
+        ALERT_TARGET_NAME);
+
+    requestProps.put(AlertTargetResourceProvider.ALERT_TARGET_DESCRIPTION,
+        ALERT_TARGET_DESC);
+
+    requestProps.put(
+        AlertTargetResourceProvider.ALERT_TARGET_NOTIFICATION_TYPE,
+        ALERT_TARGET_TYPE);
+
+    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    provider.createResources(request);
+
+    Assert.assertTrue(listCapture.hasCaptured());
+    AlertTargetEntity entity = listCapture.getValue().get(0);
+    Assert.assertNotNull(entity);
+
+    Assert.assertEquals(ALERT_TARGET_NAME, entity.getTargetName());
+    Assert.assertEquals(ALERT_TARGET_DESC, entity.getDescription());
+    Assert.assertEquals(ALERT_TARGET_TYPE, entity.getNotificationType());
+
+    verify(amc, m_dao);
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testUpdateResources() throws Exception {
+  }
+
+  /**
+   * @throws Exception
+   */
+  @Test
+  public void testDeleteResources() throws Exception {
+    AmbariManagementController amc = createMock(AmbariManagementController.class);
+    Capture<AlertTargetEntity> entityCapture = new Capture<AlertTargetEntity>();
+    Capture<List<AlertTargetEntity>> listCapture = new Capture<List<AlertTargetEntity>>();
+
+    m_dao.createTargets(capture(listCapture));
+    expectLastCall();
+
+    replay(amc, m_dao);
+
+    AlertTargetResourceProvider provider = createProvider(amc);
+
+    Map<String, Object> requestProps = new HashMap<String, Object>();
+    requestProps.put(AlertTargetResourceProvider.ALERT_TARGET_NAME,
+        ALERT_TARGET_NAME);
+
+    requestProps.put(AlertTargetResourceProvider.ALERT_TARGET_DESCRIPTION,
+        ALERT_TARGET_DESC);
+
+    requestProps.put(
+        AlertTargetResourceProvider.ALERT_TARGET_NOTIFICATION_TYPE,
+        ALERT_TARGET_TYPE);
+
+    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+    provider.createResources(request);
+
+    Assert.assertTrue(listCapture.hasCaptured());
+    AlertTargetEntity entity = listCapture.getValue().get(0);
+    Assert.assertNotNull(entity);
+
+    Predicate p = new PredicateBuilder().property(
+        AlertTargetResourceProvider.ALERT_TARGET_ID).equals(
+        ALERT_TARGET_ID.toString()).toPredicate();
+
+    // everything is mocked, there is no DB
+    entity.setTargetId(ALERT_TARGET_ID);
+
+    resetToStrict(m_dao);
+    expect(m_dao.findTargetById(ALERT_TARGET_ID.longValue())).andReturn(entity).anyTimes();
+    m_dao.remove(capture(entityCapture));
+    expectLastCall();
+    replay(m_dao);
+
+    provider.deleteResources(p);
+
+    AlertTargetEntity entity1 = entityCapture.getValue();
+    Assert.assertEquals(ALERT_TARGET_ID, entity1.getTargetId());
+
+    verify(amc, m_dao);
+  }
+
+  /**
+   * @param amc
+   * @return
+   */
+  private AlertTargetResourceProvider createProvider(
+      AmbariManagementController amc) {
+    return new AlertTargetResourceProvider(
+        PropertyHelper.getPropertyIds(Resource.Type.AlertTarget),
+        PropertyHelper.getKeyPropertyIds(Resource.Type.AlertTarget), amc);
+  }
+
+  /**
+   * @return
+   */
+  private List<AlertTargetEntity> getMockEntities() throws Exception {
+    AlertTargetEntity entity = new AlertTargetEntity();
+    entity.setTargetId(ALERT_TARGET_ID);
+    entity.setDescription(ALERT_TARGET_DESC);
+    entity.setTargetName(ALERT_TARGET_NAME);
+    entity.setNotificationType(TargetType.EMAIL.name());
+    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);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a4644b8/ambari-server/src/test/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostTest.java
index c0bdaa2..d7c8c54 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostTest.java
@@ -105,12 +105,12 @@ public class ServiceComponentHostTest {
     metaInfo.init();
     clusters.mapHostToCluster("h1","C1");
   }
-  
+
   private void setOsFamily(Host host, String osFamily, String osVersion) {
     Map<String, String> hostAttributes = new HashMap<String, String>(2);
     hostAttributes.put("os_family", osFamily);
     hostAttributes.put("os_release_version", osVersion);
-    
+
     host.setHostAttributes(hostAttributes);
   }
 
@@ -118,15 +118,16 @@ public class ServiceComponentHostTest {
   public void teardown() {
     injector.getInstance(PersistService.class).stop();
   }
-  
+
   private ServiceComponentHost createNewServiceComponentHost(
       String svc,
       String svcComponent,
       String hostName, boolean isClient) throws AmbariException{
     Cluster c = clusters.getCluster("C1");
-    
+
     return createNewServiceComponentHost(c, svc, svcComponent, hostName);
   }
+
   private ServiceComponentHost createNewServiceComponentHost(
       Cluster c,
       String svc,
@@ -156,18 +157,22 @@ public class ServiceComponentHostTest {
 
     ServiceComponentHost impl = serviceComponentHostFactory.createNew(
         sc, hostName);
+
     impl.persist();
-    Assert.assertEquals(State.INIT,
-        impl.getState());
-    Assert.assertEquals(State.INIT,
-        impl.getDesiredState());
+
+    Assert.assertEquals(State.INIT, impl.getState());
+    Assert.assertEquals(State.INIT, impl.getDesiredState());
     Assert.assertEquals(c.getClusterName(), impl.getClusterName());
     Assert.assertEquals(c.getClusterId(), impl.getClusterId());
     Assert.assertEquals(s.getName(), impl.getServiceName());
     Assert.assertEquals(sc.getName(), impl.getServiceComponentName());
     Assert.assertEquals(hostName, impl.getHostName());
+
+    Assert.assertNotNull(c.getServiceComponentHosts(hostName));
+
     Assert.assertFalse(
         impl.getDesiredStackVersion().getStackId().isEmpty());
+
     Assert.assertTrue(impl.getStackVersion().getStackId().isEmpty());
 
     return impl;
@@ -264,7 +269,7 @@ public class ServiceComponentHostTest {
 
     ServiceComponentHostEvent installEvent2 = createEvent(impl, ++timestamp,
         startEventType);
-   
+
     boolean exceptionThrown = false;
     LOG.info("Transitioning from " + impl.getState() + " " + installEvent2.getType());
     try {
@@ -503,7 +508,7 @@ public class ServiceComponentHostTest {
     Assert.assertEquals("HDP-1.1.0",
         sch.getDesiredStackVersion().getStackId());
   }
-  
+
   @Test
   public void testActualConfigs() throws Exception {
     ServiceComponentHost sch =
@@ -520,25 +525,25 @@ public class ServiceComponentHostTest {
 
     configGroup.persist();
     cluster.addConfigGroup(configGroup);
-    
+
     Map<String, Map<String,String>> actual =
         new HashMap<String, Map<String, String>>() {{
           put("global", new HashMap<String,String>() {{ put("tag", "version1"); }});
           put("core-site", new HashMap<String,String>() {{ put("tag", "version1");
             put(configGroup.getId().toString(), "version2"); }});
         }};
-        
+
     sch.updateActualConfigs(actual);
-    
+
     Map<String, HostConfig> confirm = sch.getActualConfigs();
-    
+
     Assert.assertEquals(2, confirm.size());
     Assert.assertTrue(confirm.containsKey("global"));
     Assert.assertTrue(confirm.containsKey("core-site"));
     Assert.assertEquals(1, confirm.get("core-site").getConfigGroupOverrides().size());
 
   }
-  
+
   @Test
   public void testConvertToResponse() throws AmbariException {
     ServiceComponentHost sch =
@@ -555,7 +560,7 @@ public class ServiceComponentHostTest {
     Assert.assertEquals(State.INSTALLED.toString(), r.getDesiredState());
     Assert.assertEquals(State.INSTALLING.toString(), r.getLiveState());
     Assert.assertEquals("HDP-1.0.0", r.getStackVersion());
-    
+
     Assert.assertFalse(r.isStaleConfig());
 
     // TODO check configs
@@ -615,62 +620,61 @@ public class ServiceComponentHostTest {
         impl.getState());
   }
 
-   @Test
-   public void TestDisableInVariousStates() throws AmbariException,
-           InvalidStateTransitionException {
-       ServiceComponentHost sch =
-               createNewServiceComponentHost("HDFS", "DATANODE", "h1", false);
-       ServiceComponentHostImpl impl =  (ServiceComponentHostImpl) sch;
-
-       // Test valid states in which host component can be disabled
-       long timestamp = 0;
-       HashSet<State> validStates = new HashSet<State>();
-       validStates.add(State.INSTALLED);
-       validStates.add(State.INSTALL_FAILED);
-       validStates.add(State.UNKNOWN);
-       validStates.add(State.DISABLED);
-       for (State state : validStates)
-       {
-         sch.setState(state);
-         ServiceComponentHostEvent disableEvent = createEvent(
-                   impl, ++timestamp, ServiceComponentHostEventType.HOST_SVCCOMP_DISABLE);
-         impl.handleEvent(disableEvent);
-         // TODO: At present operation timestamps are not getting updated.
-         Assert.assertEquals(-1, impl.getLastOpStartTime());
-         Assert.assertEquals(-1, impl.getLastOpLastUpdateTime());
-         Assert.assertEquals(-1, impl.getLastOpEndTime());
-         Assert.assertEquals(State.DISABLED, impl.getState());
-       }
-
-       // Test invalid states in which host component cannot be disabled
-       HashSet<State> invalidStates = new HashSet<State>();
-       invalidStates.add(State.INIT);
-       invalidStates.add(State.INSTALLING);
-       invalidStates.add(State.STARTING);
-       invalidStates.add(State.STARTED);
-       invalidStates.add(State.STOPPING);
-       invalidStates.add(State.UNINSTALLING);
-       invalidStates.add(State.UNINSTALLED);
-       invalidStates.add(State.UPGRADING);
-
-       for(State state : invalidStates)
-       {
-           sch.setState(state);
-           ServiceComponentHostEvent disableEvent = createEvent(
-                   impl, ++timestamp, ServiceComponentHostEventType.HOST_SVCCOMP_DISABLE);
-           boolean exceptionThrown = false;
-           try {
-               impl.handleEvent(disableEvent);
-           } catch (Exception e) {
-               exceptionThrown = true;
-           }
-           Assert.assertTrue("Exception not thrown on invalid event", exceptionThrown);
-           // TODO: At present operation timestamps are not getting updated.
-           Assert.assertEquals(-1, impl.getLastOpStartTime());
-           Assert.assertEquals(-1, impl.getLastOpLastUpdateTime());
-           Assert.assertEquals(-1, impl.getLastOpEndTime());
-       }
-   }
+  @Test
+  public void testDisableInVariousStates() throws AmbariException,
+      InvalidStateTransitionException {
+    ServiceComponentHost sch = createNewServiceComponentHost("HDFS",
+        "DATANODE", "h1", false);
+    ServiceComponentHostImpl impl = (ServiceComponentHostImpl) sch;
+
+    // Test valid states in which host component can be disabled
+    long timestamp = 0;
+    HashSet<State> validStates = new HashSet<State>();
+    validStates.add(State.INSTALLED);
+    validStates.add(State.INSTALL_FAILED);
+    validStates.add(State.UNKNOWN);
+    validStates.add(State.DISABLED);
+    for (State state : validStates) {
+      sch.setState(state);
+      ServiceComponentHostEvent disableEvent = createEvent(impl, ++timestamp,
+          ServiceComponentHostEventType.HOST_SVCCOMP_DISABLE);
+      impl.handleEvent(disableEvent);
+      // TODO: At present operation timestamps are not getting updated.
+      Assert.assertEquals(-1, impl.getLastOpStartTime());
+      Assert.assertEquals(-1, impl.getLastOpLastUpdateTime());
+      Assert.assertEquals(-1, impl.getLastOpEndTime());
+      Assert.assertEquals(State.DISABLED, impl.getState());
+    }
+
+    // Test invalid states in which host component cannot be disabled
+    HashSet<State> invalidStates = new HashSet<State>();
+    invalidStates.add(State.INIT);
+    invalidStates.add(State.INSTALLING);
+    invalidStates.add(State.STARTING);
+    invalidStates.add(State.STARTED);
+    invalidStates.add(State.STOPPING);
+    invalidStates.add(State.UNINSTALLING);
+    invalidStates.add(State.UNINSTALLED);
+    invalidStates.add(State.UPGRADING);
+
+    for (State state : invalidStates) {
+      sch.setState(state);
+      ServiceComponentHostEvent disableEvent = createEvent(impl, ++timestamp,
+          ServiceComponentHostEventType.HOST_SVCCOMP_DISABLE);
+      boolean exceptionThrown = false;
+      try {
+        impl.handleEvent(disableEvent);
+      } catch (Exception e) {
+        exceptionThrown = true;
+      }
+      Assert.assertTrue("Exception not thrown on invalid event",
+          exceptionThrown);
+      // TODO: At present operation timestamps are not getting updated.
+      Assert.assertEquals(-1, impl.getLastOpStartTime());
+      Assert.assertEquals(-1, impl.getLastOpLastUpdateTime());
+      Assert.assertEquals(-1, impl.getLastOpEndTime());
+    }
+  }
 
   @Test
   public void testCanBeRemoved() throws Exception{
@@ -688,13 +692,13 @@ public class ServiceComponentHostTest {
       }
     }
   }
-  
+
   @Test
   public void testStaleConfigs() throws Exception {
     String stackVersion="HDP-2.0.6";
     String clusterName = "c2";
     String hostName = "h3";
-    
+
     clusters.addCluster(clusterName);
     clusters.addHost(hostName);
     setOsFamily(clusters.getHost(hostName), "redhat", "5.9");
@@ -702,14 +706,14 @@ public class ServiceComponentHostTest {
     clusters.getCluster(clusterName).setDesiredStackVersion(
         new StackId(stackVersion));
     metaInfo.init();
-    clusters.mapHostToCluster(hostName, clusterName);    
-    
+    clusters.mapHostToCluster(hostName, clusterName);
+
     Cluster cluster = clusters.getCluster(clusterName);
-    
+
     ServiceComponentHost sch1 = createNewServiceComponentHost(cluster, "HDFS", "NAMENODE", hostName);
     ServiceComponentHost sch2 = createNewServiceComponentHost(cluster, "HDFS", "DATANODE", hostName);
     ServiceComponentHost sch3 = createNewServiceComponentHost(cluster, "MAPREDUCE2", "HISTORYSERVER", hostName);
-    
+
     sch1.setDesiredState(State.INSTALLED);
     sch1.setState(State.INSTALLING);
     sch1.setStackVersion(new StackId(stackVersion));
@@ -717,10 +721,10 @@ public class ServiceComponentHostTest {
     sch2.setDesiredState(State.INSTALLED);
     sch2.setState(State.INSTALLING);
     sch2.setStackVersion(new StackId(stackVersion));
-    
+
     sch3.setDesiredState(State.INSTALLED);
     sch3.setState(State.INSTALLING);
-    sch3.setStackVersion(new StackId(stackVersion));    
+    sch3.setStackVersion(new StackId(stackVersion));
 
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
@@ -735,7 +739,7 @@ public class ServiceComponentHostTest {
     Map<String, Map<String, String>> actual = new HashMap<String, Map<String, String>>() {{
       put("global", new HashMap<String,String>() {{ put("tag", "version1"); }});
     }};
-    
+
     sch1.updateActualConfigs(actual);
     sch2.updateActualConfigs(actual);
     sch3.updateActualConfigs(actual);
@@ -746,7 +750,7 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS does not define type 'foo', so changes do not count to stale
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
-    
+
     makeConfig(cluster, "hdfs-site", "version1",
         new HashMap<String,String>() {{ put("a", "b"); }}, new HashMap<String, Map<String,String>>());
 
@@ -755,7 +759,7 @@ public class ServiceComponentHostTest {
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
 
     actual.put("hdfs-site", new HashMap<String, String>() {{ put ("tag", "version1"); }});
-    
+
     sch1.updateActualConfigs(actual);
     // previous value from cache
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
@@ -765,7 +769,7 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS/hdfs-site up to date, only for sch1
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
-    
+
     sch2.updateActualConfigs(actual);
     // previous value from cache
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
@@ -775,15 +779,15 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS/hdfs-site up to date for both
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
-    
+
     makeConfig(cluster, "hdfs-site", "version2",
-        new HashMap<String, String>() {{ put("dfs.journalnode.http-address", "http://foo"); }}, 
+        new HashMap<String, String>() {{ put("dfs.journalnode.http-address", "http://foo"); }},
         new HashMap<String, Map<String,String>>());
 
     // HDP-x/HDFS/hdfs-site updated to changed property
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
-    
+
     actual.get("hdfs-site").put("tag", "version2");
     sch1.updateActualConfigs(actual);
     sch2.updateActualConfigs(actual);
@@ -794,13 +798,13 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS/hdfs-site updated to changed property
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
-    
+
     // make a host override
     final Host host = clusters.getHostsForCluster(cluster.getClusterName()).get(hostName);
     Assert.assertNotNull(host);
-    
+
     final Config c = configFactory.createNew(cluster, "hdfs-site",
-        new HashMap<String, String>() {{ put("dfs.journalnode.http-address", "http://goo"); }}, 
+        new HashMap<String, String>() {{ put("dfs.journalnode.http-address", "http://goo"); }},
         new HashMap<String, Map<String,String>>());
     c.setTag("version3");
     c.persist();
@@ -811,11 +815,11 @@ public class ServiceComponentHostTest {
       new HashMap<String, Host>() {{ put("h3", host); }});
     configGroup.persist();
     cluster.addConfigGroup(configGroup);
-    
+
     // HDP-x/HDFS/hdfs-site updated host to changed property
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
-    
+
     actual.get("hdfs-site").put(configGroup.getId().toString(), "version3");
     sch2.updateActualConfigs(actual);
     // previous value from cache
@@ -826,7 +830,7 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS/hdfs-site updated host to changed property
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
-    
+
     sch1.updateActualConfigs(actual);
     // previous value from cache
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
@@ -836,7 +840,7 @@ public class ServiceComponentHostTest {
     // HDP-x/HDFS/hdfs-site updated host to changed property
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
-    
+
     // change 'global' property only affecting global/HDFS
     makeConfig(cluster, "global", "version2",
       new HashMap<String,String>() {{
@@ -844,7 +848,7 @@ public class ServiceComponentHostTest {
         put("dfs_namenode_name_dir", "/foo3"); // HDFS only
         put("mapred_log_dir_prefix", "/foo2"); // MR2 only
       }}, new HashMap<String, Map<String,String>>());
-    
+
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch3.convertToResponse().isStaleConfig());
@@ -867,7 +871,7 @@ public class ServiceComponentHostTest {
     sch1.updateActualConfigs(actual);
 
     final Config c1 = configFactory.createNew(cluster, "core-site",
-      new HashMap<String, String>() {{ put("fs.trash.interval", "400"); }}, 
+      new HashMap<String, String>() {{ put("fs.trash.interval", "400"); }},
       new HashMap<String, Map<String,String>>());
     c1.setTag("version2");
     c1.persist();
@@ -912,13 +916,13 @@ public class ServiceComponentHostTest {
     sch3.setRestartRequired(false);
     Assert.assertFalse(sch3.convertToResponse().isStaleConfig());
   }
-  
+
   @Test
   public void testStaleConfigsAttributes() throws Exception {
     String stackVersion="HDP-2.0.6";
     String clusterName = "c2";
     String hostName = "h3";
-    
+
     clusters.addCluster(clusterName);
     clusters.addHost(hostName);
     setOsFamily(clusters.getHost(hostName), "redhat", "5.9");
@@ -926,14 +930,14 @@ public class ServiceComponentHostTest {
     clusters.getCluster(clusterName).setDesiredStackVersion(
         new StackId(stackVersion));
     metaInfo.init();
-    clusters.mapHostToCluster(hostName, clusterName);    
-    
+    clusters.mapHostToCluster(hostName, clusterName);
+
     Cluster cluster = clusters.getCluster(clusterName);
-    
+
     ServiceComponentHost sch1 = createNewServiceComponentHost(cluster, "HDFS", "NAMENODE", hostName);
     ServiceComponentHost sch2 = createNewServiceComponentHost(cluster, "HDFS", "DATANODE", hostName);
     ServiceComponentHost sch3 = createNewServiceComponentHost(cluster, "MAPREDUCE2", "HISTORYSERVER", hostName);
-    
+
     sch1.setDesiredState(State.INSTALLED);
     sch1.setState(State.INSTALLING);
     sch1.setStackVersion(new StackId(stackVersion));
@@ -941,10 +945,10 @@ public class ServiceComponentHostTest {
     sch2.setDesiredState(State.INSTALLED);
     sch2.setState(State.INSTALLING);
     sch2.setStackVersion(new StackId(stackVersion));
-    
+
     sch3.setDesiredState(State.INSTALLED);
     sch3.setState(State.INSTALLING);
-    sch3.setStackVersion(new StackId(stackVersion));    
+    sch3.setStackVersion(new StackId(stackVersion));
 
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch2.convertToResponse().isStaleConfig());
@@ -963,7 +967,7 @@ public class ServiceComponentHostTest {
       put("global", new HashMap<String,String>() {{ put("tag", "version1"); }});
       put("hdfs-site", new HashMap<String,String>() {{ put("tag", "version1"); }});
     }};
-    
+
     sch1.updateActualConfigs(actual);
     sch2.updateActualConfigs(actual);
     sch3.updateActualConfigs(actual);
@@ -972,7 +976,7 @@ public class ServiceComponentHostTest {
       new HashMap<String,String>() {{ put("a", "c"); }},new HashMap<String, Map<String,String>>(){{
        put("final", new HashMap<String, String>(){{
          put("a", "true");
-       }}); 
+       }});
       }});
     // HDP-x/HDFS does not define type 'foo', so changes do not count to stale
     Assert.assertFalse(sch1.convertToResponse().isStaleConfig());
@@ -985,7 +989,7 @@ public class ServiceComponentHostTest {
     sch3.setRestartRequired(false);
     sch3.updateActualConfigs(actual);
     Assert.assertFalse(sch3.convertToResponse().isStaleConfig());
-    
+
     // Now add config-attributes
     Map<String, Map<String, String>> c1PropAttributes = new HashMap<String, Map<String,String>>();
     c1PropAttributes.put("final", new HashMap<String, String>());
@@ -1000,7 +1004,7 @@ public class ServiceComponentHostTest {
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch3.convertToResponse().isStaleConfig());
-    
+
     // Now change config-attributes
     Map<String, Map<String, String>> c2PropAttributes = new HashMap<String, Map<String,String>>();
     c2PropAttributes.put("final", new HashMap<String, String>());
@@ -1015,7 +1019,7 @@ public class ServiceComponentHostTest {
     Assert.assertTrue(sch1.convertToResponse().isStaleConfig());
     Assert.assertTrue(sch2.convertToResponse().isStaleConfig());
     Assert.assertFalse(sch3.convertToResponse().isStaleConfig());
-    
+
     // Now change config-attributes
     makeConfig(cluster, "hdfs-site", "version4",
         new HashMap<String,String>() {{
@@ -1043,13 +1047,13 @@ public class ServiceComponentHostTest {
     cluster.addConfig(config);
     cluster.addDesiredConfig("user", config);
   }
-  
+
   @Test
   public void testMaintenance() throws Exception {
     String stackVersion="HDP-2.0.6";
     String clusterName = "c2";
     String hostName = "h3";
-    
+
     clusters.addCluster(clusterName);
     clusters.addHost(hostName);
     setOsFamily(clusters.getHost(hostName), "redhat", "5.9");
@@ -1057,10 +1061,10 @@ public class ServiceComponentHostTest {
     clusters.getCluster(clusterName).setDesiredStackVersion(
         new StackId(stackVersion));
     metaInfo.init();
-    clusters.mapHostToCluster(hostName, clusterName);    
-    
+    clusters.mapHostToCluster(hostName, clusterName);
+
     Cluster cluster = clusters.getCluster(clusterName);
-    
+
     ServiceComponentHost sch1 = createNewServiceComponentHost(cluster, "HDFS", "NAMENODE", hostName);
     ServiceComponentHost sch2 = createNewServiceComponentHost(cluster, "HDFS", "DATANODE", hostName);
     ServiceComponentHost sch3 = createNewServiceComponentHost(cluster, "MAPREDUCE2", "HISTORYSERVER", hostName);
@@ -1070,20 +1074,20 @@ public class ServiceComponentHostTest {
     pk.setComponentName(sch1.getServiceComponentName());
     pk.setServiceName(sch1.getServiceName());
     pk.setHostName(hostName);
-    
+
     HostComponentDesiredStateDAO dao = injector.getInstance(HostComponentDesiredStateDAO.class);
     HostComponentDesiredStateEntity entity = dao.findByPK(pk);
     Assert.assertEquals(MaintenanceState.OFF, entity.getMaintenanceState());
     Assert.assertEquals(MaintenanceState.OFF, sch1.getMaintenanceState());
-    
+
     sch1.setMaintenanceState(MaintenanceState.ON);
     Assert.assertEquals(MaintenanceState.ON, sch1.getMaintenanceState());
-    
+
     entity = dao.findByPK(pk);
     Assert.assertEquals(MaintenanceState.ON, entity.getMaintenanceState());
 
   }
 
-  
-  
+
+
 }


[2/2] git commit: AMBARI-7021 - Alerts: Create Group and Target REST Endpoints (jonathanhurley)

Posted by jo...@apache.org.
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/branch-alerts-dev
Commit: 4a4644b882c9a0d39b249db5a106f7075cb0c93d
Parents: 6ba11a2
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Tue Aug 26 14:21:31 2014 -0400
Committer: Jonathan Hurley <jh...@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);
+    }
+  }
+}