You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jl...@apache.org on 2016/06/27 23:36:56 UTC

[30/34] ambari git commit: AMBARI-17355 & AMBARI-17354: POC: FE & BE changes for first class support for Yarn hosted services

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java
index 034aeb7..3e4b6aa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProvider.java
@@ -19,6 +19,7 @@
 package org.apache.ambari.server.controller.internal;
 
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
@@ -138,8 +139,9 @@ public class QuickLinkArtifactResourceProvider extends AbstractControllerResourc
       String stackService = (String) properties.get(STACK_SERVICE_NAME_PROPERTY_ID);
 
       StackInfo stackInfo;
+      AmbariMetaInfo ambariMetaInfo = getManagementController().getAmbariMetaInfo();
       try {
-        stackInfo = getManagementController().getAmbariMetaInfo().getStack(stackName, stackVersion);
+        stackInfo = ambariMetaInfo.getStack(stackName, stackVersion);
       } catch (AmbariException e) {
         throw new NoSuchParentResourceException(String.format(
           "Parent stack resource doesn't exist: stackName='%s', stackVersion='%s'", stackName, stackVersion));
@@ -150,13 +152,19 @@ public class QuickLinkArtifactResourceProvider extends AbstractControllerResourc
       if (stackService == null) {
         serviceInfoList.addAll(stackInfo.getServices());
       } else {
-        ServiceInfo service = stackInfo.getService(stackService);
-        if (service == null) {
+        try {
+          ServiceInfo service = ambariMetaInfo.getService(stackName, stackVersion, stackService);
+          if (service == null) {
+            throw new NoSuchParentResourceException(String.format(
+                "Parent stack/service resource doesn't exist: stackName='%s', stackVersion='%s', serviceName='%s'",
+                stackName, stackVersion, stackService));
+          }
+          serviceInfoList.add(service);
+        } catch (AmbariException e) {
           throw new NoSuchParentResourceException(String.format(
-            "Parent stack/service resource doesn't exist: stackName='%s', stackVersion='%s', serviceName='%s'",
-            stackName, stackVersion, stackService));
+              "Parent stack/service resource doesn't exist: stackName='%s', stackVersion='%s', serviceName='%s'",
+              stackName, stackVersion, stackService));
         }
-        serviceInfoList.add(service);
       }
 
       for (ServiceInfo serviceInfo : serviceInfoList) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java
index 413dbff..49892cb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java
@@ -63,6 +63,7 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
    * Remote Cluster property id constants.
    */
   public static final String CLUSTER_NAME_PROPERTY_ID = "ClusterInfo/name";
+  public static final String CLUSTER_ID_PROPERTY_ID = "ClusterInfo/cluster_id";
   public static final String CLUSTER_URL_PROPERTY_ID  = "ClusterInfo/url";
   public static final String USERNAME_PROPERTY_ID = "ClusterInfo/username";
   public static final String PASSWORD_PROPERTY_ID = "ClusterInfo/password";
