AMBARI-21594. MultiEverything : Add Servicegroup as a subresource of Cluster.


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

Branch: refs/heads/branch-feature-AMBARI-14714
Commit: 2f1f96ef5b705172775d15925d24a97b14960212
Parents: e8c0954
Author: Swapan Shridhar <[email protected]>
Authored: Fri Jul 28 16:15:24 2017 -0700
Committer: Swapan Shridhar <[email protected]>
Committed: Wed Aug 2 01:29:22 2017 -0700

----------------------------------------------------------------------
 .../server/ServiceGroupNotFoundException.java   |  35 ++
 .../resources/ClusterResourceDefinition.java    |   1 +
 .../resources/ResourceInstanceFactoryImpl.java  |   4 +
 .../ServiceGroupResourceDefinition.java         |  54 +++
 .../server/api/services/ClusterService.java     |  12 +
 .../api/services/ServiceGroupService.java       | 283 ++++++++++++
 .../controller/AmbariManagementController.java  |   8 +
 .../AmbariManagementControllerImpl.java         |   9 +-
 .../server/controller/ControllerModule.java     |   7 +
 .../controller/ResourceProviderFactory.java     |   3 +
 .../server/controller/ServiceGroupRequest.java  |  65 +++
 .../server/controller/ServiceGroupResponse.java | 124 +++++
 .../AbstractControllerResourceProvider.java     |   2 +
 .../internal/DefaultProviderModule.java         |   2 +
 .../internal/ServiceGroupResourceProvider.java  | 456 +++++++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   2 +
 .../ambari/server/events/AmbariEvent.java       |  10 +
 .../ambari/server/events/ServiceGroupEvent.java |  49 ++
 .../events/ServiceGroupInstalledEvent.java      |  46 ++
 .../server/events/ServiceGroupRemovedEvent.java |  46 ++
 .../ambari/server/orm/dao/ServiceGroupDAO.java  |  93 ++++
 .../server/orm/entities/ClusterEntity.java      |  61 ++-
 .../server/orm/entities/ServiceGroupEntity.java | 123 +++++
 .../orm/entities/ServiceGroupEntityPK.java      |  71 +++
 .../servicegroup/ServiceGroupServerAction.java  |  92 ++++
 .../org/apache/ambari/server/state/Cluster.java | 239 ++++++----
 .../ambari/server/state/ServiceGroup.java       |  50 ++
 .../server/state/ServiceGroupFactory.java       |  31 ++
 .../ambari/server/state/ServiceGroupImpl.java   | 213 +++++++++
 .../server/state/cluster/ClusterImpl.java       | 413 ++++++++++-------
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |   9 +
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |   8 +
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |   8 +
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |   8 +
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |   8 +
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |   8 +
 .../src/main/resources/META-INF/persistence.xml |   1 +
 .../ambari/server/agent/AgentResourceTest.java  |   5 +
 .../ClusterResourceDefinitionTest.java          |   3 +-
 .../ActiveWidgetLayoutResourceProviderTest.java |   2 +
 .../UserAuthorizationResourceProviderTest.java  |   2 +
 .../internal/UserResourceProviderTest.java      |   2 +
 42 files changed, 2403 insertions(+), 265 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/ServiceGroupNotFoundException.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/ServiceGroupNotFoundException.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/ServiceGroupNotFoundException.java