@@ -87,6 +88,7 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
   private static Set<String> propertyIds = new HashSet<String>();
   static {
     propertyIds.add(CLUSTER_NAME_PROPERTY_ID);
+    propertyIds.add(CLUSTER_ID_PROPERTY_ID);
     propertyIds.add(CLUSTER_URL_PROPERTY_ID);
     propertyIds.add(USERNAME_PROPERTY_ID);
     propertyIds.add(PASSWORD_PROPERTY_ID);
@@ -167,6 +169,7 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
   protected Resource toResource(Set<String> requestedIds, RemoteAmbariClusterEntity cluster) {
     Resource   resource   = new ResourceImpl(Resource.Type.RemoteCluster);
     setResourceProperty(resource, CLUSTER_NAME_PROPERTY_ID, cluster.getName(), requestedIds);
+    setResourceProperty(resource, CLUSTER_ID_PROPERTY_ID, cluster.getId(), requestedIds);
     setResourceProperty(resource, CLUSTER_URL_PROPERTY_ID, cluster.getUrl(), requestedIds);
     setResourceProperty(resource, USERNAME_PROPERTY_ID, cluster.getUsername(), requestedIds);
     ArrayList<String> services = new ArrayList<String>();
@@ -237,8 +240,14 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
       public Void invoke() throws AmbariException {
         String name = (String)properties.get(CLUSTER_NAME_PROPERTY_ID);
 
-        if(StringUtils.isEmpty(name)){
-          throw new IllegalArgumentException("Cluster Name cannot ne null or Empty");
+        if (StringUtils.isEmpty(name)) {
+          throw new IllegalArgumentException("Cluster Name cannot be null or Empty");
+        }
+
+        String id = (String)properties.get(CLUSTER_ID_PROPERTY_ID);
+
+        if (StringUtils.isEmpty(id)) {
+          throw new IllegalArgumentException("Cluster Id cannot be null or Empty");
         }
 
         saveOrUpdateRemoteAmbariClusterEntity(properties,true);
@@ -261,16 +270,24 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
     String username = (String)properties.get(USERNAME_PROPERTY_ID);
     String password = (String)properties.get(PASSWORD_PROPERTY_ID);
 
-    if(StringUtils.isEmpty(url) && StringUtils.isEmpty(username)){
+    if (StringUtils.isEmpty(url) && StringUtils.isEmpty(username)) {
       throw new IllegalArgumentException("Url or username cannot be null");
     }
 
-    RemoteAmbariClusterEntity entity = remoteAmbariClusterDAO.findByName(name);
+    RemoteAmbariClusterEntity entity ;
+
+    if (update) {
+      Long id = Long.valueOf((String) properties.get(CLUSTER_ID_PROPERTY_ID));
+      entity = remoteAmbariClusterDAO.findById(id);
+      if (entity == null) {
+        throw new IllegalArgumentException(String.format("Cannot find cluster with Id : \"%s\"", id));
+      }
+    } else {
 
-    if(update && entity == null){
-      throw new IllegalArgumentException(String.format("Cannot find cluster with name : \"%s\"",name));
-    }else if(!update && entity != null){
-      throw new DuplicateResourceException(String.format("Cluster with name : \"%s\" already exists",name));
+      entity = remoteAmbariClusterDAO.findByName(name);
+      if (entity != null) {
+        throw new DuplicateResourceException(String.format("Cluster with name : \"%s\" already exists", name));
+      }
     }
 
     // Check Password not null for create
@@ -288,7 +305,7 @@ public class RemoteClusterResourceProvider extends AbstractAuthorizedResourcePro
     entity.setName(name);
     entity.setUrl(url);
     try {
-      if(password != null) {
+      if (password != null) {
         entity.setUsername(username);
         entity.setPassword(password);
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceGroupResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceGroupResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceGroupResourceProvider.java
new file mode 100644
index 0000000..ff67c5b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceGroupResourceProvider.java
@@ -0,0 +1,759 @@
+/**
+ * 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 com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import jline.internal.Log;
+import org.apache.ambari.server.*;
+import org.apache.ambari.server.api.services.*;
+import org.apache.ambari.server.controller.*;
+import org.apache.ambari.server.controller.servicegroup.cache.ServiceGroupCacheKey;
+import org.apache.ambari.server.controller.servicegroup.cache.ServiceGroupCacheProvider;
+import org.apache.ambari.server.controller.spi.*;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.controller.servicegroup.cache.ServiceGroupCache;
+import org.apache.ambari.server.controller.servicegroup.cache.ServiceGroupCacheValue;
+import org.apache.ambari.server.controller.servicegroup.cache.ServiceGroupCacheProvider;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+import org.apache.ambari.server.security.authorization.AuthorizationHelper;
+import org.apache.ambari.server.security.authorization.ResourceType;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
+import org.apache.ambari.server.serveraction.kerberos.KerberosAdminAuthenticationException;
+import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
+import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException;
+import org.apache.ambari.server.state.*;
+import org.apache.ambari.server.utils.MapUtils;
+import org.apache.ambari.server.utils.StageUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.*;
+import java.net.URLEncoder;
+import java.io.OutputStream;
+
+/**
+ * Resource provider for service resources.
+ */
+public class ServiceGroupResourceProvider extends AbstractControllerResourceProvider {
+
+
+  // ----- Property ID constants ---------------------------------------------
+
+  // Services
+  public static final String SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceGroupInfo", "cluster_name");
+  public static final String SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceGroupInfo", "service_group_name");
+  public static final String SERVICE_GROUP_SERVICE_GROUP_DISPLAY_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceGroupInfo", "service_group_display_name");
+  public static final String SERVICE_GROUP_SERVICE_GROUP_TYPE_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceGroupInfo", "service_group_type");
+  public static final String SERVICE_GROUP_ASSEMBLY_FILE_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceGroupInfo", "assembly_file");
+  public static final String SERVICE_GROUP_DESIRED_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "desired_state");
+  public static final String SERVICE_GROUP_CURRENT_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "current_state");
+  public static final String SERVICE_GROUP_APPLICATION_ID_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "application_id");
+  public static final String SERVICE_GROUP_LIFETIME_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "lifetime");
+  public static final String SERVICE_GROUP_QUICKLINKS_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "quicklinks");
+  public static final String SERVICE_GROUP_CONTAINERS_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "containers");
+  public static final String SERVICE_GROUP_NUMBER_CONTAINERS_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "number_of_containers");
+  public static final String SERVICE_GROUP_EXPECTED_CONTAINERS_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceGroupInfo", "expected_number_of_containers");
+
+
+  private static Set<String> pkPropertyIds =
+      new HashSet<String>(Arrays.asList(new String[]{
+          SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID,
+          SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID}));
+
+  private static Map<String, String> componentNameMappings = MapUtils.fillMap("/var/lib/ambari-server/resources/componentsMap.dat");
+
+  private static Gson gson = StageUtils.getGson();
+
+  /**
+   * kerberos helper
+   */
+  @Inject
+  private KerberosHelper kerberosHelper;
+
+  private ServiceGroupCacheProvider cacheProvider;
+  private ServiceGroupCache cache;
+
+  // ----- Constructors ----------------------------------------------------
+
+  /**
+   * Create a  new resource provider for the given management controller.
+   *
+   * @param propertyIds           the property ids
+   * @param keyPropertyIds        the key property ids
+   * @param managementController  the management controller
+   */
+  @AssistedInject
+  public ServiceGroupResourceProvider(@Assisted Set<String> propertyIds,
+                                      @Assisted Map<Resource.Type, String> keyPropertyIds,
+                                      @Assisted AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+
+    setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES));
+    setRequiredUpdateAuthorizations(RoleAuthorization.AUTHORIZATIONS_UPDATE_SERVICE);
+    setRequiredGetAuthorizations(RoleAuthorization.AUTHORIZATIONS_VIEW_SERVICE);
+    setRequiredDeleteAuthorizations(EnumSet.of(RoleAuthorization.SERVICE_ADD_DELETE_SERVICES));
+
+    this.cacheProvider = managementController.getServiceGroupCacheProvider();
+    this.cache = cacheProvider.getServiceGroupCache();
+  }
+
+  // ----- ResourceProvider ------------------------------------------------
+
+  @Override
+  protected RequestStatus createResourcesAuthorized(Request request)
+      throws SystemException,
+             UnsupportedPropertyException,
+             ResourceAlreadyExistsException,
+             NoSuchParentResourceException {
+
+    final Set<ServiceGroupRequest> requests = new HashSet<>();
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      requests.add(getRequest(propertyMap));
+    }
+    createResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException, AuthorizationException {
+        createServiceGroups(requests);
+        return null;
+      }
+    });
+    notifyCreate(Resource.Type.ServiceGroup, request);
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<Resource> getResourcesAuthorized(Request request, Predicate predicate) throws
+      SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<ServiceGroupRequest> requests = new HashSet<>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      requests.add(getRequest(propertyMap));
+    }
+
+    Set<ServiceGroupResponse> responses = getResources(new Command<Set<ServiceGroupResponse>>() {
+      @Override
+      public Set<ServiceGroupResponse> invoke() throws AmbariException {
+        return getServiceGroups(requests);
+      }
+    });
+
+    Set<String>   requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources    = new HashSet<Resource>();
+
+    for (ServiceGroupResponse response : responses) {
+      Resource resource = new ResourceImpl(Resource.Type.ServiceGroup);
+      setResourceProperty(resource, SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID,
+          response.getClusterName(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID,
+          response.getServiceGroupName(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_SERVICE_GROUP_DISPLAY_NAME_PROPERTY_ID,
+          response.getServiceGroupDisplayName(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_SERVICE_GROUP_TYPE_PROPERTY_ID,
+          response.getServiceGroupType(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_ASSEMBLY_FILE_PROPERTY_ID,
+          response.getAssemblyFile(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_DESIRED_STATE_PROPERTY_ID,
+          response.getDesiredState(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_CURRENT_STATE_PROPERTY_ID,
+          response.getDesiredState(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_APPLICATION_ID_PROPERTY_ID,
+          response.getApplicationId(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_LIFETIME_PROPERTY_ID,
+          response.getLifetime(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_QUICKLINKS_PROPERTY_ID,
+          response.getQuickLinks(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_CONTAINERS_PROPERTY_ID,
+          response.getContainers(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_NUMBER_CONTAINERS_PROPERTY_ID,
+          response.getNumContainers(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_EXPECTED_CONTAINERS_PROPERTY_ID,
+          response.getExpectedContainers(), requestedIds);
+      resources.add(resource);
+    }
+    return resources;
+  }
+
+  @Override
+  protected RequestStatus updateResourcesAuthorized(final Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    RequestStageContainer requestStages = doUpdateResources(null, request, predicate);
+
+    RequestStatusResponse response = null;
+    if (requestStages != null) {
+      try {
+        requestStages.persist();
+      } catch (AmbariException e) {
+        throw new SystemException(e.getMessage(), e);
+      }
+      response = requestStages.getRequestStatusResponse();
+    }
+    notifyUpdate(Resource.Type.ServiceGroup, request, predicate);
+
+    return getRequestStatus(response);
+  }
+
+  @Override
+  protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<ServiceGroupRequest> requests = new HashSet<>();
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      requests.add(getRequest(propertyMap));
+    }
+    RequestStatusResponse response = modifyResources(new Command<RequestStatusResponse>() {
+      @Override
+      public RequestStatusResponse invoke() throws AmbariException, AuthorizationException {
+        return deleteServiceGroups(requests);
+      }
+    });
+
+    notifyDelete(Resource.Type.ServiceGroup, predicate);
+    return getRequestStatus(response);
+  }
+
+  @Override
+  public Set<String> checkPropertyIds(Set<String> propertyIds) {
+    propertyIds = super.checkPropertyIds(propertyIds);
+
+    if (propertyIds.isEmpty()) {
+      return propertyIds;
+    }
+    Set<String> unsupportedProperties = new HashSet<String>();
+    return unsupportedProperties;
+  }
+
+
+  // ----- AbstractResourceProvider ----------------------------------------
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+  // ----- utility methods -------------------------------------------------
+
+  private RequestStageContainer doUpdateResources(final RequestStageContainer stages, final Request request, Predicate predicate)
+      throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<ServiceGroupRequest> requests = new HashSet<>();
+    RequestStageContainer requestStages = null;
+
+    Iterator<Map<String,Object>> iterator = request.getProperties().iterator();
+    if (iterator.hasNext()) {
+      for (Map<String, Object> propertyMap : getPropertyMaps(iterator.next(), predicate)) {
+        requests.add(getRequest(propertyMap));
+      }
+
+      requestStages = modifyResources(new Command<RequestStageContainer>() {
+        @Override
+        public RequestStageContainer invoke() throws AmbariException, AuthorizationException {
+          return updateServiceGroup(stages, requests, request.getRequestInfoProperties());
+        }
+      });
+    }
+    return requestStages;
+  }
+
+  // Update services based on the given requests.
+  protected synchronized RequestStageContainer updateServiceGroup(RequestStageContainer requestStages,
+      Set<ServiceGroupRequest> requests, Map<String, String> requestProperties) throws AmbariException, AuthorizationException {
+
+    AmbariManagementController controller = getManagementController();
+    if (requests.isEmpty()) {
+      LOG.warn("Received an empty requests set");
+      return null;
+    }
+
+    Map<String, Set<String>> serviceGroupNames = new HashMap<>();
+    Map<State, List<ServiceGroup>> changedServiceGroups = new EnumMap<>(State.class);
+    Set<State> seenNewStates = new HashSet<>();
+    Clusters  clusters = controller.getClusters();
+    Set<String> clusterNames = new HashSet<>();
+
+    for (ServiceGroupRequest request : requests) {
+      if (request.getClusterName() == null
+          || request.getClusterName().isEmpty()
+          || request.getServiceGroupName() == null
+          || request.getServiceGroupName().isEmpty()) {
+        throw new IllegalArgumentException("Invalid arguments, cluster name"
+            + " and service group name should be provided to update service groups");
+      }
+
+      LOG.info("Received a updateServiceGroup request"
+          + ", clusterName=" + request.getClusterName()
+          + ", serviceGroupName=" + request.getServiceGroupName()
+          + ", request=" + request.toString());
+
+      clusterNames.add(request.getClusterName());
+
+      if (clusterNames.size() > 1) {
+        throw new IllegalArgumentException("Updates to multiple clusters is not"
+            + " supported");
+      }
+
+      if (!serviceGroupNames.containsKey(request.getClusterName())) {
+        serviceGroupNames.put(request.getClusterName(), new HashSet<String>());
+      }
+      if (serviceGroupNames.get(request.getClusterName())
+          .contains(request.getServiceGroupName())) {
+        // TODO throw single exception
+        throw new IllegalArgumentException("Invalid request contains duplicate"
+            + " service group names");
+      }
+      serviceGroupNames.get(request.getClusterName()).add(request.getServiceGroupName());
+
+      Cluster cluster = clusters.getCluster(request.getClusterName());
+      ServiceGroup sg = cluster.getServiceGroup(request.getServiceGroupName());
+      State oldState = sg.getCurrentState();
+      State newState = null;
+      if (request.getDesiredState() != null) {
+        newState = State.valueOf(request.getDesiredState());
+        if (!newState.isValidDesiredState()) {
+          throw new IllegalArgumentException("Invalid arguments, invalid"
+              + " desired state, desiredState=" + newState);
+        }
+      }
+
+      boolean persist = false;
+      if(request.getServiceGroupDisplayName() != null && !request.getServiceGroupDisplayName().equals(sg.getServiceGroupDisplayName())) {
+        sg.setServiceGroupDisplayName(request.getServiceGroupDisplayName());
+        persist = true;
+      }
+
+      if(request.getAssemblyFile() != null && !request.getAssemblyFile().equals(sg.getAssemblyFile())) {
+        sg.setAssemblyFile(request.getAssemblyFile());
+        persist = true;
+      }
+
+      if(persist) {
+        sg.persist();
+        LOG.info("Updated properties for service group " + sg.getName());
+      }
+
+      if (newState == null) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Nothing to do for new updateServiceGroup request"
+              + ", clusterName=" + request.getClusterName()
+              + ", serviceName=" + request.getServiceGroupName()
+              + ", newDesiredState=null");
+        }
+        continue;
+      }
+
+      seenNewStates.add(newState);
+      if (newState != oldState) {
+        // The if user is trying to start or stop the service, ensure authorization
+        if (((newState == State.INSTALLED) || (newState == State.STARTED)) &&
+            !AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, cluster.getResourceId(), RoleAuthorization.SERVICE_START_STOP)) {
+          throw new AuthorizationException("The authenticated user is not authorized to start or stop services");
+        }
+
+        if (!State.isValidDesiredStateTransition(oldState, newState)) {
+          throw new AmbariException("Invalid transition for"
+              + " service group"
+              + ", clusterName=" + cluster.getClusterName()
+              + ", clusterId=" + cluster.getClusterId()
+              + ", serviceGroupName=" + sg.getName()
+              + ", currentDesiredState=" + oldState
+              + ", newDesiredState=" + newState);
+        }
+        // TODO: Remove block for stopping service groups
+        if (newState  == State.INSTALLED && oldState == State.STARTED ) {
+          throw new AmbariException("Stopping service groups not supported for"
+              + " service group"
+              + ", clusterName=" + cluster.getClusterName()
+              + ", clusterId=" + cluster.getClusterId()
+              + ", serviceGroupName=" + sg.getName()
+              + ", currentDesiredState=" + oldState
+              + ", newDesiredState=" + newState);
+        }
+        if (!changedServiceGroups.containsKey(newState)) {
+          changedServiceGroups.put(newState, new ArrayList<ServiceGroup>());
+        }
+        changedServiceGroups.get(newState).add(sg);
+      }
+    }
+    if (seenNewStates.size() > 1) {
+      // TODO should we handle this scenario
+      throw new IllegalArgumentException("Cannot handle different desired state"
+          + " changes for a set of services at the same time");
+    }
+    Cluster cluster = clusters.getCluster(clusterNames.iterator().next());
+
+    return controller.addServiceGroupStages(requestStages, cluster, requestProperties,
+        null, changedServiceGroups);
+  }
+
+  /**
+   * Get a service group request object from a map of property values.
+   *
+   * @param properties  the predicate
+   *
+   * @return the service request object
+   */
+  private ServiceGroupRequest getRequest(Map<String, Object> properties) {
+    ServiceGroupRequest svcRequest = new ServiceGroupRequest(
+        (String) properties.get(SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_SERVICE_GROUP_DISPLAY_NAME_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_SERVICE_GROUP_TYPE_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_ASSEMBLY_FILE_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_DESIRED_STATE_PROPERTY_ID),
+        (String) properties.get(SERVICE_GROUP_CURRENT_STATE_PROPERTY_ID));
+    return svcRequest;
+  }
+
+  // Create services from the given request.
+  public synchronized void createServiceGroups(Set<ServiceGroupRequest> requests)
+      throws AmbariException, AuthorizationException {
+
+    if (requests.isEmpty()) {
+      LOG.warn("Received an empty requests set");
+      return;
+    }
+    AmbariManagementController controller = getManagementController();
+    Clusters clusters = controller.getClusters();
+    for(ServiceGroupRequest request: requests) {
+      if(StringUtils.isBlank(request.getServiceGroupDisplayName())) {
+        request.setServiceGroupDisplayName(request.getServiceGroupName());
+      }
+    }
+
+    // do all validation checks
+    validateCreateRequests(requests, clusters);
+
+    ServiceGroupFactory serviceGroupFactory = controller.getServiceGroupFactory();
+    ServiceFactory serviceFactory = getManagementController().getServiceFactory();
+    ServiceComponentFactory serviceComponentFactory = getManagementController().getServiceComponentFactory();
+    for (ServiceGroupRequest request : requests) {
+      Cluster cluster = clusters.getCluster(request.getClusterName());
+
+      State desiredState = State.INIT;
+      State currentState = State.INIT;
+      if (!request.getServiceGroupType().equalsIgnoreCase("AMBARI")) {
+        ServiceGroupCacheKey cacheKey = new ServiceGroupCacheKey(request.getServiceGroupName());
+        ServiceGroupCacheValue cacheValue = null;
+        try {
+          cacheValue = cache.getServiceGroupCacheValue(cacheKey);
+        } catch (Exception e) {
+          LOG.error("Hit exception when retrieving service group from cache. Exception: " + e.getMessage());
+        }
+        if (cacheValue != null) {
+          // YarnApp already deployed (Ambari takeover)
+          desiredState = State.STARTED;
+          currentState = State.STARTED;
+        }
+      }
+      // Already checked that service group does not exist
+      ServiceGroup sg = serviceGroupFactory.createNew(cluster, request.getServiceGroupName(),
+          request.getServiceGroupDisplayName(), request.getServiceGroupType(), request.getAssemblyFile(), desiredState, currentState);
+      sg.persist();
+
+      try {
+        if (!request.getServiceGroupType().equalsIgnoreCase("AMBARI")) {
+          Map<String, Map<String, Integer>> serviceComponentsMap = parseAssemblyFile(request.getAssemblyFile(), cluster);
+          if (serviceComponentsMap != null) {
+            for (Map.Entry<String, Map<String, Integer>> serviceEntry : serviceComponentsMap.entrySet()) {
+              String stackServiceName = serviceEntry.getKey();
+              String serviceName = stackServiceName + "_" + request.getServiceGroupName();
+              LOG.info("Creating service " + serviceName + " in service group " + request.getServiceGroupName());
+              State state = State.INIT;
+              // Already checked that service does not exist
+              Service s = serviceFactory.createNew(
+                  cluster, serviceName, stackServiceName, request.getServiceGroupName());
+              s.setDesiredState(state);
+              s.setDesiredStackVersion(cluster.getDesiredStackVersion());
+              cluster.addService(s);
+              s.persist();
+              LOG.info("Created service " + serviceName + " in service group " + request.getServiceGroupName());
+
+              for (Map.Entry<String, Integer> componentEntry : serviceEntry.getValue().entrySet()) {
+                String componentName = componentEntry.getKey();
+                Integer desiredCount = componentEntry.getValue();
+                LOG.info("Creating service component " + componentName +
+                    " in service " + serviceName +
+                    " with desired count " + desiredCount);
+                ServiceComponent sc = serviceComponentFactory.createNew(s, componentName);
+                sc.setDesiredState(s.getDesiredState());
+                sc.setDesiredCount(desiredCount);
+                s.addServiceComponent(sc);
+                sc.persist();
+                LOG.info("Created service component " + componentName +
+                    " in service " + serviceName +
+                    " with desired count " + desiredCount);
+              }
+            }
+          }
+        }
+      } catch (Exception e) {
+        LOG.error("Failed to create service components for service group " + sg.getName());
+        LOG.error("Ignoring Exception : " + e.getMessage());
+      }
+    }
+  }
+
+  // Get services from the given set of requests.
+  protected Set<ServiceGroupResponse> getServiceGroups(Set<ServiceGroupRequest> requests)
+      throws AmbariException {
+    Set<ServiceGroupResponse> response = new HashSet<ServiceGroupResponse>();
+    for (ServiceGroupRequest request : requests) {
+      try {
+        response.addAll(getServiceGroups(request));
+      } catch (ServiceGroupNotFoundException e) {
+        if (requests.size() == 1) {
+          // only throw exception if 1 request.
+          // there will be > 1 request in case of OR predicate
+          throw e;
+        }
+      }
+    }
+    return response;
+  }
+
+  // Get services from the given request.
+  private Set<ServiceGroupResponse> getServiceGroups(ServiceGroupRequest request)
+      throws AmbariException {
+    if (request.getClusterName() == null
+        || request.getClusterName().isEmpty()) {
+      throw new AmbariException("Invalid arguments, cluster name"
+          + " cannot be null");
+    }
+    AmbariManagementController controller = getManagementController();
+    Clusters clusters    = controller.getClusters();
+    String   clusterName = request.getClusterName();
+
+    final Cluster cluster;
+    try {
+      cluster = clusters.getCluster(clusterName);
+    } catch (ObjectNotFoundException e) {
+      throw new ParentObjectNotFoundException("Parent Cluster resource doesn't exist", e);
+    }
+
+    Set<ServiceGroupResponse> response = new HashSet<>();
+    if (request.getServiceGroupName() != null) {
+      ServiceGroup sg = cluster.getServiceGroup(request.getServiceGroupName());
+      ServiceGroupResponse serviceGroupResponse = sg.convertToResponse();
+      if(sg != null && !sg.getServiceGroupType().equalsIgnoreCase("AMBARI")) {
+        ServiceGroupCacheKey cacheKey = new ServiceGroupCacheKey(sg.getName());
+        ServiceGroupCacheValue cacheValue = null;
+        try {
+          cacheValue = cache.getServiceGroupCacheValue(cacheKey);
+        } catch (Exception e) {
+          LOG.error("Hit exception when retrieving service group from cache. Exception: " + e.getMessage());
+        }
+        if(cacheValue != null) {
+          serviceGroupResponse.setApplicationId(cacheValue.getApplicationId());
+          serviceGroupResponse.setLifetime(cacheValue.getLifetime());
+          serviceGroupResponse.setQuickLinks(cacheValue.getQuicklinks());
+          serviceGroupResponse.setContainers(cacheValue.getContainers());
+          serviceGroupResponse.setNumContainers(cacheValue.getNumContainers());
+          serviceGroupResponse.setExpectedContainers(cacheValue.getExpectedContainers());
+        }
+      }
+      response.add(serviceGroupResponse);
+      return response;
+    }
+
+    for (ServiceGroup sg : cluster.getServiceGroups().values()) {
+      ServiceGroupResponse serviceGroupResponse = sg.convertToResponse();
+      if(!sg.getServiceGroupType().equalsIgnoreCase("AMBARI")) {
+        ServiceGroupCacheKey cacheKey = new ServiceGroupCacheKey(sg.getName());
+        ServiceGroupCacheValue cacheValue = null;
+        try {
+          cacheValue = cache.getServiceGroupCacheValue(cacheKey);
+        } catch (Exception e) {
+          LOG.error("Hit exception when retrieving service group from cache. Exception: " + e.getMessage());
+        }
+        if(cacheValue != null) {
+          serviceGroupResponse.setApplicationId(cacheValue.getApplicationId());
+          serviceGroupResponse.setLifetime(cacheValue.getLifetime());
+          serviceGroupResponse.setQuickLinks(cacheValue.getQuicklinks());
+          serviceGroupResponse.setContainers(cacheValue.getContainers());
+          serviceGroupResponse.setNumContainers(cacheValue.getNumContainers());
+          serviceGroupResponse.setExpectedContainers(cacheValue.getExpectedContainers());
+        }
+      }
+      response.add(serviceGroupResponse);
+    }
+    return response;
+  }
+
+
+  // Delete services based on the given set of requests
+  protected RequestStatusResponse deleteServiceGroups(Set<ServiceGroupRequest> request)
+      throws AmbariException, AuthorizationException {
+
+    Clusters clusters    = getManagementController().getClusters();
+
+    Set<ServiceGroup> removable = new HashSet<>();
+
+    for (ServiceGroupRequest serviceGroupRequest : request) {
+      if (StringUtils.isEmpty(serviceGroupRequest.getClusterName())
+          || StringUtils.isEmpty(serviceGroupRequest.getServiceGroupName())) {
+        // FIXME throw correct error
+        throw new AmbariException("invalid arguments");
+      } else {
+
+        if(!AuthorizationHelper.isAuthorized(
+            ResourceType.CLUSTER, getClusterResourceId(serviceGroupRequest.getClusterName()),
+            RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
+          throw new AuthorizationException("The user is not authorized to delete service groups");
+        }
+
+        ServiceGroup serviceGroup = clusters.getCluster(
+            serviceGroupRequest.getClusterName()).getServiceGroup(
+            serviceGroupRequest.getServiceGroupName());
+
+        // TODO: Add check to validate there are no services in the service group
+        removable.add(serviceGroup);
+      }
+    }
+
+    for (ServiceGroup serviceGroup : removable) {
+      serviceGroup.getCluster().deleteServiceGroup(serviceGroup.getName());
+    }
+
+    return null;
+  }
+
+
+  private void validateCreateRequests(Set<ServiceGroupRequest> requests, Clusters clusters)
+          throws AuthorizationException, AmbariException {
+
+    AmbariMetaInfo ambariMetaInfo = getManagementController().getAmbariMetaInfo();
+    Map<String, Set<String>> serviceGroupNames = new HashMap<>();
+    Set<String> duplicates = new HashSet<>();
+    for (ServiceGroupRequest request : requests) {
+      final String clusterName = request.getClusterName();
+      final String serviceGroupName = request.getServiceGroupName();
+
+      Validate.notEmpty(clusterName, "Cluster name should be provided when creating a service group");
+      Validate.notEmpty(serviceGroupName, "Service group name should be provided when creating a service group");
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Received a createServiceGroup request"
+            + ", clusterName=" + ", serviceGroupName=" + serviceGroupName + ", request=" + request);
+      }
+
+      if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER,
+          getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
+        throw new AuthorizationException("The user is not authorized to create service groups");
+      }
+
+      if (!serviceGroupNames.containsKey(clusterName)) {
+        serviceGroupNames.put(clusterName, new HashSet<String>());
+      }
+
+      if (serviceGroupNames.get(clusterName).contains(serviceGroupName)) {
+        // throw error later for dup
+        duplicates.add(serviceGroupName);
+        continue;
+      }
+      serviceGroupNames.get(clusterName).add(serviceGroupName);
+
+      Cluster cluster;
+      try {
+        cluster = clusters.getCluster(clusterName);
+      } catch (ClusterNotFoundException e) {
+        throw new ParentObjectNotFoundException("Attempted to add a service group to a cluster which doesn't exist", e);
+      }
+      try {
+        ServiceGroup sg = cluster.getServiceGroup(serviceGroupName);
+        if (sg != null) {
+          // throw error later for dup
+          duplicates.add(serviceGroupName);
+          continue;
+        }
+      } catch (ServiceGroupNotFoundException e) {
+        // Expected
+      }
+    }
+    // ensure only a single cluster update
+    if (serviceGroupNames.size() != 1) {
+      throw new IllegalArgumentException("Invalid arguments, updates allowed"
+              + "on only one cluster at a time");
+    }
+
+    // Validate dups
+    if (!duplicates.isEmpty()) {
+      String clusterName = requests.iterator().next().getClusterName();
+      String msg = "Attempted to create a service group which already exists: "
+              + ", clusterName=" + clusterName  + " serviceGroupName=" + StringUtils.join(duplicates, ",");
+
+      throw new DuplicateResourceException(msg);
+    }
+  }
+
+  private Map<String, Map<String, Integer>> parseAssemblyFile(String assemblyFile, Cluster cluster) throws AmbariException {
+    StackId stackId = cluster.getDesiredStackVersion();
+    AmbariMetaInfo ambariMetaInfo = getManagementController().getAmbariMetaInfo();
+    if(StringUtils.isBlank(assemblyFile)) {
+      return null;
+    }
+    Map<String, Map<String, Integer>> serviceComponentsMap = new HashMap<>();
+    Map<String, Object> assemblyMap = gson.fromJson(assemblyFile,
+        new TypeToken<Map<String, Object>>() {
+        }.getType());
+    if(assemblyMap != null && assemblyMap.containsKey("components")) {
+      for (Map<String, Object> componentMap : (ArrayList<Map<String, Object>>) assemblyMap.get("components")) {
+        if(componentMap.containsKey("name")) {
+          String stackComponentName = null;
+          String stackServiceName = null;
+          Integer desiredCount = 0;
+          String assemblyComponentName = (String)componentMap.get("name");
+          for(Map.Entry<String, String> componentEntry : componentNameMappings.entrySet()) {
+            if(componentEntry.getValue().equalsIgnoreCase(assemblyComponentName)) {
+              stackComponentName = componentEntry.getKey();
+              break;
+            }
+          }
+          if(stackComponentName != null) {
+            stackServiceName =
+                ambariMetaInfo.getComponentToService(stackId.getStackName(),
+                    stackId.getStackVersion(), stackComponentName);
+            if(componentMap.containsKey("number_of_containers")) {
+              desiredCount = ((Double)componentMap.get("number_of_containers")).intValue();
+            }
+          }
+          if(stackComponentName != null && stackServiceName != null) {
+            if(!serviceComponentsMap.containsKey(stackServiceName)) {
+              serviceComponentsMap.put(stackServiceName, new HashMap<String, Integer>());
+            }
+            Map<String, Integer> componentsMap = serviceComponentsMap.get(stackServiceName);
+            componentsMap.put(stackComponentName, desiredCount);
+          }
+        }
+      }
+    }
+    return serviceComponentsMap;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
index 56196c1..893cdb2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
@@ -89,6 +89,8 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
   // Services
   public static final String SERVICE_CLUSTER_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceInfo", "cluster_name");
   public static final String SERVICE_SERVICE_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceInfo", "service_name");
+  public static final String SERVICE_STACK_SERVICE_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceInfo", "stack_service_name");
+  public static final String SERVICE_SERVICE_GROUP_NAME_PROPERTY_ID    = PropertyHelper.getPropertyId("ServiceInfo", "service_group_name");
   public static final String SERVICE_SERVICE_STATE_PROPERTY_ID   = PropertyHelper.getPropertyId("ServiceInfo", "state");
   public static final String SERVICE_MAINTENANCE_STATE_PROPERTY_ID = PropertyHelper.getPropertyId("ServiceInfo", "maintenance_state");
 
@@ -109,7 +111,6 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
           SERVICE_CLUSTER_NAME_PROPERTY_ID,
           SERVICE_SERVICE_NAME_PROPERTY_ID}));
 
-
   private MaintenanceStateHelper maintenanceStateHelper;
 
   /**
@@ -192,6 +193,10 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
           response.getClusterName(), requestedIds);
       setResourceProperty(resource, SERVICE_SERVICE_NAME_PROPERTY_ID,
           response.getServiceName(), requestedIds);
+      setResourceProperty(resource, SERVICE_STACK_SERVICE_NAME_PROPERTY_ID,
+          response.getStackServiceName(), requestedIds);
+      setResourceProperty(resource, SERVICE_SERVICE_GROUP_NAME_PROPERTY_ID,
+          response.getServiceGroupName(), requestedIds);
       setResourceProperty(resource, SERVICE_SERVICE_STATE_PROPERTY_ID,
           calculateServiceState(response.getClusterName(), response.getServiceName()),
           requestedIds);
@@ -199,7 +204,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
           response.getMaintenanceState(), requestedIds);
 
       Map<String, Object> serviceSpecificProperties = getServiceSpecificProperties(
-          response.getClusterName(), response.getServiceName(), requestedIds);
+          response.getClusterName(), response.getServiceName(), response.getStackServiceName(), requestedIds);
 
       for (Map.Entry<String, Object> entry : serviceSpecificProperties.entrySet()) {
         setResourceProperty(resource, entry.getKey(), entry.getValue(), requestedIds);
@@ -322,6 +327,8 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
     ServiceRequest svcRequest = new ServiceRequest(
         (String) properties.get(SERVICE_CLUSTER_NAME_PROPERTY_ID),
         (String) properties.get(SERVICE_SERVICE_NAME_PROPERTY_ID),
+        (String) properties.get(SERVICE_STACK_SERVICE_NAME_PROPERTY_ID),
+        (String) properties.get(SERVICE_SERVICE_GROUP_NAME_PROPERTY_ID),
         (String) properties.get(SERVICE_SERVICE_STATE_PROPERTY_ID));
 
     Object o = properties.get(SERVICE_MAINTENANCE_STATE_PROPERTY_ID);
@@ -341,6 +348,14 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
       return;
     }
     Clusters clusters = getManagementController().getClusters();
+    for(ServiceRequest request: requests) {
+      if(StringUtils.isBlank(request.getStackServiceName())) {
+        request.setStackServiceName(request.getServiceName());
+      }
+      if(StringUtils.isBlank(request.getServiceGroupName())) {
+        request.setServiceGroupName("CORE");
+      }
+    }
     // do all validation checks
     validateCreateRequests(requests, clusters);
 
@@ -351,7 +366,8 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
       State state = State.INIT;
 
       // Already checked that service does not exist
-      Service s = serviceFactory.createNew(cluster, request.getServiceName());
+      Service s = serviceFactory.createNew(
+          cluster, request.getServiceName(), request.getStackServiceName(), request.getServiceGroupName());
 
       s.setDesiredState(state);
       s.setDesiredStackVersion(cluster.getDesiredStackVersion());
@@ -848,9 +864,10 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
    * @param serviceName  service name
    * @param requestedIds relevant request property ids
    */