new file mode 100644
index 0000000..39378ef
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/ServiceGroupNotFoundException.java
@@ -0,0 +1,35 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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;
+
+@SuppressWarnings("serial")
+public class ServiceGroupNotFoundException extends ObjectNotFoundException {
+
+  public ServiceGroupNotFoundException(String clusterName, String 
serviceGroupName) {
+    super("ServiceGroup not found"
+      + ", clusterName=" + clusterName
+      + ", serviceGroupName=" + serviceGroupName);
+  }
+
+  public ServiceGroupNotFoundException(String clusterName, Long 
serviceGroupId) {
+    super("ServiceGroup not found"
+      + ", clusterName=" + clusterName
+      + ", serviceGroupId=" + serviceGroupId);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
index f689841..24c2810 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
@@ -62,6 +62,7 @@ public class ClusterResourceDefinition extends 
BaseResourceDefinition {
   public Set<SubResourceDefinition> getSubResourceDefinitions() {
     Set<SubResourceDefinition> setChildren = new HashSet<>();
     setChildren.add(new SubResourceDefinition(Resource.Type.Service));
+    setChildren.add(new SubResourceDefinition(Resource.Type.ServiceGroup));
     setChildren.add(new SubResourceDefinition(Resource.Type.Host));
     setChildren.add(new SubResourceDefinition(Resource.Type.Configuration));
     setChildren.add(new 
SubResourceDefinition(Resource.Type.ServiceConfigVersion));

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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 73963df..f43c5d9 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
@@ -104,6 +104,10 @@ public class ResourceInstanceFactoryImpl implements 
ResourceInstanceFactory {
         resourceDefinition = new ClusterResourceDefinition();
         break;
 
+      case ServiceGroup:
+        resourceDefinition = new ServiceGroupResourceDefinition();
+        break;
+
       case Service:
         resourceDefinition = new ServiceResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ServiceGroupResourceDefinition.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ServiceGroupResourceDefinition.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ServiceGroupResourceDefinition.java
new file mode 100644
index 0000000..f686851
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ServiceGroupResourceDefinition.java
@@ -0,0 +1,54 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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;
+
+/**
+ * Service group resource definition.
+ */
+public class ServiceGroupResourceDefinition extends BaseResourceDefinition {
+
+  /**
+   * Constructor.
+   *
+   */
+  public ServiceGroupResourceDefinition() {
+    super(Resource.Type.ServiceGroup);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "servicegroups";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "servicegroup";
+  }
+
+  /* TODO: To be called when Services become sub-resource of ServiceGroup.
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+    Set<SubResourceDefinition> subs = new HashSet<SubResourceDefinition>();
+    subs.add(new SubResourceDefinition(Resource.Type.Service));
+    return subs;
+  }
+  */
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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 44d50731..c38489d 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
@@ -510,6 +510,18 @@ public class ClusterService extends BaseService {
   }
 
   /**
+   * Get the servicegroups sub-resource
+   *
+   * @param request     the request
+   * @param clusterName cluster Name
+   * @return the service groups service
+   */
+  @Path("{clusterName}/servicegroups")
+  public ServiceGroupService getServiceGroupHandler(@Context 
javax.ws.rs.core.Request request, @ApiParam @PathParam("clusterName") String 
clusterName) {
+    return new ServiceGroupService(clusterName);
+  }
+
+  /**
    * Gets the configurations sub-resource.
    *
    * @param request      the request

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceGroupService.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceGroupService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceGroupService.java
new file mode 100644
index 0000000..aa1270a
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceGroupService.java
@@ -0,0 +1,283 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.PUT;
+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.MediaType;
+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.ServiceGroupResponse;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.http.HttpStatus;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+
+
+/**
+ * Service responsible for servicegroups resource requests.
+ */
+@Api(value = "Service Groups", description = "Endpoint for servicegroup 
specific operations")
+public class ServiceGroupService extends BaseService {
+  private static final String SERVICE_GROUP_REQUEST_TYPE = 
"org.apache.ambari.server.controller.ServiceGroupRequestSwagger";
+
+  /**
+   * Parent cluster Name.
+   */
+  private String m_clusterName;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterName cluster Name
+   */
+  public ServiceGroupService(String clusterName) {
+    super();
+    m_clusterName = clusterName;
+  }
+
+  /**
+   * Handles: POST /clusters/{clusterName}/servicegroups
+   * Create multiple servicegroups.
+   *
+   * @param body        http body
+   * @param headers     http headers
+   * @param ui          uri info
+   * @return information regarding the created servicegroups
+   */
+  @POST
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Creates a servicegroup",
+    nickname = "ServiceGroupService#createServiceGroups"
+  )
+  @ApiImplicitParams({
+    @ApiImplicitParam(dataType = SERVICE_GROUP_REQUEST_TYPE, paramType = 
PARAM_TYPE_BODY)
+  })
+  @ApiResponses({
+    @ApiResponse(code = HttpStatus.SC_CREATED, message = 
MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_ACCEPTED, message = 
MSG_REQUEST_ACCEPTED),
+    @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = 
MSG_INVALID_ARGUMENTS),
+    @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = 
MSG_RESOURCE_NOT_FOUND),
+    @ApiResponse(code = HttpStatus.SC_CONFLICT, message = 
MSG_RESOURCE_ALREADY_EXISTS),
+    @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = 
MSG_NOT_AUTHENTICATED),
+    @ApiResponse(code = HttpStatus.SC_FORBIDDEN, message = 
MSG_PERMISSION_DENIED),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR),
+  })
+  public Response createServiceGroups(String body, @Context HttpHeaders 
headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+      createServiceGroupResource(m_clusterName, null));
+  }
+
+  /**
+   * Handles URL: /clusters/{clusterName}/servicegroups
+   * Get all servicegroups for a cluster.
+   *
+   * @param headers http headers
+   * @param ui      uri info
+   * @return service collection resource representation
+   */
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Get all servicegroups",
+    nickname = "ServiceGroupService#getServiceGroups",
+    notes = "Returns all servicegroups.",
+    response = ServiceGroupResponse.ServiceGroupResponseSwagger.class,
+    responseContainer = RESPONSE_CONTAINER_LIST)
+  @ApiImplicitParams({
+    @ApiImplicitParam(name = QUERY_FIELDS, value = QUERY_FILTER_DESCRIPTION,
+      defaultValue = "ServiceGroupInfo/service_group_name, 
ServiceGroupInfo/cluster_name",
+      dataType = DATA_TYPE_STRING, paramType = PARAM_TYPE_QUERY),
+    @ApiImplicitParam(name = QUERY_SORT, value = QUERY_SORT_DESCRIPTION,
+      defaultValue = "ServiceGroupInfo/service_group_name.asc, 
ServiceGroupInfo/cluster_name.asc",
+      dataType = DATA_TYPE_STRING, paramType = PARAM_TYPE_QUERY),
+    @ApiImplicitParam(name = QUERY_PAGE_SIZE, value = 
QUERY_PAGE_SIZE_DESCRIPTION, defaultValue = DEFAULT_PAGE_SIZE, dataType = 
DATA_TYPE_INT, paramType = PARAM_TYPE_QUERY),
+    @ApiImplicitParam(name = QUERY_FROM, value = QUERY_FROM_DESCRIPTION, 
defaultValue = DEFAULT_FROM, dataType = DATA_TYPE_STRING, paramType = 
PARAM_TYPE_QUERY),
+    @ApiImplicitParam(name = QUERY_TO, value = QUERY_TO_DESCRIPTION, dataType 
= DATA_TYPE_STRING, paramType = PARAM_TYPE_QUERY)
+  })
+  @ApiResponses(value = {
+    @ApiResponse(code = HttpStatus.SC_OK, message = MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR)
+  })
+  public Response getServiceGroups(String body, @Context HttpHeaders headers, 
@Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.GET,
+      createServiceGroupResource(m_clusterName, null));
+  }
+
+  /**
+   * Handles URL: /clusters/{clusterName}/servicegroups/{serviceGroupName}
+   * Get a specific servicegroup.
+   *
+   * @param headers           http headers
+   * @param ui                uri info
+   * @param serviceGroupName  service group name
+   * @return servicegroup    resource representation
+   */
+  @GET
+  @Path("{serviceGroupName}")
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Get the details of a servicegroup",
+    nickname = "ServiceGroupService#getServiceGroup",
+    notes = "Returns the details of a servicegroup",
+    response = ServiceGroupResponse.ServiceGroupResponseSwagger.class,
+    responseContainer = RESPONSE_CONTAINER_LIST)
+  @ApiImplicitParams({
+    @ApiImplicitParam(name = QUERY_FIELDS, value = QUERY_FILTER_DESCRIPTION, 
defaultValue = "ServiceGroupInfo/*",
+      dataType = DATA_TYPE_STRING, paramType = PARAM_TYPE_QUERY)
+  })
+  @ApiResponses(value = {
+    @ApiResponse(code = HttpStatus.SC_OK, message = MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = 
MSG_RESOURCE_NOT_FOUND),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR)
+  })
+  public Response getServiceGroup(String body, @Context HttpHeaders headers, 
@Context UriInfo ui,
+                                  @PathParam("serviceGroupName") String 
serviceGroupName) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+      createServiceGroupResource(m_clusterName, serviceGroupName));
+  }
+
+  /**
+   * Handles: PUT /clusters/{clusterName}/servicegroups
+   * Update multiple servicegroups.
+   *
+   * @param body        http body
+   * @param headers     http headers
+   * @param ui          uri info
+   * @return information regarding the updated servicegroups
+   */
+  @PUT
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Updates multiple servicegroups",
+    nickname = "ServiceGroupService#updateServiceGroups"
+  )
+  @ApiImplicitParams({
+    @ApiImplicitParam(dataType = SERVICE_GROUP_REQUEST_TYPE, paramType = 
PARAM_TYPE_BODY)
+  })
+  @ApiResponses({
+    @ApiResponse(code = HttpStatus.SC_OK, message = MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_ACCEPTED, message = 
MSG_REQUEST_ACCEPTED),
+    @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = 
MSG_INVALID_ARGUMENTS),
+    @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = 
MSG_RESOURCE_NOT_FOUND),
+    @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = 
MSG_NOT_AUTHENTICATED),
+    @ApiResponse(code = HttpStatus.SC_FORBIDDEN, message = 
MSG_PERMISSION_DENIED),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR),
+  })
+  public Response updateServiceGroups(String body, @Context HttpHeaders 
headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.PUT, 
createServiceGroupResource(m_clusterName, null));
+  }
+
+  /**
+   * Handles: PUT /clusters/{clusterName}/servicegroups/{serviceGroupName}
+   * Update a specific servicegroup.
+   *
+   * @param body                http body
+   * @param headers             http headers
+   * @param ui                  uri info
+   * @param serviceGroupName    service group name
+   * @return information regarding the updated servicegroup
+   */
+  @PUT
+  @Path("{serviceGroupName}")
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Updates a servicegroup",
+    nickname = "ServiceGroupService#updateServiceGroup"
+  )
+  @ApiImplicitParams({
+    @ApiImplicitParam(dataType = SERVICE_GROUP_REQUEST_TYPE, paramType = 
PARAM_TYPE_BODY)
+  })
+  @ApiResponses({
+    @ApiResponse(code = HttpStatus.SC_OK, message = MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_ACCEPTED, message = 
MSG_REQUEST_ACCEPTED),
+    @ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = 
MSG_INVALID_ARGUMENTS),
+    @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = 
MSG_RESOURCE_NOT_FOUND),
+    @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = 
MSG_NOT_AUTHENTICATED),
+    @ApiResponse(code = HttpStatus.SC_FORBIDDEN, message = 
MSG_PERMISSION_DENIED),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR),
+  })
+  public Response updateServiceGroup(String body, @Context HttpHeaders 
headers, @Context UriInfo ui,
+                                     @PathParam("serviceGroupName") String 
serviceGroupName) {
+
+    return handleRequest(headers, body, ui, Request.Type.PUT, 
createServiceGroupResource(m_clusterName, serviceGroupName));
+  }
+
+  /**
+   * Handles: DELETE /clusters/{clusterName}/servicegroups/{serviceGroupName}
+   * Delete a specific servicegroup.
+
+   * @param headers           http headers
+   * @param ui                uri info
+   * @param serviceGroupName  service group name
+   * @return information regarding the deleted servicegroup
+   */
+  @DELETE
+  @Path("{serviceGroupName}")
+  @Produces(MediaType.TEXT_PLAIN)
+  @ApiOperation(value = "Deletes a servicegroup",
+    nickname = "ServiceGroupService#deleteServiceGroup"
+  )
+  @ApiResponses({
+    @ApiResponse(code = HttpStatus.SC_OK, message = MSG_SUCCESSFUL_OPERATION),
+    @ApiResponse(code = HttpStatus.SC_NOT_FOUND, message = 
MSG_RESOURCE_NOT_FOUND),
+    @ApiResponse(code = HttpStatus.SC_UNAUTHORIZED, message = 
MSG_NOT_AUTHENTICATED),
+    @ApiResponse(code = HttpStatus.SC_FORBIDDEN, message = 
MSG_PERMISSION_DENIED),
+    @ApiResponse(code = HttpStatus.SC_INTERNAL_SERVER_ERROR, message = 
MSG_SERVER_ERROR),
+  })
+  public Response deleteServiceGroup(@Context HttpHeaders headers, @Context 
UriInfo ui,
+                                @PathParam("serviceGroupName") String 
serviceGroupName) {
+
+    return handleRequest(headers, null, ui, Request.Type.DELETE, 
createServiceGroupResource(m_clusterName, serviceGroupName));
+  }
+
+  /**
+   * Create a service resource instance.
+   *
+   * @param clusterName  cluster Name
+   * @param serviceGroupName  servicegroup Name
+   *
+   * @return a service resource instance
+   */
+  ResourceInstance createServiceGroupResource(String clusterName, String 
serviceGroupName) {
+    Map<Resource.Type, String> mapIds = new HashMap<>();
+    mapIds.put(Resource.Type.Cluster, clusterName);
+    mapIds.put(Resource.Type.ServiceGroup, serviceGroupName);
+
+
+    return createResource(Resource.Type.ServiceGroup, mapIds);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
index d792717..663d857 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
@@ -62,6 +62,7 @@ import org.apache.ambari.server.state.Service;
 import org.apache.ambari.server.state.ServiceComponent;
 import org.apache.ambari.server.state.ServiceComponentFactory;
 import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.ServiceGroupFactory;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.ServiceOsSpecific;
 import org.apache.ambari.server.state.StackId;
@@ -566,6 +567,13 @@ public interface AmbariManagementController {
   AmbariMetaInfo getAmbariMetaInfo();
 
   /**
+   * Get the service groups factory for this management controller.
+   *
+   * @return the service factory
+   */
+  ServiceGroupFactory getServiceGroupFactory();
+
+  /**
    * Get the service component factory for this management controller.
    *
    * @return the service component factory

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index 12e4a08..0f45270 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -182,6 +182,7 @@ import 
org.apache.ambari.server.state.ServiceComponentFactory;
 import org.apache.ambari.server.state.ServiceComponentHost;
 import org.apache.ambari.server.state.ServiceComponentHostEvent;
 import org.apache.ambari.server.state.ServiceComponentHostFactory;
+import org.apache.ambari.server.state.ServiceGroupFactory;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.ServiceOsSpecific;
 import org.apache.ambari.server.state.StackId;
@@ -266,7 +267,8 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
 
   @Inject
   private RoleCommandOrderProvider roleCommandOrderProvider;
-
+  @Inject
+  private ServiceGroupFactory serviceGroupFactory;
   @Inject
   private ServiceComponentFactory serviceComponentFactory;
   @Inject
@@ -5139,6 +5141,11 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
   }
 
   @Override
+  public ServiceGroupFactory getServiceGroupFactory() {
+    return serviceGroupFactory;
+  }
+
+  @Override
   public ServiceComponentFactory getServiceComponentFactory() {
     return serviceComponentFactory;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index 28c0d10..e09f433 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -70,6 +70,7 @@ import 
org.apache.ambari.server.controller.internal.HostResourceProvider;
 import 
org.apache.ambari.server.controller.internal.KerberosDescriptorResourceProvider;
 import org.apache.ambari.server.controller.internal.MemberResourceProvider;
 import 
org.apache.ambari.server.controller.internal.RepositoryVersionResourceProvider;
+import 
org.apache.ambari.server.controller.internal.ServiceGroupResourceProvider;
 import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
 import org.apache.ambari.server.controller.internal.UpgradeResourceProvider;
 import org.apache.ambari.server.controller.logging.LoggingRequestHelperFactory;
@@ -133,6 +134,9 @@ import org.apache.ambari.server.state.ServiceComponentHost;
 import org.apache.ambari.server.state.ServiceComponentHostFactory;
 import org.apache.ambari.server.state.ServiceComponentImpl;
 import org.apache.ambari.server.state.ServiceFactory;
+import org.apache.ambari.server.state.ServiceGroup;
+import org.apache.ambari.server.state.ServiceGroupFactory;
+import org.apache.ambari.server.state.ServiceGroupImpl;
 import org.apache.ambari.server.state.ServiceImpl;
 import org.apache.ambari.server.state.UpgradeContextFactory;
 import org.apache.ambari.server.state.cluster.ClusterFactory;
@@ -461,11 +465,14 @@ public class ControllerModule extends AbstractModule {
         Host.class, HostImpl.class).build(HostFactory.class));
     install(new FactoryModuleBuilder().implement(
         Service.class, ServiceImpl.class).build(ServiceFactory.class));
+    install(new FactoryModuleBuilder().implement(
+        ServiceGroup.class, 
ServiceGroupImpl.class).build(ServiceGroupFactory.class));
 
     install(new FactoryModuleBuilder()
         .implement(ResourceProvider.class, Names.named("host"), 
HostResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("hostComponent"), 
HostComponentResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("service"), 
ServiceResourceProvider.class)
+        .implement(ResourceProvider.class, Names.named("servicegroup"), 
ServiceGroupResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("component"), 
ComponentResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("member"), 
MemberResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("repositoryVersion"), 
RepositoryVersionResourceProvider.class)

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
index 3912138..d88d2ff 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
@@ -43,6 +43,9 @@ public interface ResourceProviderFactory {
   @Named("service")
   ResourceProvider getServiceResourceProvider(AmbariManagementController 
managementController);
 
+  @Named("servicegroup")
+  ResourceProvider getServiceGroupResourceProvider(AmbariManagementController 
managementController);
+
   @Named("component")
   ResourceProvider getComponentResourceProvider(AmbariManagementController 
managementController);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupRequest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupRequest.java
new file mode 100644
index 0000000..53c3d1e
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupRequest.java
@@ -0,0 +1,65 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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;
+
+
+public class ServiceGroupRequest {
+
+  private String clusterName; // REF
+  private String serviceGroupName; // GET/CREATE/UPDATE/DELETE
+
+  public ServiceGroupRequest(String clusterName, String serviceGroupName) {
+    this.clusterName = clusterName;
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  /**
+   * @return the clusterName
+   */
+  public String getClusterName() {
+    return clusterName;
+  }
+
+  /**
+   * @param clusterName the clusterName to set
+   */
+  public void setClusterName(String clusterName) {
+    this.clusterName = clusterName;
+  }
+
+  /**
+   * @return the serviceGroupName
+   */
+  public String getServiceGroupName() {
+    return serviceGroupName;
+  }
+
+  /**
+   * @param serviceGroupName the service group name to set
+   */
+  public void setServiceGroupName(String serviceGroupName) {
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("clusterName=" + clusterName
+      + ", serviceGroupName=" + serviceGroupName);
+    return sb.toString();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupResponse.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupResponse.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupResponse.java
new file mode 100644
index 0000000..147650c
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/ServiceGroupResponse.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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ServiceGroupResponse {
+
+  private Long clusterId;
+  private Long serviceGroupId;
+  private String clusterName;
+  private String serviceGroupName;
+
+  public ServiceGroupResponse(Long clusterId, String clusterName, Long 
serviceGroupId, String serviceGroupName) {
+    this.clusterId = clusterId;
+    this.serviceGroupId = serviceGroupId;
+    this.clusterName = clusterName;
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  /**
+   * @return the clusterId
+   */
+  public Long getClusterId() {
+    return clusterId;
+  }
+
+  /**
+   * @param clusterId the clusterId to set
+   */
+  public void setClusterId(Long clusterId) {
+    this.clusterId = clusterId;
+  }
+
+  /**
+   * @return the clusterName
+   */
+  public String getClusterName() {
+    return clusterName;
+  }
+
+  /**
+   * @param clusterName the clusterName to set
+   */
+  public void setClusterName(String clusterName) {
+    this.clusterName = clusterName;
+  }
+
+  /**
+   * @return the service group Id
+   */
+  public Long getServiceGroupId() {
+    return serviceGroupId;
+  }
+
+  /**
+   * @param  serviceGroupId the service group Id
+   */
+  public void setServiceGroupId(Long serviceGroupId) {
+    this.serviceGroupId = serviceGroupId;
+  }
+
+  /**
+   * @return the service group name
+   */
+  public String getServiceGroupName() {
+    return serviceGroupName;
+  }
+
+  /**
+   * @param  serviceGroupName the service group name
+   */
+  public void setServiceGroupName(String serviceGroupName) {
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ServiceGroupResponse that = (ServiceGroupResponse) o;
+
+    if (clusterId != null ?
+      !clusterId.equals(that.clusterId) : that.clusterId != null) {
+      return false;
+    }
+    if (clusterName != null ?
+      !clusterName.equals(that.clusterName) : that.clusterName != null) {
+      return false;
+    }
+    if (serviceGroupName != null ?
+      !serviceGroupName.equals(that.serviceGroupName) : that.serviceGroupName 
!= null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Interface to help correct Swagger documentation generation
+   */
+  public interface ServiceGroupResponseSwagger extends ApiModel {
+    @ApiModelProperty(name = "ServiceGroupInfo")
+    ServiceResponse getServiceGroupResponse();
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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 3228a7f..e8876fb 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
@@ -154,6 +154,8 @@ public abstract class AbstractControllerResourceProvider 
extends AbstractAuthori
         return new ClusterResourceProvider(managementController);
       case Service:
         return 
resourceProviderFactory.getServiceResourceProvider(managementController);
+      case ServiceGroup:
+        return 
resourceProviderFactory.getServiceGroupResourceProvider(managementController);
       case Component:
         return 
resourceProviderFactory.getComponentResourceProvider(managementController);
       case Host:

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
index 248abad..05e79f4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
@@ -90,6 +90,8 @@ public class DefaultProviderModule extends 
AbstractProviderModule {
         return new GroupPrivilegeResourceProvider();
       case Alert:
         return new AlertResourceProvider(managementController);
+      case ServiceGroup:
+        return new ServiceGroupResourceProvider(managementController);
       case Registry:
         return new RegistryResourceProvider(managementController);
       case RegistryRecommendation:

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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..2e935af
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceGroupResourceProvider.java
@@ -0,0 +1,456 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.ClusterNotFoundException;
+import org.apache.ambari.server.DuplicateResourceException;
+import org.apache.ambari.server.ObjectNotFoundException;
+import org.apache.ambari.server.ParentObjectNotFoundException;
+import org.apache.ambari.server.ServiceGroupNotFoundException;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.controller.RequestStatusResponse;
+import org.apache.ambari.server.controller.ServiceGroupRequest;
+import org.apache.ambari.server.controller.ServiceGroupResponse;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+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.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.ServiceGroup;
+import org.apache.ambari.server.utils.StageUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+/**
+ * Resource provider for service resources.
+ **/
+
+public class ServiceGroupResourceProvider extends 
AbstractControllerResourceProvider {
+
+
+  // ----- Property ID constants ---------------------------------------------
+
+  public static final String RESPONSE_KEY = "ServiceGroupInfo";
+  public static final String ALL_PROPERTIES = RESPONSE_KEY + 
PropertyHelper.EXTERNAL_PATH_SEP + "*";
+  public static final String SERVICE_GROUP_CLUSTER_ID_PROPERTY_ID = 
RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "cluster_id";
+  public static final String SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID = 
RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "cluster_name";
+  public static final String SERVICE_GROUP_SERVICE_GROUP_ID_PROPERTY_ID = 
RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "service_group_id";
+  public static final String SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID = 
RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "service_group_name";
+
+  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 Gson gson = StageUtils.getGson();
+
+  /**
+   * The property ids for an service group resource.
+   */
+  private static final Set<String> PROPERTY_IDS = new HashSet<>();
+
+  /**
+   * The key property ids for an service group resource.
+   */
+  private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = new 
HashMap<>();
+
+  static {
+    // properties
+    PROPERTY_IDS.add(SERVICE_GROUP_CLUSTER_ID_PROPERTY_ID);
+    PROPERTY_IDS.add(SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID);
+    PROPERTY_IDS.add(SERVICE_GROUP_SERVICE_GROUP_ID_PROPERTY_ID);
+    PROPERTY_IDS.add(SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID);
+
+    // keys
+    KEY_PROPERTY_IDS.put(Resource.Type.Cluster, 
SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID);
+    KEY_PROPERTY_IDS.put(Resource.Type.ServiceGroup, 
SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID);
+  }
+
+  private Clusters clusters;
+
+  /**
+   * kerberos helper
+   */
+  @Inject
+  private KerberosHelper kerberosHelper;
+
+  // ----- Constructors ----------------------------------------------------
+
+  /**
+   * Create a  new resource provider for the given management controller.
+   *
+   * @param managementController the management controller
+   */
+  @AssistedInject
+  public ServiceGroupResourceProvider(@Assisted AmbariManagementController 
managementController) {
+    super(Resource.Type.ServiceGroup, PROPERTY_IDS, KEY_PROPERTY_IDS, 
managementController);
+  }
+
+  // ----- 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));
+    }
+    Set<ServiceGroupResponse> createServiceGroups = null;
+    createServiceGroups = createResources(new 
Command<Set<ServiceGroupResponse>>() {
+      @Override
+      public Set<ServiceGroupResponse> invoke() throws AmbariException, 
AuthorizationException {
+        return createServiceGroups(requests);
+      }
+    });
+    Set<Resource> associatedResources = new HashSet<>();
+    if (createServiceGroups != null) {
+      Iterator<ServiceGroupResponse> itr = createServiceGroups.iterator();
+      while (itr.hasNext()) {
+        ServiceGroupResponse response = itr.next();
+        notifyCreate(Resource.Type.ServiceGroup, request);
+        Resource resource = new ResourceImpl(Resource.Type.ServiceGroup);
+        resource.setProperty(SERVICE_GROUP_CLUSTER_ID_PROPERTY_ID, 
response.getClusterId());
+        resource.setProperty(SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID, 
response.getClusterName());
+        resource.setProperty(SERVICE_GROUP_SERVICE_GROUP_ID_PROPERTY_ID, 
response.getServiceGroupId());
+        resource.setProperty(SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID, 
response.getServiceGroupName());
+
+        associatedResources.add(resource);
+      }
+      return getRequestStatus(null, associatedResources);
+    }
+
+    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_ID_PROPERTY_ID,
+        response.getClusterId(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID,
+        response.getClusterName(), requestedIds);
+      setResourceProperty(resource, SERVICE_GROUP_SERVICE_GROUP_ID_PROPERTY_ID,
+        response.getServiceGroupId(), requestedIds);
+      setResourceProperty(resource, 
SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID,
+        response.getServiceGroupName(), requestedIds);
+      resources.add(resource);
+    }
+    return resources;
+  }
+
+  @Override
+  protected RequestStatus updateResourcesAuthorized(final Request request, 
Predicate predicate)
+    throws SystemException, UnsupportedPropertyException, 
NoSuchResourceException, NoSuchParentResourceException {
+
+    // TODO : Add functionality for updating SG : RENAME, START ALL, STOP ALL 
services.
+    RequestStatusResponse response = null;
+    return getRequestStatus(response);
+  }
+
+  @Override
+  protected RequestStatus deleteResourcesAuthorized(Request request, Predicate 
predicate)
+    throws SystemException, UnsupportedPropertyException, 
NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<ServiceGroupRequest> requests = new HashSet<>();
+    DeleteStatusMetaData deleteStatusMetaData = null;
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      requests.add(getRequest(propertyMap));
+    }
+    deleteStatusMetaData = modifyResources(new Command<DeleteStatusMetaData>() 
{
+      @Override
+      public DeleteStatusMetaData invoke() throws AmbariException, 
AuthorizationException {
+        deleteServiceGroups(requests);
+        return new DeleteStatusMetaData();
+      }
+    });
+
+    notifyDelete(Resource.Type.ServiceGroup, predicate);
+    for(ServiceGroupRequest svgReq : requests) {
+      deleteStatusMetaData.addDeletedKey("cluster_name: 
"+svgReq.getClusterName() + ", " + "service_group_name: 
"+svgReq.getServiceGroupName());
+    }
+    return getRequestStatus(null, null, deleteStatusMetaData);
+  }
+
+  @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 -------------------------------------------------
+
+  /**
+   * 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) {
+    String clusterName = (String) 
properties.get(SERVICE_GROUP_CLUSTER_NAME_PROPERTY_ID);
+    String serviceGroupName = (String) 
properties.get(SERVICE_GROUP_SERVICE_GROUP_NAME_PROPERTY_ID);
+    ServiceGroupRequest svcRequest = new ServiceGroupRequest(clusterName, 
serviceGroupName);
+    return svcRequest;
+  }
+
+  // Create services from the given request.
+  public synchronized Set<ServiceGroupResponse> 
createServiceGroups(Set<ServiceGroupRequest> requests)
+    throws AmbariException, AuthorizationException {
+
+    if (requests.isEmpty()) {
+      LOG.warn("Received an empty requests set");
+      return null;
+    }
+    AmbariManagementController controller = getManagementController();
+    Clusters clusters = controller.getClusters();
+
+    // do all validation checks
+    validateCreateRequests(requests, clusters);
+
+    Set<ServiceGroupResponse> createdSvcGrps = new HashSet<>();
+    for (ServiceGroupRequest request : requests) {
+      Cluster cluster = clusters.getCluster(request.getClusterName());
+
+      // Already checked that service group does not exist
+      ServiceGroup sg = cluster.addServiceGroup(request.getServiceGroupName());
+      createdSvcGrps.add(sg.convertToResponse());
+    }
+    return createdSvcGrps;
+  }
+
+  // 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) {
+      throw new AmbariException("Invalid arguments, cluster id"
+        + " 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();
+
+      response.add(serviceGroupResponse);
+      return response;
+    }
+
+    for (ServiceGroup sg : cluster.getServiceGroups().values()) {
+      ServiceGroupResponse serviceGroupResponse = sg.convertToResponse();
+      response.add(serviceGroupResponse);
+    }
+    return response;
+  }
+
+
+  // Delete services based on the given set of requests
+  protected void deleteServiceGroups(Set<ServiceGroupRequest> request)
+    throws AmbariException, AuthorizationException {
+
+    Clusters clusters = getManagementController().getClusters();
+
+    Set<ServiceGroup> removable = new HashSet<>();
+
+    for (ServiceGroupRequest serviceGroupRequest : request) {
+      if (null == 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.getServiceGroupName());
+    }
+
+    return;
+  }
+
+
+  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.notNull(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=" + 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);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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 1994501..a364c4c 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,
@@ -203,6 +204,7 @@ public interface Resource {
      * Internal types.  See {@link InternalType}.
      */
     public static final Type Cluster = InternalType.Cluster.getType();
+    public static final Type ServiceGroup = 
InternalType.ServiceGroup.getType();
     public static final Type Service = InternalType.Service.getType();
     public static final Type Setting = InternalType.Setting.getType();
     public static final Type Host = InternalType.Host.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/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 9a5ee79..07b70b6 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/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupEvent.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupEvent.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupEvent.java
new file mode 100644
index 0000000..92b739b
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupEvent.java
@@ -0,0 +1,49 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.events;
+
+
+/**
+ * The {@link ServiceEvent} class is the base for all service events in Ambari.
+ */
+public abstract class ServiceGroupEvent extends ClusterEvent {
+
+  /**
+   * The name of the service group.
+   */
+  protected final String m_serviceGroupName;
+
+  /**
+   * Constructor.
+   *
+   * @param eventType
+   * @param clusterId
+   */
+  public ServiceGroupEvent(AmbariEventType eventType, long clusterId, String 
serviceGroupName) {
+    super(eventType, clusterId);
+    m_serviceGroupName = serviceGroupName;
+  }
+
+  /**
+   * @return the service group name (never {@code null}).
+   */
+  public String getServiceGroupName() {
+    return m_serviceGroupName;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupInstalledEvent.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupInstalledEvent.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupInstalledEvent.java
new file mode 100644
index 0000000..e66d8a0
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupInstalledEvent.java
@@ -0,0 +1,46 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.events;
+
+/**
+ * The {@link ServiceGroupInstalledEvent} class is fired when a service group 
is
+ * successfully installed.
+ */
+public class ServiceGroupInstalledEvent extends ServiceGroupEvent {
+  /**
+   * Constructor.
+   *
+   * @param clusterId
+   * @param serviceGroupName
+   */
+  public ServiceGroupInstalledEvent(long clusterId, String serviceGroupName) {
+    super(AmbariEventType.SERVICE_GROUP_INSTALL_SUCCESS, clusterId, 
serviceGroupName);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("ServiceGroupInstalledEvent{");
+    buffer.append("cluserId=").append(m_clusterId);
+    buffer.append(", serviceGroupName=").append(m_serviceGroupName);
+    buffer.append("}");
+    return buffer.toString();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupRemovedEvent.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupRemovedEvent.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupRemovedEvent.java
new file mode 100644
index 0000000..b29a9a4
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceGroupRemovedEvent.java
@@ -0,0 +1,46 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.events;
+
+/**
+ * The {@link ServiceRemovedEvent} class is fired when a service is 
successfully
+ * removed.
+ */
+public class ServiceGroupRemovedEvent extends ServiceGroupEvent {
+  /**
+   * Constructor.
+   *
+   * @param clusterId
+   * @param serviceGroupName
+   */
+  public ServiceGroupRemovedEvent(long clusterId, String serviceGroupName) {
+    super(AmbariEventType.SERVICE_GROUP_REMOVED_SUCCESS, clusterId, 
serviceGroupName);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder("ServiceGroupRemovedEvent{");
+    buffer.append("cluserId=").append(m_clusterId);
+    buffer.append(", serviceGroupName=").append(m_serviceGroupName);
+    buffer.append("}");
+    return buffer.toString();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceGroupDAO.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceGroupDAO.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceGroupDAO.java
new file mode 100644
index 0000000..dc997b0
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceGroupDAO.java
@@ -0,0 +1,93 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.ServiceGroupEntity;
+import org.apache.ambari.server.orm.entities.ServiceGroupEntityPK;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+
+@Singleton
+public class ServiceGroupDAO {
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
+  @Inject
+  DaoUtils daoUtils;
+
+  @RequiresSession
+  public ServiceGroupEntity findByPK(ServiceGroupEntityPK 
clusterServiceGroupEntityPK) {
+    return entityManagerProvider.get().find(ServiceGroupEntity.class, 
clusterServiceGroupEntityPK);
+  }
+
+  @RequiresSession
+  public ServiceGroupEntity findByClusterAndServiceGroupNames(String 
clusterName, String serviceGroupName) {
+    TypedQuery<ServiceGroupEntity> query = entityManagerProvider.get()
+      .createNamedQuery("serviceGroupByClusterAndServiceGroupNames", 
ServiceGroupEntity.class);
+    query.setParameter("clusterName", clusterName);
+    query.setParameter("serviceGroupName", serviceGroupName);
+
+    try {
+      return query.getSingleResult();
+    } catch (NoResultException ignored) {
+      return null;
+    }
+  }
+
+  @RequiresSession
+  public List<ServiceGroupEntity> findAll() {
+    return daoUtils.selectAll(entityManagerProvider.get(), 
ServiceGroupEntity.class);
+  }
+
+  @Transactional
+  public void refresh(ServiceGroupEntity clusterServiceGroupEntity) {
+    entityManagerProvider.get().refresh(clusterServiceGroupEntity);
+  }
+
+  @Transactional
+  public void create(ServiceGroupEntity clusterServiceGroupEntity) {
+    entityManagerProvider.get().persist(clusterServiceGroupEntity);
+  }
+
+  @Transactional
+  public ServiceGroupEntity merge(ServiceGroupEntity 
clusterServiceGroupEntity) {
+    return entityManagerProvider.get().merge(clusterServiceGroupEntity);
+  }
+
+  @Transactional
+  public void remove(ServiceGroupEntity clusterServiceGroupEntity) {
+    entityManagerProvider.get().remove(merge(clusterServiceGroupEntity));
+  }
+
+  @Transactional
+  public void removeByPK(ServiceGroupEntityPK clusterServiceGroupEntityPK) {
+    ServiceGroupEntity entity = findByPK(clusterServiceGroupEntityPK);
+    entityManagerProvider.get().remove(entity);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterEntity.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterEntity.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterEntity.java
index c22449c..abbf709 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterEntity.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterEntity.java
@@ -47,23 +47,23 @@ import org.apache.ambari.server.state.State;
 
 @Table(name = "clusters")
 @NamedQueries({
-    @NamedQuery(name = "clusterByName", query =
-        "SELECT cluster " +
-            "FROM ClusterEntity cluster " +
-            "WHERE cluster.clusterName=:clusterName"),
-    @NamedQuery(name = "allClusters", query =
-        "SELECT clusters " +
-            "FROM ClusterEntity clusters"),
-    @NamedQuery(name = "clusterByResourceId", query =
-        "SELECT cluster " +
-            "FROM ClusterEntity cluster " +
-            "WHERE cluster.resource.id=:resourceId")
+  @NamedQuery(name = "clusterByName", query =
+    "SELECT cluster " +
+      "FROM ClusterEntity cluster " +
+      "WHERE cluster.clusterName=:clusterName"),
+  @NamedQuery(name = "allClusters", query =
+    "SELECT clusters " +
+      "FROM ClusterEntity clusters"),
+  @NamedQuery(name = "clusterByResourceId", query =
+    "SELECT cluster " +
+      "FROM ClusterEntity cluster " +
+      "WHERE cluster.resource.id=:resourceId")
 })
 @Entity
 @TableGenerator(name = "cluster_id_generator",
-    table = "ambari_sequences", pkColumnName = "sequence_name", 
valueColumnName = "sequence_value"
-    , pkColumnValue = "cluster_id_seq"
-    , initialValue = 1
+  table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName 
= "sequence_value"
+  , pkColumnValue = "cluster_id_seq"
+  , initialValue = 1
 )
 public class ClusterEntity {
 
@@ -74,7 +74,7 @@ public class ClusterEntity {
 
   @Basic
   @Column(name = "cluster_name", nullable = false, insertable = true,
-      updatable = true, unique = true, length = 100)
+    updatable = true, unique = true, length = 100)
   private String clusterName;
 
   @Basic
@@ -105,6 +105,9 @@ public class ClusterEntity {
   @OneToMany(mappedBy = "clusterEntity")
   private Collection<ClusterServiceEntity> clusterServiceEntities;
 
+  @OneToMany(mappedBy = "clusterEntity")
+  private Collection<ServiceGroupEntity> serviceGroupEntities;
+
   @OneToOne(mappedBy = "clusterEntity", cascade = CascadeType.REMOVE)
   private ClusterStateEntity clusterStateEntity;
 
@@ -134,7 +137,7 @@ public class ClusterEntity {
 
   @OneToOne(cascade = CascadeType.ALL)
   @JoinColumns({
-      @JoinColumn(name = "resource_id", referencedColumnName = "resource_id", 
nullable = false)
+    @JoinColumn(name = "resource_id", referencedColumnName = "resource_id", 
nullable = false)
   })
   private ResourceEntity resource;
 
@@ -147,11 +150,11 @@ public class ClusterEntity {
    */
   @OneToOne(cascade = CascadeType.REMOVE)
   @JoinColumn(
-      name = "upgrade_id",
-      referencedColumnName = "upgrade_id",
-      nullable = true,
-      insertable = false,
-      updatable = true)
+    name = "upgrade_id",
+    referencedColumnName = "upgrade_id",
+    nullable = true,
+    insertable = false,
+    updatable = true)
   private UpgradeEntity upgradeEntity = null;
 
   public Long getClusterId() {
@@ -201,7 +204,7 @@ public class ClusterEntity {
    * @return either {@link State#INIT} or {@link State#INSTALLED},
    * never {@code null}.
    */
-  public State getProvisioningState(){
+  public State getProvisioningState() {
     return provisioningState;
   }
 
@@ -210,9 +213,9 @@ public class ClusterEntity {
    * deployment requests.
    *
    * @param provisioningState either {@link State#INIT} or
-   * {@link State#INSTALLED}, never {@code null}.
+   *                          {@link State#INSTALLED}, never {@code null}.
    */
-  public void setProvisioningState(State provisioningState){
+  public void setProvisioningState(State provisioningState) {
     this.provisioningState = provisioningState;
   }
 
@@ -270,6 +273,14 @@ public class ClusterEntity {
     this.clusterServiceEntities = clusterServiceEntities;
   }
 
+  public Collection<ServiceGroupEntity> getServiceGroupEntities() {
+    return serviceGroupEntities;
+  }
+
+  public void setServiceGroupEntities(Collection<ServiceGroupEntity> 
serviceGroupEntities) {
+    this.serviceGroupEntities = serviceGroupEntities;
+  }
+
   public ClusterStateEntity getClusterStateEntity() {
     return clusterStateEntity;
   }
@@ -334,7 +345,7 @@ public class ClusterEntity {
   /**
    * Set the admin resource entity.
    *
-   * @param resource  the resource entity
+   * @param resource the resource entity
    */
   public void setResource(ResourceEntity resource) {
     this.resource = resource;

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntity.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntity.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntity.java
new file mode 100644
index 0000000..cd9d2c8
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntity.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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+
+
+@IdClass(ServiceGroupEntityPK.class)
+@Table(name = "servicegroups")
+@NamedQueries({
+  @NamedQuery(name = "serviceGroupByClusterAndServiceGroupNames", query =
+    "SELECT serviceGroup " +
+      "FROM ServiceGroupEntity serviceGroup " +
+      "JOIN serviceGroup.clusterEntity cluster " +
+      "WHERE serviceGroup.serviceGroupName=:serviceGroupName AND 
cluster.clusterName=:clusterName")
+})
+@Entity
+@TableGenerator(name = "service_group_id_generator",
+  table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName 
= "sequence_value"
+  , pkColumnValue = "service_group_id_seq"
+  , initialValue = 1
+)
+public class ServiceGroupEntity {
+
+  @Id
+  @Column(name = "cluster_id", nullable = false, insertable = false, updatable 
= false, length = 10)
+  private Long clusterId;
+
+  @Id
+  @Column(name = "id", nullable = false, insertable = true, updatable = true)
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = 
"service_group_id_generator")
+  private Long serviceGroupId;
+
+  @Column(name = "service_group_name", nullable = false, insertable = true, 
updatable = true)
+  private String serviceGroupName;
+
+  @ManyToOne
+  @JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", 
nullable = false)
+  private ClusterEntity clusterEntity;
+
+  public Long getClusterId() {
+    return clusterId;
+  }
+
+  public void setClusterId(Long clusterId) {
+    this.clusterId = clusterId;
+  }
+
+  public Long getServiceGroupId() {
+    return serviceGroupId;
+  }
+
+  public void setServiceGroupId(Long serviceGroupId) {
+    this.serviceGroupId = serviceGroupId;
+  }
+
+
+  public String getServiceGroupName() {
+    return serviceGroupName;
+  }
+
+  public void setServiceGroupName(String serviceGroupName) {
+    this.serviceGroupName = serviceGroupName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ServiceGroupEntity that = (ServiceGroupEntity) o;
+
+    if (clusterId != null ? !clusterId.equals(that.clusterId) : that.clusterId 
!= null) return false;
+    if (serviceGroupName != null ? 
!serviceGroupName.equals(that.serviceGroupName) : that.serviceGroupName != null)
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = clusterId != null ? clusterId.intValue() : 0;
+    result = 31 * result + (serviceGroupName != null ? 
serviceGroupName.hashCode() : 0);
+    return result;
+  }
+
+  public ClusterEntity getClusterEntity() {
+    return clusterEntity;
+  }
+
+  public void setClusterEntity(ClusterEntity clusterEntity) {
+    this.clusterEntity = clusterEntity;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntityPK.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntityPK.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntityPK.java
new file mode 100644
index 0000000..c993442
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceGroupEntityPK.java
@@ -0,0 +1,71 @@
+/**
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.entities;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+
+@SuppressWarnings("serial")
+public class ServiceGroupEntityPK implements Serializable {
+  private Long clusterId;
+
+  @Id
+  @Column(name = "cluster_id", nullable = false, insertable = true, updatable 
= true, length = 10)
+  public Long getClusterId() {
+    return clusterId;
+  }
+
+  public void setClusterId(Long clusterId) {
+    this.clusterId = clusterId;
+  }
+
+  private Long serviceGroupId;
+
+  @Id
+  @Column(name = "id", nullable = false, insertable = true, updatable = true, 
length = 10)
+  public Long getServiceGroupId() {
+    return this.serviceGroupId;
+  }
+
+  public void setServiceGroupId(Long serviceGroupId) {
+    this.serviceGroupId = serviceGroupId;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ServiceGroupEntityPK that = (ServiceGroupEntityPK) o;
+
+    if (clusterId != null ? !clusterId.equals(that.clusterId) : that.clusterId 
!= null) return false;
+    if (serviceGroupId != null ? !serviceGroupId.equals(that.serviceGroupId) : 
that.serviceGroupId != null) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = clusterId != null ? clusterId.intValue() : 0;
+    result = 31 * result + (serviceGroupId != null ? serviceGroupId.hashCode() 
: 0);
+    return result;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/2f1f96ef/ambari-server/src/main/java/org/apache/ambari/server/serveraction/servicegroup/ServiceGroupServerAction.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/servicegroup/ServiceGroupServerAction.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/servicegroup/ServiceGroupServerAction.java
new file mode 100644
index 0000000..818957e
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/servicegroup/ServiceGroupServerAction.java
@@ -0,0 +1,92 @@
+/*
+ * 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.serveraction.servicegroup;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.agent.ExecutionCommand;
+import org.apache.ambari.server.serveraction.AbstractServerAction;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+
+
+public abstract class ServiceGroupServerAction extends AbstractServerAction {
+
+  protected final static Logger LOG = 
LoggerFactory.getLogger(ServiceGroupServerAction.class);
+
+  /**
+   * Gson
+   */
+  @Inject
+  protected Gson gson;
+
+  /**
+   * The Cluster that this ServerAction implementation is executing on
+   */
+  @Inject
+  private Clusters clusters = null;
+
+  /**
+   * Returns the relevant Cluster object
+   *
+   * @return the relevant Cluster
+   * @throws AmbariException if the Cluster object cannot be retrieved
+   */
+  protected Cluster getCluster() throws AmbariException {
+    Cluster cluster = clusters.getCluster(getClusterName());
+
+    if (cluster == null) {
+      throw new AmbariException(String.format("Failed to retrieve cluster for 
%s", getClusterName()));
+    }
+
+    return cluster;
+  }
+
+  /**
+   * The Clusters object for this KerberosServerAction
+   *
+   * @return a Clusters object
+   */
+  protected Clusters getClusters() {
+    return clusters;
+  }
+
+  /**
+   * Returns the relevant cluster's name
+   * <p/>
+   * Using the data from the execution command, retrieve the relevant 
cluster's name.
+   *
+   * @return a String declaring the relevant cluster's name
+   * @throws AmbariException if the cluster's name is not available
+   */
+  protected String getClusterName() throws AmbariException {
+    ExecutionCommand executionCommand = getExecutionCommand();
+    String clusterName = (executionCommand == null) ? null : 
executionCommand.getClusterName();
+
+    if ((clusterName == null) || clusterName.isEmpty()) {
+      throw new AmbariException("Failed to retrieve the cluster name from the 
execution command");
+    }
+
+    return clusterName;
+  }
+}
\ No newline at end of file

Reply via email to