-  private Map<String, Object> getServiceSpecificProperties(String clusterName, String serviceName, Set<String> requestedIds) {
+  private Map<String, Object> getServiceSpecificProperties(String clusterName, String serviceName,
+                                                           String stackServiceName, Set<String> requestedIds) {
     Map<String, Object> serviceSpecificProperties = new HashMap<String, Object>();
-    if (serviceName.equals("KERBEROS")) {
+    if (stackServiceName.equals("KERBEROS")) {
       // Only include details on whether the KDC administrator credentials are set and correct if
       // implicitly (Service/attributes) or explicitly (Service/attributes/kdc_...) queried
       if (requestedIds.contains(SERVICE_ATTRIBUTES_PROPERTY_ID) ||
@@ -895,15 +912,22 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
     for (ServiceRequest request : requests) {
       final String clusterName = request.getClusterName();
       final String serviceName = request.getServiceName();
+      final String stackServiceName = request.getStackServiceName();
+      final String serviceGroupName = request.getServiceGroupName();
+
       Validate.notEmpty(clusterName, "Cluster name should be provided when creating a service");
       Validate.notEmpty(serviceName, "Service name should be provided when creating a service");
+      Validate.notEmpty(stackServiceName, "Stack service name should be provided when creating a service");
+      Validate.notEmpty(serviceGroupName, "Service group name should be provided when creating a service");
 
       if (LOG.isDebugEnabled()) {
         LOG.debug("Received a createService request"
-                + ", clusterName=" + clusterName + ", serviceName=" + serviceName + ", request=" + request);
+            + ", clusterName=" + clusterName + ", serviceName=" + serviceName + ", stackServiceName=" + stackServiceName
+            + ", serviceGroupName=" + serviceGroupName + ", request=" + request);
       }
 
-      if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
+      if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER,
+          getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
         throw new AuthorizationException("The user is not authorized to create services");
       }
 
@@ -946,7 +970,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
 
       StackId stackId = cluster.getDesiredStackVersion();
       if (!ambariMetaInfo.isValidService(stackId.getStackName(),
-              stackId.getStackVersion(), request.getServiceName())) {
+              stackId.getStackVersion(), request.getStackServiceName())) {
         throw new IllegalArgumentException("Unsupported or invalid service in stack, clusterName=" + clusterName
                 + ", serviceName=" + serviceName + ", stackInfo=" + stackId.getStackId());
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
index b8ed215..bdb1087 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java
@@ -352,7 +352,7 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv
     }
 
     if (properties.containsKey(CLUSTER_HANDLE_PROPERTY_ID)) {
-      viewInstanceEntity.setClusterHandle((String) properties.get(CLUSTER_HANDLE_PROPERTY_ID));
+      viewInstanceEntity.setClusterHandle(Long.valueOf((String) properties.get(CLUSTER_HANDLE_PROPERTY_ID)));
     }
 
     if (properties.containsKey(CLUSTER_TYPE_PROPERTY_ID)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCache.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCache.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCache.java
new file mode 100644
index 0000000..84bb32a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCache.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.controller.servicegroup.cache;
+
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.constructs.blocking.LockTimeoutException;
+import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
+import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
+import net.sf.ehcache.statistics.StatisticsGateway;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Map;
+
+public class ServiceGroupCache extends UpdatingSelfPopulatingCache {
+
+  private final static Logger LOG = LoggerFactory.getLogger(ServiceGroupCache.class);
+  private static AtomicInteger printCacheStatsCounter = new AtomicInteger(0);
+
+  /**
+   * Creates a SelfPopulatingCache.
+   *
+   * @param cache @Cache
+   * @param factory @CacheEntryFactory
+   */
+  public ServiceGroupCache(Ehcache cache, UpdatingCacheEntryFactory factory) throws CacheException {
+    super(cache, factory);
+  }
+
+
+
+  public ServiceGroupCacheValue getServiceGroupCacheValue(ServiceGroupCacheKey key)
+      throws IllegalArgumentException, IOException {
+
+    LOG.debug("Fetching service group cache value with key: " + key);
+    // Make sure key is valid
+    validateKey(key);
+
+    Element element = null;
+    try {
+      element = get(key);
+    } catch (LockTimeoutException le) {
+      // Ehcache masks the Socket Timeout to look as a LockTimeout
+      Throwable t = le.getCause();
+      if (t instanceof CacheException) {
+        t = t.getCause();
+        if (t instanceof SocketTimeoutException) {
+          throw new SocketTimeoutException(t.getMessage());
+        }
+      }
+    }
+
+    ServiceGroupCacheValue cacheValue = null;
+    if (element != null && element.getObjectValue() != null) {
+      cacheValue = (ServiceGroupCacheValue) element.getObjectValue();
+      LOG.debug("Returning service group value from cache: " + cacheValue);
+    }
+    return cacheValue;
+  }
+
+  public Map<String, Object> getResponseFromCache(ServiceGroupCacheKey key) throws IllegalArgumentException, IOException {
+    LOG.debug("Fetching service group with key: " + key);
+
+    // Make sure key is valid
+    validateKey(key);
+
+    Element element = null;
+    try {
+      element = get(key);
+    } catch (LockTimeoutException le) {
+      // Ehcache masks the Socket Timeout to look as a LockTimeout
+      Throwable t = le.getCause();
+      if (t instanceof CacheException) {
+        t = t.getCause();
+        if (t instanceof SocketTimeoutException) {
+          throw new SocketTimeoutException(t.getMessage());
+        }
+      }
+    }
+
+    Map<String, Object> response = null;
+    if (element != null && element.getObjectValue() != null) {
+      ServiceGroupCacheValue value = (ServiceGroupCacheValue) element.getObjectValue();
+      LOG.debug("Returning service group value from cache: " + value);
+      response = value.getResponse();
+    }
+    return response;
+  }
+
+
+  private void validateKey(ServiceGroupCacheKey key) throws IllegalArgumentException {
+    StringBuilder msg = new StringBuilder("Invalid service group key requested.");
+    boolean throwException = false;
+
+    if (key.getServiceGroupName() == null) {
+      msg.append(" No service group name provided.");
+      throwException = true;
+    }
+
+    if (throwException) {
+      throw new IllegalArgumentException(msg.toString());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheEntryFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheEntryFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheEntryFactory.java
new file mode 100644
index 0000000..718243c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheEntryFactory.java
@@ -0,0 +1,102 @@
+/**
+ * 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.servicegroup.cache;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
+import org.apache.ambari.server.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import java.util.Map;
+import org.apache.commons.io.IOUtils;
+
+@Singleton
+public class ServiceGroupCacheEntryFactory implements UpdatingCacheEntryFactory {
+  private final static Logger LOG = LoggerFactory.getLogger(ServiceGroupCacheEntryFactory.class);
+  private final String dashEndpoint;
+  private final Gson gson;
+
+  @Inject
+  public ServiceGroupCacheEntryFactory(Configuration configuration) {
+    gson = new Gson();
+    dashEndpoint = configuration.getDashApiEndpoint();
+  }
+
+  @Override
+  public Object createEntry(Object key) throws Exception {
+    LOG.debug("Creating cache entry since none exists, key = " + key);
+    ServiceGroupCacheKey serviceGroupCacheKey = (ServiceGroupCacheKey) key;
+
+    Map<String, Object> response = null;
+    String urlString = dashEndpoint + "/" +  serviceGroupCacheKey.getServiceGroupName().toLowerCase();
+    try {
+      URL url = new URL(urlString);
+      HttpURLConnection httpRequest = (HttpURLConnection) url.openConnection();
+      InputStream inputStream = httpRequest.getInputStream();
+      String jsonResponse = IOUtils.toString(inputStream, "UTF-8");
+      response = gson.fromJson(jsonResponse, new TypeToken<Map<String, Object>>() {
+      }.getType());
+    } catch (Exception e) {
+      LOG.error("Failed to get response from DASH endpoint " + dashEndpoint + "Exception : " + e.getMessage());
+      response = null;
+    }
+    ServiceGroupCacheValue value = null;
+    if (response != null) {
+      value = new ServiceGroupCacheValue(System.currentTimeMillis(), response);
+      LOG.debug("Created cache entry: " + value);
+    }
+    return value;
+  }
+
+  @Override
+  public void updateEntryValue(Object key, Object value) throws Exception {
+    ServiceGroupCacheKey serviceGroupCacheKey = (ServiceGroupCacheKey) key;
+    ServiceGroupCacheValue existingValue = (ServiceGroupCacheValue) value;
+    long fetchTime = existingValue.getFetchTime();
+    if(System.currentTimeMillis() - fetchTime > 60000) {
+      LOG.debug("Updating service group cache entry for key = " + key);
+      Map<String, Object> response = null;
+      String urlString = dashEndpoint + "/" +  serviceGroupCacheKey.getServiceGroupName().toLowerCase();
+      try {
+        URL url = new URL(urlString);
+        HttpURLConnection httpRequest = (HttpURLConnection) url.openConnection();
+        InputStream inputStream = httpRequest.getInputStream();
+        String jsonResponse = IOUtils.toString(inputStream, "UTF-8");
+        response = gson.fromJson(jsonResponse, new TypeToken<Map<String, Object>>() {
+        }.getType());
+      } catch (Exception e) {
+        LOG.error("Failed to get response from Dash endpoint " + dashEndpoint + "Exception : " + e.getMessage());
+        response = null;
+      }
+      if (response != null) {
+        existingValue.setFetchTime(System.currentTimeMillis());
+        existingValue.setResponse(response);
+        LOG.debug("Updated cache entry for key = " + key + ", newValue = " + value);
+      }
+    } else {
+      LOG.debug("Skipping updating service group cache entry for key = " + key);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheKey.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheKey.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheKey.java
new file mode 100644
index 0000000..b45e870
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheKey.java
@@ -0,0 +1,55 @@
+/**
+ * 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.servicegroup.cache;
+
+public class ServiceGroupCacheKey {
+  private String serviceGroupName;
+
+  public ServiceGroupCacheKey(String serviceGroupName) {
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  public String getServiceGroupName() {
+    return serviceGroupName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ServiceGroupCacheKey that = (ServiceGroupCacheKey) o;
+
+    if (!serviceGroupName.equals(that.serviceGroupName))
+      return false;
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = serviceGroupName.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "ServiceGroupCacheKey { " +
+      "serviceGroupName=" + serviceGroupName +
+      " }";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheProvider.java
new file mode 100644
index 0000000..2571216
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheProvider.java
@@ -0,0 +1,114 @@
+/**
+ * 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.servicegroup.cache;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.PersistenceConfiguration;
+import net.sf.ehcache.config.SizeOfPolicyConfiguration;
+import net.sf.ehcache.config.SizeOfPolicyConfiguration.MaxDepthExceededBehavior;
+import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
+import org.apache.ambari.server.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static net.sf.ehcache.config.PersistenceConfiguration.Strategy;
+
+@Singleton
+public class ServiceGroupCacheProvider {
+  private ServiceGroupCache serviceGroupCache;
+  private volatile boolean isCacheInitialized = false;
+  public static final String SERVICE_GROUP_CACHE_MANAGER_NAME = "ServiceGroupCacheManager";
+  public static final String SERVICE_GROUP_CACHE_INSTANCE_NAME = "ServiceGroupCache";
+
+  Configuration configuration;
+  ServiceGroupCacheEntryFactory cacheEntryFactory;
+  private final static Logger LOG = LoggerFactory.getLogger(ServiceGroupCacheProvider.class);
+
+  @Inject
+  public ServiceGroupCacheProvider(Configuration configuration,
+                                     ServiceGroupCacheEntryFactory cacheEntryFactory) {
+    this.configuration = configuration;
+    this.cacheEntryFactory = cacheEntryFactory;
+  }
+
+  private synchronized void initializeCache() {
+    // Check in case of contention to avoid ObjectExistsException
+    if (isCacheInitialized) {
+      return;
+    }
+
+    System.setProperty("net.sf.ehcache.skipUpdateCheck", "true");
+
+    net.sf.ehcache.config.Configuration managerConfig =
+      new net.sf.ehcache.config.Configuration();
+    managerConfig.setName(SERVICE_GROUP_CACHE_MANAGER_NAME);
+
+    // Set max heap available to the cache manager
+    managerConfig.setMaxBytesLocalHeap("10%");
+
+    //Create a singleton CacheManager using defaults
+    CacheManager manager = CacheManager.create(managerConfig);
+
+    // TODO: Add service group cache timeout configs
+    LOG.info("Creating service group cache with timeouts => ttl = " +
+      configuration.getMetricCacheTTLSeconds() + ", idle = " +
+      configuration.getMetricCacheIdleSeconds());
+
+    // Create a Cache specifying its configuration.
+    CacheConfiguration cacheConfiguration = createCacheConfiguration();
+    Cache cache = new Cache(cacheConfiguration);
+
+    // Decorate with UpdatingSelfPopulatingCache
+    serviceGroupCache = new ServiceGroupCache(cache, cacheEntryFactory);
+
+    LOG.info("Registering service group cache with provider: name = " +
+      cache.getName() + ", guid: " + cache.getGuid());
+    manager.addCache(serviceGroupCache);
+    isCacheInitialized = true;
+  }
+
+  // Having this as a separate public method for testing/mocking purposes
+  public CacheConfiguration createCacheConfiguration() {
+
+    CacheConfiguration cacheConfiguration = new CacheConfiguration()
+      .name(SERVICE_GROUP_CACHE_INSTANCE_NAME)
+      .timeToLiveSeconds(configuration.getMetricCacheTTLSeconds()) // 1 hour
+      .timeToIdleSeconds(configuration.getMetricCacheIdleSeconds()) // 5 minutes
+      .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
+      .sizeOfPolicy(new SizeOfPolicyConfiguration() // Set sizeOf policy to continue on max depth reached - avoid OOM
+        .maxDepth(10000)
+        .maxDepthExceededBehavior(MaxDepthExceededBehavior.CONTINUE))
+      .eternal(false)
+      .persistence(new PersistenceConfiguration()
+        .strategy(Strategy.NONE.name()));
+
+    return cacheConfiguration;
+  }
+
+  public ServiceGroupCache getServiceGroupCache() {
+    if (!isCacheInitialized) {
+      initializeCache();
+    }
+    return serviceGroupCache;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheValue.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheValue.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheValue.java
new file mode 100644
index 0000000..690c47a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/servicegroup/cache/ServiceGroupCacheValue.java
@@ -0,0 +1,135 @@
+/**
+ * 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.servicegroup.cache;
+
+
+import java.util.Map;
+import java.util.ArrayList;
+
+/**
+ * Wrapper object for service group cache value.
+ */
+public class ServiceGroupCacheValue {
+  private long fetchTime;
+  private Map<String, Object> response = null;
+  private String applicationId = null;
+  private String state;
+  private String lifetime = null;
+  private Map<String, String> quicklinks = null;
+  private Integer numContainers;
+  private Integer expectedContainers;
+  public ArrayList<Map<String, Object>> containers = null;
+
+
+  public ServiceGroupCacheValue(long fetchTime, Map<String, Object>  response) {
+    this.fetchTime = fetchTime;
+    this.response = response;
+    parseResponse();
+  }
+
+  public Map<String, Object> getResponse() {
+    return response;
+  }
+
+  public void setResponse(Map<String, Object> response) {
+    this.response = response;
+    parseResponse();
+  }
+
+  public Long getFetchTime() {
+    return fetchTime;
+  }
+
+  public void setFetchTime(Long fetchTime) {
+    this.fetchTime = fetchTime;
+  }
+
+  public String getApplicationId() {
+    return applicationId;
+  }
+
+  public String getState() {
+    return state;
+  }
+
+  public Map<String, String> getQuicklinks() {
+    return quicklinks;
+  }
+
+  public Integer getNumContainers() {
+    return numContainers;
+  }
+
+  public Integer getExpectedContainers() {
+    return expectedContainers;
+  }
+
+  public String getLifetime() {
+    return lifetime;
+  }
+
+  public ArrayList<Map<String, Object>> getContainers() {
+    return containers;
+  }
+
+  public void parseResponse() {
+    if(response != null) {
+      if(response.containsKey("id")) {
+        applicationId = (String) response.get("id");
+      }
+
+      if(response.containsKey("quicklinks")) {
+        quicklinks = (Map<String, String>) response.get("quicklinks");
+      }
+
+      if(response.containsKey("state")) {
+        state = (String) response.get("state");
+      }
+
+      if(response.containsKey("lifetime")) {
+        lifetime = (String) response.get("lifetime");
+      }
+
+      if(response.containsKey("number_of_containers")) {
+        numContainers = ((Double) response.get("number_of_containers")).intValue();
+      }
+
+      if(response.containsKey("expected_number_of_containers")) {
+        expectedContainers = ((Double) response.get("expected_number_of_containers")).intValue();
+      }
+
+      if(response.containsKey("containers")) {
+        containers = (ArrayList<Map<String, Object>>) response.get("containers");
+
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "ServiceGroupCacheValue { " +
+        "fetchTime=" + fetchTime +
+        ", applicationId=" + applicationId +
+        ", state=" + state +
+        ", quicklinks=" + quicklinks +
+        ", lifetime=" + lifetime +
+        ", numContainers=" + numContainers +
+        ", expectedContainers=" + expectedContainers +
+        " }";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/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 99e4ccd..7e6fb09 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
@@ -76,6 +76,7 @@ public interface Resource {
   enum InternalType {
     Cluster,
     Service,
+    ServiceGroup,
     Setting,
     Host,
     Component,
@@ -194,6 +195,7 @@ public interface Resource {
      */
     public static final Type Cluster = InternalType.Cluster.getType();
     public static final Type Service = InternalType.Service.getType();
+    public static final Type ServiceGroup = InternalType.ServiceGroup.getType();
     public static final Type Setting = InternalType.Setting.getType();
     public static final Type Host = InternalType.Host.getType();
     public static final Type Component = InternalType.Component.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/DatabaseChecker.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/DatabaseChecker.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/DatabaseChecker.java
index d35fc1a..436258f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/DatabaseChecker.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/DatabaseChecker.java
@@ -88,8 +88,8 @@ public class DatabaseChecker {
         if (serviceDesiredStateEntity == null) {
           checkPassed = false;
           LOG.error(String.format("ServiceDesiredStateEntity is null for " +
-              "ServiceComponentDesiredStateEntity, clusterName=%s, serviceName=%s ",
-            clusterEntity.getClusterName(), clusterServiceEntity.getServiceName()));
+              "ServiceComponentDesiredStateEntity, clusterName=%s, serviceName=%s, stackServiceName=%s ",
+            clusterEntity.getClusterName(), clusterServiceEntity.getServiceName(), clusterServiceEntity.getStackServiceName()));
         }
         Collection<ServiceComponentDesiredStateEntity> scDesiredStateEntities =
           clusterServiceEntity.getServiceComponentDesiredStateEntities();
@@ -97,8 +97,8 @@ public class DatabaseChecker {
           scDesiredStateEntities.isEmpty()) {
           checkPassed = false;
           LOG.error(String.format("serviceComponentDesiredStateEntities is null or empty for " +
-              "ServiceComponentDesiredStateEntity, clusterName=%s, serviceName=%s ",
-            clusterEntity.getClusterName(), clusterServiceEntity.getServiceName()));
+              "ServiceComponentDesiredStateEntity, clusterName=%s, serviceName=%s, stackServiceName=%s ",
+            clusterEntity.getClusterName(), clusterServiceEntity.getServiceName(), clusterServiceEntity.getStackServiceName()));
         } else {
           for (ServiceComponentDesiredStateEntity scDesiredStateEnity : scDesiredStateEntities) {
 
@@ -227,22 +227,24 @@ public class DatabaseChecker {
             for (ClusterServiceEntity clusterServiceEntity : clusterServiceEntities) {
               if (!State.INIT.equals(clusterServiceEntity.getServiceDesiredStateEntity().getDesiredState())) {
                 String serviceName = clusterServiceEntity.getServiceName();
-                ServiceInfo serviceInfo = ambariMetaInfo.getService(stack.getName(), stack.getVersion(), serviceName);
+                String stackServiceName = clusterServiceEntity.getStackServiceName();
+                String serviceGroupName = clusterServiceEntity.getServiceGroupName();
+                ServiceInfo serviceInfo = ambariMetaInfo.getService(stack.getName(), stack.getVersion(), stackServiceName);
                 for (String configTypeName : serviceInfo.getConfigTypeAttributes().keySet()) {
                   if (selectedCountForType.get(configTypeName) == null) {
                     checkPassed = false;
-                    LOG.error("ClusterConfigMapping does not contain mapping for service=" + serviceName + " type_name="
-                        + configTypeName);
+                    LOG.error("ClusterConfigMapping does not contain mapping for serviceName=" + serviceName
+                        + " stackServiceName=" + stackServiceName +  " serviceGroupName=" + serviceGroupName + " type_name=" + configTypeName);
                   } else {
                     // Check that for each config type exactly one is selected
                     if (selectedCountForType.get(configTypeName) == 0) {
                       checkPassed = false;
-                      LOG.error("ClusterConfigMapping selected count is 0 for service=" + serviceName + " type_name="
-                          + configTypeName);
+                      LOG.error("ClusterConfigMapping selected count is 0 for serviceName=" + serviceName
+                          + " stackServiceName=" + stackServiceName +  " serviceGroupName=" + serviceGroupName + " type_name=" + configTypeName);
                     } else if (selectedCountForType.get(configTypeName) > 1) {
                       checkPassed = false;
-                      LOG.error("ClusterConfigMapping selected count is more than 1 for service=" + serviceName
-                          + " type_name=" + configTypeName);
+                      LOG.error("ClusterConfigMapping selected count is more than 1 for serviceName=" + serviceName
+                          + " stackServiceName=" + stackServiceName +  " serviceGroupName=" + serviceGroupName + " type_name="+ configTypeName);
                     }
                   }
                 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java
index 1079806..d91da98 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java
@@ -38,6 +38,16 @@ public abstract class AmbariEvent {
     SERVICE_REMOVED_SUCCESS,
 
     /**
+     * A service group was successfully installed.
+     */
+    SERVICE_GROUP_INSTALL_SUCCESS,
+
+    /**
+     * A service group was successfully removed.
+     */
+    SERVICE_GROUP_REMOVED_SUCCESS,
+
+    /**
      * A service component was successfully installed.
      */
     SERVICE_COMPONENT_INSTALL_SUCCESS,

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentInstalledEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentInstalledEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentInstalledEvent.java
index 3ad1492..7e60980 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentInstalledEvent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentInstalledEvent.java
@@ -37,11 +37,11 @@ public class ServiceComponentInstalledEvent extends ServiceEvent {
    * @param hostName
    */
   public ServiceComponentInstalledEvent(long clusterId, String stackName,
-      String stackVersion, String serviceName, String componentName,
+      String stackVersion, String serviceName, String stackServiceName, String serviceGroupName, String componentName,
       String hostName, boolean recoveryEnabled) {
     super(AmbariEventType.SERVICE_COMPONENT_INSTALL_SUCCESS, clusterId,
         stackName,
-        stackVersion, serviceName);
+        stackVersion, serviceName, stackServiceName, serviceGroupName);
 
     m_componentName = componentName;
     m_hostName = hostName;

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
index 6f3fa95..822d325 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
@@ -33,15 +33,16 @@ public class ServiceComponentUninstalledEvent extends ServiceEvent {
    * @param stackName
    * @param stackVersion
    * @param serviceName
+   * @param serviceGroupName
    * @param componentName
    * @param hostName
    */
   public ServiceComponentUninstalledEvent(long clusterId, String stackName,
-      String stackVersion, String serviceName, String componentName,
+      String stackVersion, String serviceName, String stackServiceName, String serviceGroupName, String componentName,
       String hostName, boolean recoveryEnabled) {
     super(AmbariEventType.SERVICE_COMPONENT_UNINSTALLED_SUCCESS, clusterId,
         stackName,
-        stackVersion, serviceName);
+        stackVersion, serviceName, stackServiceName, serviceGroupName);
 
     m_componentName = componentName;
     m_hostName = hostName;

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceEvent.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceEvent.java
index 3bc5c17..837e299 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceEvent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceEvent.java
@@ -29,6 +29,16 @@ public abstract class ServiceEvent extends ClusterEvent {
   protected final String m_serviceName;
 
   /**
+   * The name of the real service.
+   */
+  protected final String m_stackServiceName;
+
+  /**
+   * The name of the service group.
+   */
+  protected final String m_serviceGroupName;
+
+  /**
    * The name of the services' stack.
    */
   protected final String m_stackName;
@@ -44,12 +54,14 @@ public abstract class ServiceEvent extends ClusterEvent {
    * @param eventType
    * @param clusterId
    */
-  public ServiceEvent(AmbariEventType eventType, long clusterId,
-      String stackName, String stackVersion, String serviceName) {
+  public ServiceEvent(AmbariEventType eventType, long clusterId, String stackName, String stackVersion,
+                      String serviceName, String stackServiceName, String serviceGroupName) {
     super(eventType, clusterId);
     m_stackName = stackName;
     m_stackVersion = stackVersion;
     m_serviceName = serviceName;
+    m_stackServiceName = stackServiceName;
+    m_serviceGroupName = serviceGroupName;
   }
 
   /**
@@ -60,6 +72,20 @@ public abstract class ServiceEvent extends ClusterEvent {
   }
 
   /**
+   * @return the stackServiceName (never {@code null}).
+   */
+  public String getStackServiceName() {
+    return m_stackServiceName;
+  }
+
+  /**
+   * @return the service group name (never {@code null}).
+   */
+  public String getServiceGroupName() {
+    return m_serviceGroupName;
+  }
+
+  /**
    * @return the stackName (never {@code null}).
    */
   public String getStackName() {