You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sw...@apache.org on 2014/07/01 22:42:03 UTC

[1/2] AMBARI-6343. Views : Admin - Add Group and Group Member Resources.

Repository: ambari
Updated Branches:
  refs/heads/trunk 01e9e7607 -> 00a4991ee


http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java
new file mode 100644
index 0000000..b4e015d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.MemberEntity;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+
+@Singleton
+public class MemberDAO {
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
+  @Inject
+  DaoUtils daoUtils;
+
+  @RequiresSession
+  public MemberEntity findByPK(Integer memberPK) {
+    return entityManagerProvider.get().find(MemberEntity.class, memberPK);
+  }
+
+  @RequiresSession
+  public List<MemberEntity> findAll() {
+    final TypedQuery<MemberEntity> query = entityManagerProvider.get().createQuery("SELECT member FROM MemberEntity member", MemberEntity.class);
+    return daoUtils.selectList(query);
+  }
+
+  @Transactional
+  public void create(MemberEntity member) {
+    entityManagerProvider.get().persist(member);
+  }
+
+  @Transactional
+  public MemberEntity merge(MemberEntity member) {
+    return entityManagerProvider.get().merge(member);
+  }
+
+  @Transactional
+  public void remove(MemberEntity member) {
+    entityManagerProvider.get().remove(merge(member));
+  }
+
+  @Transactional
+  public void removeByPK(Integer memberPK) {
+    remove(findByPK(memberPK));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/GroupEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/GroupEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/GroupEntity.java
new file mode 100644
index 0000000..64fcf9a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/GroupEntity.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.entities;
+
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+import javax.persistence.UniqueConstraint;
+
+@Entity
+@Table(name = "groups", uniqueConstraints = {@UniqueConstraint(columnNames = {"group_name", "ldap_group"})})
+@TableGenerator(name = "group_id_generator",
+    table = "ambari_sequences",
+    pkColumnName = "sequence_name",
+    valueColumnName = "value",
+    pkColumnValue = "group_id_seq",
+    initialValue = 1,
+    allocationSize = 1
+    )
+@NamedQueries({
+  @NamedQuery(name = "groupByName", query = "SELECT group_entity FROM GroupEntity group_entity where lower(group_entity.groupName)=:groupname")
+})
+public class GroupEntity {
+  @Id
+  @Column(name = "group_id")
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = "group_id_generator")
+  private Integer groupId;
+
+  @Column(name = "group_name")
+  private String groupName;
+
+  @Column(name = "ldap_group")
+  private Integer ldapGroup = 0;
+
+  @OneToMany(mappedBy = "group", cascade = CascadeType.ALL)
+  private Set<MemberEntity> memberEntities;
+
+  public Integer getGroupId() {
+    return groupId;
+  }
+
+  public void setGroupId(Integer groupId) {
+    this.groupId = groupId;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public void setGroupName(String groupName) {
+    this.groupName = groupName;
+  }
+
+  public Boolean getLdapGroup() {
+    return ldapGroup == 0 ? Boolean.FALSE : Boolean.TRUE;
+  }
+
+  public void setLdapGroup(Boolean ldapGroup) {
+    if (ldapGroup == null) {
+      this.ldapGroup = null;
+    } else {
+      this.ldapGroup = ldapGroup ? 1 : 0;
+    }
+  }
+
+  public Set<MemberEntity> getMemberEntities() {
+    return memberEntities;
+  }
+
+  public void setMemberEntities(Set<MemberEntity> memberEntities) {
+    this.memberEntities = memberEntities;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    GroupEntity that = (GroupEntity) o;
+
+    if (groupId != null ? !groupId.equals(that.groupId) : that.groupId != null) return false;
+    if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false;
+    if (ldapGroup != null ? !ldapGroup.equals(that.ldapGroup) : that.ldapGroup != null) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = groupId != null ? groupId.hashCode() : 0;
+    result = 31 * result + (groupName != null ? groupName.hashCode() : 0);
+    result = 31 * result + (ldapGroup != null ? ldapGroup.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java
new file mode 100644
index 0000000..04b1a87
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+import javax.persistence.UniqueConstraint;
+
+@Entity
+@Table(name = "members", uniqueConstraints = {@UniqueConstraint(columnNames = {"group_id", "user_id"})})
+@TableGenerator(name = "member_id_generator",
+    table = "ambari_sequences",
+    pkColumnName = "sequence_name",
+    valueColumnName = "value",
+    pkColumnValue = "member_id_seq",
+    initialValue = 1,
+    allocationSize = 1
+    )
+public class MemberEntity {
+  @Id
+  @Column(name = "member_id")
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = "member_id_generator")
+  private Integer memberId;
+
+  @ManyToOne
+  @JoinColumn(name = "group_id")
+  private GroupEntity group;
+
+  @ManyToOne
+  @JoinColumn(name = "user_id")
+  private UserEntity user;
+
+  public Integer getMemberId() {
+    return memberId;
+  }
+
+  public void setMemberId(Integer memberId) {
+    this.memberId = memberId;
+  }
+
+  public GroupEntity getGroup() {
+    return group;
+  }
+
+  public void setGroup(GroupEntity group) {
+    this.group = group;
+  }
+
+  public UserEntity getUser() {
+    return user;
+  }
+
+  public void setUser(UserEntity user) {
+    this.user = user;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    MemberEntity that = (MemberEntity) o;
+
+    if (memberId != null ? !memberId.equals(that.memberId) : that.memberId != null) return false;
+    if (group != null ? !group.equals(that.group) : that.group != null) return false;
+    if (user != null ? !user.equals(that.user) : that.user != null) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = memberId != null ? memberId.hashCode() : 0;
+    result = 31 * result + (group != null ? group.hashCode() : 0);
+    result = 31 * result + (user != null ? user.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java
index 1dc9e9f..14ad304 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java
@@ -59,6 +59,9 @@ public class UserEntity {
   @ManyToMany(mappedBy = "userEntities")
   private Set<RoleEntity> roleEntities;
 
+  @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
+  private Set<MemberEntity> memberEntities;
+
   public Integer getUserId() {
     return userId;
   }
@@ -103,6 +106,22 @@ public class UserEntity {
     this.createTime = createTime;
   }
 
+  public Set<RoleEntity> getRoleEntities() {
+    return roleEntities;
+  }
+
+  public void setRoleEntities(Set<RoleEntity> roleEntities) {
+    this.roleEntities = roleEntities;
+  }
+
+  public Set<MemberEntity> getMemberEntities() {
+    return memberEntities;
+  }
+
+  public void setMemberEntities(Set<MemberEntity> memberEntities) {
+    this.memberEntities = memberEntities;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -128,12 +147,4 @@ public class UserEntity {
     result = 31 * result + (createTime != null ? createTime.hashCode() : 0);
     return result;
   }
-
-  public Set<RoleEntity> getRoleEntities() {
-    return roleEntities;
-  }
-
-  public void setRoleEntities(Set<RoleEntity> roleEntities) {
-    this.roleEntities = roleEntities;
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Group.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Group.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Group.java
new file mode 100644
index 0000000..b20df8d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Group.java
@@ -0,0 +1,53 @@
+/*
+ * 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.security.authorization;
+
+import org.apache.ambari.server.orm.entities.GroupEntity;
+
+/**
+ * Describes group of users of web-service.
+ */
+public class Group {
+  private final int groupId;
+  private final String groupName;
+  private final boolean ldapGroup;
+
+  Group(GroupEntity groupEntity) {
+    this.groupId = groupEntity.getGroupId();
+    this.groupName = groupEntity.getGroupName();
+    this.ldapGroup = groupEntity.getLdapGroup();
+  }
+
+  public int getGroupId() {
+    return groupId;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public boolean isLdapGroup() {
+    return ldapGroup;
+  }
+
+  @Override
+  public String toString() {
+    return "Group [groupId=" + groupId + ", groupName=" + groupName
+        + ", ldapGroup=" + ldapGroup + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Member.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Member.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Member.java
new file mode 100644
index 0000000..da4732a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Member.java
@@ -0,0 +1,53 @@
+/*
+ * 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.security.authorization;
+
+import org.apache.ambari.server.orm.entities.GroupEntity;
+
+/**
+ * Describes group of users of web-service.
+ */
+public class Member {
+  private final int groupId;
+  private final String groupName;
+  private final boolean ldapGroup;
+
+  Member(GroupEntity groupEntity) {
+    this.groupId = groupEntity.getGroupId();
+    this.groupName = groupEntity.getGroupName();
+    this.ldapGroup = groupEntity.getLdapGroup();
+  }
+
+  public int getGroupId() {
+    return groupId;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public boolean isLdapGroup() {
+    return ldapGroup;
+  }
+
+  @Override
+  public String toString() {
+    return "Group [groupId=" + groupId + ", groupName=" + groupName
+        + ", ldapGroup=" + ldapGroup + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
index b4cbdf9..072d3de 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
@@ -18,13 +18,19 @@
 package org.apache.ambari.server.security.authorization;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.dao.GroupDAO;
+import org.apache.ambari.server.orm.dao.MemberDAO;
 import org.apache.ambari.server.orm.dao.RoleDAO;
 import org.apache.ambari.server.orm.dao.UserDAO;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+import org.apache.ambari.server.orm.entities.MemberEntity;
 import org.apache.ambari.server.orm.entities.RoleEntity;
 import org.apache.ambari.server.orm.entities.UserEntity;
 import org.slf4j.Logger;
@@ -38,7 +44,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
-import java.util.Set;
 
 /**
  * Provides high-level access to Users and Roles in database
@@ -53,6 +58,10 @@ public class Users {
   @Inject
   protected RoleDAO roleDAO;
   @Inject
+  protected GroupDAO groupDAO;
+  @Inject
+  protected MemberDAO memberDAO;
+  @Inject
   protected PasswordEncoder passwordEncoder;
   @Inject
   protected Configuration configuration;
@@ -191,6 +200,72 @@ public class Users {
   }
 
   /**
+   * Gets group by given name.
+   *
+   * @param groupName group name
+   * @return group
+   */
+  public Group getGroup(String groupName) {
+    final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+    return (null == groupEntity) ? null : new Group(groupEntity);
+  }
+
+  /**
+   * Gets group members.
+   *
+   * @param groupName group name
+   * @return list of members
+   */
+  public Collection<User> getGroupMembers(String groupName) {
+    final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+    if (groupEntity == null) {
+      return null;
+    } else {
+      final Set<User> users = new HashSet<User>();
+      for (MemberEntity memberEntity: groupEntity.getMemberEntities()) {
+        users.add(new User(memberEntity.getUser()));
+      }
+      return users;
+    }
+  }
+
+  /**
+   * Creates new local group with provided name
+   */
+  @Transactional
+  public synchronized void createGroup(String groupName) {
+    final GroupEntity groupEntity = new GroupEntity();
+    groupEntity.setGroupName(groupName);
+    groupDAO.create(groupEntity);
+  }
+
+  /**
+   * Gets all groups.
+   *
+   * @return list of groups
+   */
+  public List<Group> getAllGroups() {
+    final List<GroupEntity> groupEntities = groupDAO.findAll();
+    final List<Group> groups = new ArrayList<Group>(groupEntities.size());
+
+    for (GroupEntity groupEntity: groupEntities) {
+      groups.add(new Group(groupEntity));
+    }
+
+    return groups;
+  }
+
+  @Transactional
+  public synchronized void removeGroup(Group group) throws AmbariException {
+    final GroupEntity groupEntity = groupDAO.findByPK(group.getGroupId());
+    if (groupEntity != null) {
+      groupDAO.remove(groupEntity);
+    } else {
+      throw new AmbariException("Group " + group + " doesn't exist");
+    }
+  }
+
+  /**
    * Grants ADMIN role to provided user
    * @throws AmbariException
    */
@@ -243,6 +318,37 @@ public class Users {
   }
 
   @Transactional
+  public synchronized void addMemberToGroup(String groupName, String userName)
+      throws AmbariException {
+
+    final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+    if (groupEntity == null) {
+      throw new AmbariException("Group " + groupName + " doesn't exist");
+    }
+
+    UserEntity userEntity = userDAO.findLocalUserByName(userName);
+    if (userEntity == null) {
+      userEntity = userDAO.findLdapUserByName(userName);
+      if (userEntity == null) {
+        throw new AmbariException("User " + userName + " doesn't exist");
+      }
+    }
+
+    if (isUserInGroup(userEntity, groupEntity)) {
+      throw new AmbariException("User " + userName + " is already present in group " + groupName);
+    } else {
+      final MemberEntity memberEntity = new MemberEntity();
+      memberEntity.setGroup(groupEntity);
+      memberEntity.setUser(userEntity);
+      userEntity.getMemberEntities().add(memberEntity);
+      groupEntity.getMemberEntities().add(memberEntity);
+      memberDAO.create(memberEntity);
+      userDAO.merge(userEntity);
+      groupDAO.merge(groupEntity);
+    }
+  }
+
+  @Transactional
   public synchronized void removeRoleFromUser(User user, String role)
       throws AmbariException {
 
@@ -269,7 +375,7 @@ public class Users {
         ". System should have at least one user with administrator role.");
       }
     }
-    
+
     if (userEntity.getRoleEntities().contains(roleEntity)) {
       userEntity.getRoleEntities().remove(roleEntity);
       roleEntity.getUserEntities().remove(userEntity);
@@ -281,13 +387,65 @@ public class Users {
 
   }
 
+  @Transactional
+  public synchronized void removeMemberFromGroup(String groupName, String userName)
+      throws AmbariException {
+
+    final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+    if (groupEntity == null) {
+      throw new AmbariException("Group " + groupName + " doesn't exist");
+    }
+
+    UserEntity userEntity = userDAO.findLocalUserByName(userName);
+    if (userEntity == null) {
+      userEntity = userDAO.findLdapUserByName(userName);
+      if (userEntity == null) {
+        throw new AmbariException("User " + userName + " doesn't exist");
+      }
+    }
+
+    if (isUserInGroup(userEntity, groupEntity)) {
+      MemberEntity memberEntity = null;
+      for (MemberEntity entity: userEntity.getMemberEntities()) {
+        if (entity.getGroup().equals(groupEntity)) {
+          memberEntity = entity;
+          break;
+        }
+      }
+      userEntity.getMemberEntities().remove(memberEntity);
+      groupEntity.getMemberEntities().remove(memberEntity);
+      userDAO.merge(userEntity);
+      groupDAO.merge(groupEntity);
+      memberDAO.remove(memberEntity);
+    } else {
+      throw new AmbariException("User " + userName + " is not present in group " + groupName);
+    }
+
+  }
+
   public synchronized boolean isUserCanBeRemoved(UserEntity userEntity){
     RoleEntity roleEntity = new RoleEntity();
     roleEntity.setRoleName(getAdminRole());
     Set<UserEntity> userEntitysSet = new HashSet<UserEntity>(userDAO.findAllLocalUsersByRole(roleEntity));
     return (userEntitysSet.contains(userEntity) && userEntitysSet.size() < 2) ? false : true;
-  }  
-  
+  }
+
+  /**
+   * Performs a check if given user belongs to given group.
+   *
+   * @param userEntity user entity
+   * @param groupEntity group entity
+   * @return true if user presents in group
+   */
+  private boolean isUserInGroup(UserEntity userEntity, GroupEntity groupEntity) {
+    for (MemberEntity memberEntity: userEntity.getMemberEntities()) {
+      if (memberEntity.getGroup().equals(groupEntity)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public String getUserRole() {
     return configuration.getConfigsMap().get(Configuration.USER_ROLE_NAME_KEY);
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index f12c5d1..4821e91 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -38,6 +38,8 @@ CREATE TABLE servicecomponentdesiredstate (component_name VARCHAR(255) NOT NULL,
 CREATE TABLE servicedesiredstate (cluster_id BIGINT NOT NULL, desired_host_role_mapping INTEGER NOT NULL, desired_stack_version VARCHAR(255) NOT NULL, desired_state VARCHAR(255) NOT NULL, service_name VARCHAR(255) NOT NULL, maintenance_state VARCHAR(32) NOT NULL DEFAULT 'ACTIVE', PRIMARY KEY (cluster_id, service_name));
 CREATE TABLE roles (role_name VARCHAR(255) NOT NULL, PRIMARY KEY (role_name));
 CREATE TABLE users (user_id INTEGER, create_time TIMESTAMP DEFAULT NOW(), ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, user_password VARCHAR(255), PRIMARY KEY (user_id));
+CREATE TABLE groups (group_id INTEGER, group_name VARCHAR(255) NOT NULL, ldap_group INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (group_id));
+CREATE TABLE members (member_id INTEGER, group_id INTEGER NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (member_id));
 CREATE TABLE execution_command (task_id BIGINT NOT NULL, command LONGBLOB, PRIMARY KEY (task_id));
 CREATE TABLE host_role_command (task_id BIGINT NOT NULL, attempt_count SMALLINT NOT NULL, event LONGTEXT NOT NULL, exitcode INTEGER NOT NULL, host_name VARCHAR(255) NOT NULL, last_attempt_time BIGINT NOT NULL, request_id BIGINT NOT NULL, role VARCHAR(255), role_command VARCHAR(255), stage_id BIGINT NOT NULL, start_time BIGINT NOT NULL, end_time BIGINT, status VARCHAR(255), std_error LONGBLOB, std_out LONGBLOB, structured_out LONGBLOB, command_detail VARCHAR(255), custom_command_name VARCHAR(255), PRIMARY KEY (task_id));
 CREATE TABLE role_success_criteria (role VARCHAR(255) NOT NULL, request_id BIGINT NOT NULL, stage_id BIGINT NOT NULL, success_factor DOUBLE NOT NULL, PRIMARY KEY (role, request_id, stage_id));
@@ -71,6 +73,10 @@ CREATE TABLE viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NO
 CREATE TABLE viewentity (id BIGINT NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user);
+ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group);
+ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_group_id FOREIGN KEY (group_id) REFERENCES groups (group_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_user_id FOREIGN KEY (user_id) REFERENCES users (user_id);
 ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterservices ADD CONSTRAINT FK_clusterservices_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterconfigmapping ADD CONSTRAINT clusterconfigmappingcluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
@@ -119,6 +125,8 @@ ALTER TABLE viewentity ADD CONSTRAINT FK_viewentity_view_name FOREIGN KEY (view_
 INSERT INTO ambari_sequences(sequence_name, value) values ('cluster_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('host_role_command_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('user_id_seq', 2);
+INSERT INTO ambari_sequences(sequence_name, value) values ('group_id_seq', 1);
+INSERT INTO ambari_sequences(sequence_name, value) values ('member_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('configgroup_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('requestschedule_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('resourcefilter_id_seq', 1);

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index 3620788..be2477b 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -28,6 +28,8 @@ CREATE TABLE servicecomponentdesiredstate (component_name VARCHAR2(255) NOT NULL
 CREATE TABLE servicedesiredstate (cluster_id NUMBER(19) NOT NULL, desired_host_role_mapping NUMBER(10) NOT NULL, desired_stack_version VARCHAR2(255) NULL, desired_state VARCHAR2(255) NOT NULL, service_name VARCHAR2(255) NOT NULL, maintenance_state VARCHAR2(32) NOT NULL, PRIMARY KEY (cluster_id, service_name));
 CREATE TABLE roles (role_name VARCHAR2(255) NOT NULL, PRIMARY KEY (role_name));
 CREATE TABLE users (user_id NUMBER(10) NOT NULL, create_time TIMESTAMP NULL, ldap_user NUMBER(10) DEFAULT 0, user_name VARCHAR2(255) NULL, user_password VARCHAR2(255) NULL, PRIMARY KEY (user_id));
+CREATE TABLE groups (group_id NUMBER(10) NOT NULL, group_name VARCHAR2(255) NOT NULL, ldap_group NUMBER(10) DEFAULT 0, PRIMARY KEY (group_id));
+CREATE TABLE members (member_id NUMBER(10), group_id NUMBER(10) NOT NULL, user_id NUMBER(10) NOT NULL, PRIMARY KEY (member_id));
 CREATE TABLE execution_command (task_id NUMBER(19) NOT NULL, command BLOB NULL, PRIMARY KEY (task_id));
 CREATE TABLE host_role_command (task_id NUMBER(19) NOT NULL, attempt_count NUMBER(5) NOT NULL, event CLOB NULL, exitcode NUMBER(10) NOT NULL, host_name VARCHAR2(255) NOT NULL, last_attempt_time NUMBER(19) NOT NULL, request_id NUMBER(19) NOT NULL, role VARCHAR2(255) NULL, role_command VARCHAR2(255) NULL, stage_id NUMBER(19) NOT NULL, start_time NUMBER(19) NOT NULL, end_time NUMBER(19), status VARCHAR2(255) NULL, std_error BLOB NULL, std_out BLOB NULL, structured_out BLOB NULL,  command_detail VARCHAR2(255) NULL, custom_command_name VARCHAR2(255) NULL, PRIMARY KEY (task_id));
 CREATE TABLE role_success_criteria (role VARCHAR2(255) NOT NULL, request_id NUMBER(19) NOT NULL, stage_id NUMBER(19) NOT NULL, success_factor NUMBER(19,4) NOT NULL, PRIMARY KEY (role, request_id, stage_id));
@@ -61,6 +63,10 @@ CREATE TABLE viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NO
 CREATE TABLE viewentity (id NUMBER(19) NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 ALTER TABLE users ADD CONSTRAINT UNQ_users_0 UNIQUE (user_name, ldap_user);
+ALTER TABLE groups ADD CONSTRAINT UNQ_groups_0 UNIQUE (group_name, ldap_group);
+ALTER TABLE members ADD CONSTRAINT UNQ_members_0 UNIQUE (group_id, user_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_group_id FOREIGN KEY (group_id) REFERENCES groups (group_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_user_id FOREIGN KEY (user_id) REFERENCES users (user_id);
 ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterservices ADD CONSTRAINT FK_clusterservices_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterconfigmapping ADD CONSTRAINT clusterconfigmappingcluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
@@ -107,6 +113,8 @@ ALTER TABLE viewentity ADD CONSTRAINT FK_viewentity_view_name FOREIGN KEY (view_
 
 INSERT INTO ambari_sequences(sequence_name, value) values ('host_role_command_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, value) values ('user_id_seq', 1);
+INSERT INTO ambari_sequences(sequence_name, value) values ('group_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, value) values ('member_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, value) values ('cluster_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, value) values ('configgroup_id_seq', 1);
 INSERT INTO ambari_sequences(sequence_name, value) values ('requestschedule_id_seq', 1);

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 8c1cf3b..d964d01 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -43,6 +43,10 @@ CREATE TABLE roles (role_name VARCHAR(255) NOT NULL, PRIMARY KEY (role_name));
 
 CREATE TABLE users (user_id INTEGER, ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, create_time TIMESTAMP DEFAULT NOW(), user_password VARCHAR(255), PRIMARY KEY (user_id), UNIQUE (ldap_user, user_name));
 
+CREATE TABLE groups (group_id INTEGER, group_name VARCHAR(255) NOT NULL, ldap_group INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (group_id), UNIQUE (ldap_group, group_name));
+
+CREATE TABLE members (member_id INTEGER, group_id INTEGER NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (member_id), UNIQUE(group_id, user_id));
+
 CREATE TABLE execution_command (command BYTEA, task_id BIGINT NOT NULL, PRIMARY KEY (task_id));
 
 CREATE TABLE host_role_command (task_id BIGINT NOT NULL, attempt_count SMALLINT NOT NULL, event VARCHAR(32000) NOT NULL, exitcode INTEGER NOT NULL, host_name VARCHAR(255) NOT NULL, last_attempt_time BIGINT NOT NULL, request_id BIGINT NOT NULL, role VARCHAR(255), stage_id BIGINT NOT NULL, start_time BIGINT NOT NULL, end_time BIGINT, status VARCHAR(255), std_error BYTEA, std_out BYTEA, structured_out BYTEA, role_command VARCHAR(255), command_detail VARCHAR(255), custom_command_name VARCHAR(255), PRIMARY KEY (task_id));
@@ -94,6 +98,8 @@ CREATE TABLE viewresource (view_name VARCHAR(255) NOT NULL, name VARCHAR(255) NO
 CREATE TABLE viewentity (id BIGINT NOT NULL, view_name VARCHAR(255) NOT NULL, view_instance_name VARCHAR(255) NOT NULL, class_name VARCHAR(255) NOT NULL, id_property VARCHAR(255), PRIMARY KEY(id));
 
 --------altering tables by creating foreign keys----------
+ALTER TABLE members ADD CONSTRAINT FK_members_group_id FOREIGN KEY (group_id) REFERENCES groups (group_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_user_id FOREIGN KEY (user_id) REFERENCES users (user_id);
 ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterservices ADD CONSTRAINT FK_clusterservices_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE clusterconfigmapping ADD CONSTRAINT clusterconfigmappingcluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
@@ -145,6 +151,10 @@ BEGIN;
   UNION ALL
   SELECT 'user_id_seq', 2
   UNION ALL
+  SELECT 'group_id_seq', 1
+  UNION ALL
+  SELECT 'member_id_seq', 1
+  UNION ALL
   SELECT 'host_role_command_id_seq', 1
   union all
   select 'configgroup_id_seq', 1

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
index 6ac6558..63f0957 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-EMBEDDED-CREATE.sql
@@ -67,6 +67,12 @@ GRANT ALL PRIVILEGES ON TABLE ambari.roles TO :username;
 CREATE TABLE ambari.users (user_id INTEGER, ldap_user INTEGER NOT NULL DEFAULT 0, user_name VARCHAR(255) NOT NULL, create_time TIMESTAMP DEFAULT NOW(), user_password VARCHAR(255), PRIMARY KEY (user_id), UNIQUE (ldap_user, user_name));
 GRANT ALL PRIVILEGES ON TABLE ambari.users TO :username;
 
+CREATE TABLE ambari.groups (group_id INTEGER, group_name VARCHAR(255) NOT NULL, ldap_group INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (group_id), UNIQUE (ldap_group, group_name));
+GRANT ALL PRIVILEGES ON TABLE ambari.groups TO :username;
+
+CREATE TABLE ambari.members (member_id INTEGER, group_id INTEGER NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (member_id), UNIQUE(group_id, user_id));
+GRANT ALL PRIVILEGES ON TABLE ambari.members TO :username;
+
 CREATE TABLE ambari.execution_command (command BYTEA, task_id BIGINT NOT NULL, PRIMARY KEY (task_id));
 GRANT ALL PRIVILEGES ON TABLE ambari.execution_command TO :username;
 
@@ -148,6 +154,8 @@ GRANT ALL PRIVILEGES ON TABLE ambari.viewresource TO :username;
 GRANT ALL PRIVILEGES ON TABLE ambari.viewentity TO :username;
 
 --------altering tables by creating foreign keys----------
+ALTER TABLE members ADD CONSTRAINT FK_members_group_id FOREIGN KEY (group_id) REFERENCES groups (group_id);
+ALTER TABLE members ADD CONSTRAINT FK_members_user_id FOREIGN KEY (user_id) REFERENCES users (user_id);
 ALTER TABLE ambari.clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES ambari.clusters (cluster_id);
 ALTER TABLE ambari.clusterservices ADD CONSTRAINT FK_clusterservices_cluster_id FOREIGN KEY (cluster_id) REFERENCES ambari.clusters (cluster_id);
 ALTER TABLE ambari.clusterconfigmapping ADD CONSTRAINT clusterconfigmappingcluster_id FOREIGN KEY (cluster_id) REFERENCES ambari.clusters (cluster_id);
@@ -200,6 +208,10 @@ INSERT INTO ambari.ambari_sequences (sequence_name, "value")
   UNION ALL
   SELECT 'user_id_seq', 2
   UNION ALL
+  SELECT 'group_id_seq', 1
+  UNION ALL
+  SELECT 'member_id_seq', 1
+  UNION ALL
   SELECT 'host_role_command_id_seq', 1
   union all
   select 'configgroup_id_seq', 1

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/META-INF/persistence.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/META-INF/persistence.xml b/ambari-server/src/main/resources/META-INF/persistence.xml
index 9f3dcac..3026cbc 100644
--- a/ambari-server/src/main/resources/META-INF/persistence.xml
+++ b/ambari-server/src/main/resources/META-INF/persistence.xml
@@ -26,6 +26,8 @@
     <class>org.apache.ambari.server.orm.entities.ServiceDesiredStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.RoleEntity</class>
     <class>org.apache.ambari.server.orm.entities.UserEntity</class>
+    <class>org.apache.ambari.server.orm.entities.GroupEntity</class>
+    <class>org.apache.ambari.server.orm.entities.MemberEntity</class>
     <class>org.apache.ambari.server.orm.entities.ExecutionCommandEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostRoleCommandEntity</class>
     <class>org.apache.ambari.server.orm.entities.RoleSuccessCriteriaEntity</class>

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/key_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/key_properties.json b/ambari-server/src/main/resources/key_properties.json
index c30a53b..68aa632 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -41,6 +41,13 @@
   "User": {
     "User": "Users/user_name"
   },
+  "Group": {
+    "Group": "Groups/group_name"
+  },
+  "Member": {
+    "Group": "MemberInfo/group_name",
+    "Member": "MemberInfo/user_name"
+  },
   "Stack": {
     "Stack": "Stacks/stack_name"
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/resources/properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/properties.json b/ambari-server/src/main/resources/properties.json
index b24aa0b..79be0e1 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -162,6 +162,16 @@
         "Users/ldap_user",
         "_"
     ],
+    "Group":[
+        "Groups/group_name",
+        "Groups/ldap_group",
+        "_"
+    ],
+    "Member":[
+        "MemberInfo/group_name",
+        "MemberInfo/user_name",
+        "_"
+    ],
     "Stack":[
         "Stacks/stack_name",
         "_"

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
new file mode 100644
index 0000000..2458920
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/GroupServiceTest.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.services;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
+import org.apache.ambari.server.api.services.serializers.ResultSerializer;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for GroupService.
+ */
+public class GroupServiceTest extends BaseServiceTest {
+
+  public List<ServiceTestInvocation> getTestInvocations() throws Exception {
+    List<ServiceTestInvocation> listInvocations = new ArrayList<ServiceTestInvocation>();
+
+    GroupService groupService;
+    Method m;
+    Object[] args;
+
+    //getGroups
+    groupService = new TestGroupService();
+    m = groupService.getClass().getMethod("getGroups", HttpHeaders.class, UriInfo.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, groupService, m, args, null));
+
+    //getGroup
+    groupService = new TestGroupService();
+    m = groupService.getClass().getMethod("getGroup", HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo(), "groupname"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, groupService, m, args, null));
+
+    //createGroup
+    groupService = new TestGroupService();
+    m = groupService.getClass().getMethod("createGroup", String.class, HttpHeaders.class, UriInfo.class);
+    args = new Object[] {"body", getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, groupService, m, args, "body"));
+
+    //createGroup
+    groupService = new TestGroupService();
+    m = groupService.getClass().getMethod("createGroup", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {"body", getHttpHeaders(), getUriInfo(), "groupname"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, groupService, m, args, "body"));
+
+    //deleteGroup
+    groupService = new TestGroupService();
+    m = groupService.getClass().getMethod("deleteGroup", HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo(), "groupname"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.DELETE, groupService, m, args, null));
+
+    return listInvocations;
+  }
+
+  private class TestGroupService extends GroupService {
+    @Override
+    ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
+      return getTestResource();
+    }
+
+    @Override
+    RequestFactory getRequestFactory() {
+      return getTestRequestFactory();
+    }
+
+    @Override
+    protected RequestBodyParser getBodyParser() {
+      return getTestBodyParser();
+    }
+
+    @Override
+    protected ResultSerializer getResultSerializer() {
+      return getTestResultSerializer();
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
new file mode 100644
index 0000000..17b2031
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/MemberServiceTest.java
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.api.services;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
+import org.apache.ambari.server.api.services.serializers.ResultSerializer;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for GroupService.
+ */
+public class MemberServiceTest extends BaseServiceTest {
+
+  public List<ServiceTestInvocation> getTestInvocations() throws Exception {
+    List<ServiceTestInvocation> listInvocations = new ArrayList<ServiceTestInvocation>();
+
+    MemberService memberService;
+    Method m;
+    Object[] args;
+
+    //createMember
+    memberService = new TestMemberService("engineering");
+    m = memberService.getClass().getMethod("createMember", String.class, HttpHeaders.class, UriInfo.class);
+    args = new Object[] {"body", getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, memberService, m, args, "body"));
+
+    //createMember
+    memberService = new TestMemberService("engineering");
+    m = memberService.getClass().getMethod("createMember", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {"body", getHttpHeaders(), getUriInfo(), "joe"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, memberService, m, args, "body"));
+
+    //deleteMember
+    memberService = new TestMemberService("engineering");
+    m = memberService.getClass().getMethod("deleteMember", HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo(), "joe"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.DELETE, memberService, m, args, null));
+
+    //getMembers
+    memberService = new TestMemberService("engineering");
+    m = memberService.getClass().getMethod("getMembers", HttpHeaders.class, UriInfo.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, memberService, m, args, null));
+
+    //getMember
+    memberService = new TestMemberService("engineering");
+    m = memberService.getClass().getMethod("getMember", HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo(), "joe"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, memberService, m, args, null));
+
+    return listInvocations;
+  }
+
+  private class TestMemberService extends MemberService {
+    public TestMemberService(String groupName) {
+      super(groupName);
+    }
+
+    @Override
+    ResourceInstance createResource(Type type, Map<Type, String> mapIds) {
+      return getTestResource();
+    }
+
+    @Override
+    RequestFactory getRequestFactory() {
+      return getTestRequestFactory();
+    }
+
+    @Override
+    protected RequestBodyParser getBodyParser() {
+      return getTestBodyParser();
+    }
+
+    @Override
+    protected ResultSerializer getResultSerializer() {
+      return getTestResultSerializer();
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java
index 83a1a49..b3f40a5 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java
@@ -30,7 +30,9 @@ import java.util.Set;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.ClusterRequest;
 import org.apache.ambari.server.controller.ConfigurationRequest;
+import org.apache.ambari.server.controller.GroupRequest;
 import org.apache.ambari.server.controller.HostRequest;
+import org.apache.ambari.server.controller.MemberRequest;
 import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.ServiceComponentHostRequest;
 import org.apache.ambari.server.controller.StackConfigurationRequest;
@@ -47,7 +49,6 @@ 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.PredicateBuilder;
-import org.apache.ambari.server.state.State;
 import org.easymock.EasyMock;
 import org.easymock.IArgumentMatcher;
 import org.junit.Assert;
@@ -297,7 +298,19 @@ public class AbstractResourceProviderTest {
       EasyMock.reportMatcher(new UserRequestSetMatcher(name));
       return null;
     }
-    
+
+    public static Set<GroupRequest> getGroupRequestSet(String name)
+    {
+      EasyMock.reportMatcher(new GroupRequestSetMatcher(name));
+      return null;
+    }
+
+    public static Set<MemberRequest> getMemberRequestSet(String groupname, String username)
+    {
+      EasyMock.reportMatcher(new MemberRequestSetMatcher(groupname, username));
+      return null;
+    }
+
     public static Set<StackConfigurationRequest> getStackConfigurationRequestSet(String stackName, String stackVersion,
         String serviceName, String propertyName)
     {
@@ -547,7 +560,81 @@ public class AbstractResourceProviderTest {
       stringBuffer.append("UserRequestSetMatcher(").append(userRequest).append(")");
     }
   }
-  
+
+  /**
+   * Matcher for a GroupRequest set containing a single request.
+   */
+  public static class GroupRequestSetMatcher extends HashSet<GroupRequest> implements IArgumentMatcher {
+
+    private final GroupRequest groupRequest;
+
+    public GroupRequestSetMatcher(String name) {
+      this.groupRequest = new GroupRequest(name);
+      add(this.groupRequest);
+    }
+
+    @Override
+    public boolean matches(Object o) {
+
+      if (!(o instanceof Set)) {
+        return false;
+      }
+
+      Set set = (Set) o;
+
+      if (set.size() != 1) {
+        return false;
+      }
+
+      Object request = set.iterator().next();
+
+      return request instanceof GroupRequest &&
+          eq(((GroupRequest) request).getGroupName(), groupRequest.getGroupName());
+    }
+
+    @Override
+    public void appendTo(StringBuffer stringBuffer) {
+      stringBuffer.append("GroupRequestSetMatcher(").append(groupRequest).append(")");
+    }
+  }
+
+  /**
+   * Matcher for a MemberRequest set containing a single request.
+   */
+  public static class MemberRequestSetMatcher extends HashSet<MemberRequest> implements IArgumentMatcher {
+
+    private final MemberRequest memberRequest;
+
+    public MemberRequestSetMatcher(String groupname, String username) {
+      this.memberRequest = new MemberRequest(groupname, username);
+      add(this.memberRequest);
+    }
+
+    @Override
+    public boolean matches(Object o) {
+
+      if (!(o instanceof Set)) {
+        return false;
+      }
+
+      Set set = (Set) o;
+
+      if (set.size() != 1) {
+        return false;
+      }
+
+      Object request = set.iterator().next();
+
+      return request instanceof MemberRequest &&
+          eq(((MemberRequest) request).getGroupName(), memberRequest.getGroupName()) &&
+          eq(((MemberRequest) request).getUserName(), memberRequest.getUserName());
+    }
+
+    @Override
+    public void appendTo(StringBuffer stringBuffer) {
+      stringBuffer.append("MemberRequestSetMatcher(").append(memberRequest).append(")");
+    }
+  }
 
   /**
    * Matcher for a Stack set containing a single request.

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/GroupResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/GroupResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/GroupResourceProviderTest.java
new file mode 100644
index 0000000..0cd1be1
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/GroupResourceProviderTest.java
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.GroupResponse;
+import org.apache.ambari.server.controller.RequestStatusResponse;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * GroupResourceProvider tests.
+ */
+public class GroupResourceProviderTest {
+  @Test
+  public void testCreateResources() throws Exception {
+    Resource.Type type = Resource.Type.Group;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    managementController.createGroups(AbstractResourceProviderTest.Matcher.getGroupRequestSet("engineering"));
+
+    // replay
+    replay(managementController, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.  add more maps for multiple creates
+    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
+
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    // add properties to the request map
+    properties.put(GroupResourceProvider.GROUP_GROUPNAME_PROPERTY_ID, "engineering");
+
+    propertySet.add(properties);
+
+    // create the request
+    Request request = PropertyHelper.getCreateRequest(propertySet, null);
+
+    provider.createResources(request);
+
+    // verify
+    verify(managementController, response);
+  }
+
+  @Test
+  public void testGetResources() throws Exception {
+    Resource.Type type = Resource.Type.Group;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+
+    Set<GroupResponse> allResponse = new HashSet<GroupResponse>();
+    allResponse.add(new GroupResponse("engineering", false));
+
+    // set expectations
+    expect(managementController.getGroups(AbstractResourceProviderTest.Matcher.getGroupRequestSet("engineering"))).
+        andReturn(allResponse).once();
+
+    // replay
+    replay(managementController);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    Set<String> propertyIds = new HashSet<String>();
+
+    propertyIds.add(GroupResourceProvider.GROUP_GROUPNAME_PROPERTY_ID);
+
+    Predicate predicate = new PredicateBuilder().property(GroupResourceProvider.GROUP_GROUPNAME_PROPERTY_ID).
+        equals("engineering").toPredicate();
+    Request request = PropertyHelper.getReadRequest(propertyIds);
+    Set<Resource> resources = provider.getResources(request, predicate);
+
+    Assert.assertEquals(1, resources.size());
+    for (Resource resource : resources) {
+      String groupName = (String) resource.getPropertyValue(GroupResourceProvider.GROUP_GROUPNAME_PROPERTY_ID);
+      Assert.assertEquals("engineering", groupName);
+    }
+
+    // verify
+    verify(managementController);
+  }
+
+  @Test
+  public void testUpdateResources() throws Exception {
+    // currently provider.updateResources() does nothing, nothing to test
+  }
+
+  @Test
+  public void testDeleteResources() throws Exception {
+    Resource.Type type = Resource.Type.Group;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    // set expectations
+    managementController.deleteGroups(AbstractResourceProviderTest.Matcher.getGroupRequestSet("engineering"));
+
+    // replay
+    replay(managementController, response);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    Predicate predicate = new PredicateBuilder().property(GroupResourceProvider.GROUP_GROUPNAME_PROPERTY_ID).
+        equals("engineering").toPredicate();
+    provider.deleteResources(predicate);
+
+    // verify
+    verify(managementController, response);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/MemberResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/MemberResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/MemberResourceProviderTest.java
new file mode 100644
index 0000000..4860ddd
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/MemberResourceProviderTest.java
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.RequestStatusResponse;
+import org.apache.ambari.server.controller.ResourceProviderFactory;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.junit.Test;
+
+/**
+ * MemberResourceProvider tests.
+ */
+public class MemberResourceProviderTest {
+  @Test
+  public void testCreateResources() throws Exception {
+    Resource.Type type = Resource.Type.Member;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+    ResourceProviderFactory resourceProviderFactory = createNiceMock(ResourceProviderFactory.class);
+    ResourceProvider memberResourceProvider = createNiceMock(MemberResourceProvider.class);
+
+    AbstractControllerResourceProvider.init(resourceProviderFactory);
+
+    expect(resourceProviderFactory.getMemberResourceProvider(anyObject(Set.class), anyObject(Map.class),
+        eq(managementController))).andReturn(memberResourceProvider).anyTimes();
+    // replay
+    replay(managementController, response, resourceProviderFactory, memberResourceProvider);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // add the property map to a set for the request.  add more maps for multiple creates
+    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
+
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    // add properties to the request map
+    properties.put(MemberResourceProvider.MEMBER_GROUP_NAME_PROPERTY_ID, "engineering");
+    properties.put(MemberResourceProvider.MEMBER_USER_NAME_PROPERTY_ID, "joe");
+
+    propertySet.add(properties);
+
+    // create the request
+    Request request = PropertyHelper.getCreateRequest(propertySet, null);
+
+    provider.createResources(request);
+
+    // verify
+    verify(managementController, response);
+  }
+
+  @Test
+  public void testUpdateResources() throws Exception {
+    // currently provider.updateResources() does nothing, nothing to test
+  }
+
+  @Test
+  public void testDeleteResources() throws Exception {
+    Resource.Type type = Resource.Type.Member;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+    ResourceProviderFactory resourceProviderFactory = createNiceMock(ResourceProviderFactory.class);
+    ResourceProvider memberResourceProvider = createNiceMock(MemberResourceProvider.class);
+
+    AbstractControllerResourceProvider.init(resourceProviderFactory);
+
+    // set expectations
+    expect(resourceProviderFactory.getMemberResourceProvider(anyObject(Set.class), anyObject(Map.class),
+        eq(managementController))).andReturn(memberResourceProvider).anyTimes();
+
+    // replay
+    replay(managementController, response, resourceProviderFactory, memberResourceProvider);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    PredicateBuilder builder = new PredicateBuilder();
+    builder.property(MemberResourceProvider.MEMBER_GROUP_NAME_PROPERTY_ID).equals("engineering");
+    Predicate predicate = builder.toPredicate();
+    provider.deleteResources(predicate);
+
+    // verify
+    verify(managementController, response);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/GroupDAOTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/GroupDAOTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/GroupDAOTest.java
new file mode 100644
index 0000000..27dab37
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/GroupDAOTest.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.dao;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertSame;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.entities.GroupEntity;
+
+/**
+ * GroupDAO unit tests.
+ */
+public class GroupDAOTest {
+
+  @Inject
+  DaoUtils daoUtils;
+
+  Provider<EntityManager> entityManagerProvider = createStrictMock(Provider.class);
+  EntityManager entityManager = createStrictMock(EntityManager.class);
+
+  @Before
+  public void init() {
+    reset(entityManagerProvider);
+    expect(entityManagerProvider.get()).andReturn(entityManager).atLeastOnce();
+    replay(entityManagerProvider);
+  }
+
+
+  @Test
+  public void testfindGroupByName() {
+    final String groupName = "engineering";
+    final GroupEntity entity = new GroupEntity();
+    entity.setGroupName(groupName);
+    TypedQuery<GroupEntity> query = createStrictMock(TypedQuery.class);
+
+    // set expectations
+    expect(entityManager.createNamedQuery(eq("groupByName"), eq(GroupEntity.class))).andReturn(query);
+    expect(query.setParameter("groupname", groupName)).andReturn(query);
+    expect(query.getSingleResult()).andReturn(entity);
+
+    replay(entityManager, query);
+
+    final GroupDAO dao = new GroupDAO();
+    dao.entityManagerProvider = entityManagerProvider;
+
+    final GroupEntity result = dao.findGroupByName(groupName);
+
+    assertSame(entity, result);
+
+    verify(entityManagerProvider, entityManager, query);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/TestUsers.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/TestUsers.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/TestUsers.java
index d5fc62a..aef0ea7 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/TestUsers.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/TestUsers.java
@@ -17,15 +17,22 @@
  */
 package org.apache.ambari.server.security.authorization;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.persist.PersistService;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.Properties;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.dao.GroupDAO;
+import org.apache.ambari.server.orm.dao.MemberDAO;
 import org.apache.ambari.server.orm.dao.RoleDAO;
 import org.apache.ambari.server.orm.dao.UserDAO;
 import org.apache.ambari.server.orm.entities.RoleEntity;
@@ -38,10 +45,10 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
-import java.util.List;
-import java.util.Properties;
-
-import static org.junit.Assert.*;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
 
 public class TestUsers {
   private Injector injector;
@@ -51,6 +58,10 @@ public class TestUsers {
   @Inject
   protected UserDAO userDAO;
   @Inject
+  protected GroupDAO groupDAO;
+  @Inject
+  protected MemberDAO memberDAO;
+  @Inject
   protected RoleDAO roleDAO;
   @Inject
   protected PasswordEncoder passwordEncoder;
@@ -96,6 +107,87 @@ public class TestUsers {
     assertNotSame(userEntity.getUserPassword(), userDAO.findLocalUserByName("user").getUserPassword());
   }
 
+  @Test
+  public void testCreateGroup() throws Exception {
+    final String groupName = "engineering";
+    users.createGroup(groupName);
+    assertNotNull(groupDAO.findGroupByName(groupName));
+  }
+
+  @Test
+  public void testGetGroup() throws Exception {
+    final String groupName = "engineering";
+    users.createGroup(groupName);
+
+    final Group group = users.getGroup(groupName);
+    assertNotNull(group);
+    assertEquals(false, group.isLdapGroup());
+    assertEquals(groupName, group.getGroupName());
+
+    assertNotNull(groupDAO.findGroupByName(groupName));
+  }
+
+  @Test
+  public void testGetAllGroups() throws Exception {
+    users.createGroup("one");
+    users.createGroup("two");
+
+    final List<Group> groupList = users.getAllGroups();
+
+    assertEquals(2, groupList.size());
+    assertEquals(2, groupDAO.findAll().size());
+  }
+
+  @Test
+  public void testRemoveGroup() throws Exception {
+    final String groupName = "engineering";
+    users.createGroup(groupName);
+    final Group group = users.getGroup(groupName);
+    assertEquals(1, users.getAllGroups().size());
+    users.removeGroup(group);
+    assertEquals(0, users.getAllGroups().size());
+  }
+
+  @Test
+  public void testAddMemberToGroup() throws Exception {
+    final String groupName = "engineering";
+    users.createGroup(groupName);
+    users.createUser("user", "user");
+    users.addMemberToGroup(groupName, "user");
+    assertEquals(1, groupDAO.findGroupByName(groupName).getMemberEntities().size());
+  }
+
+  @Test
+  public void testRemoveMemberFromGroup() throws Exception {
+    final String groupName = "engineering";
+    users.createGroup(groupName);
+    users.createUser("user", "user");
+    users.addMemberToGroup(groupName, "user");
+    assertEquals(1, groupDAO.findGroupByName(groupName).getMemberEntities().size());
+    users.removeMemberFromGroup(groupName, "user");
+    assertEquals(0, groupDAO.findGroupByName(groupName).getMemberEntities().size());
+  }
+
+  @Test
+  public void testGetGroupMembers() throws Exception {
+    final String groupNameTwoMembers = "engineering";
+    final String groupNameZeroMembers = "management";
+    users.createGroup(groupNameTwoMembers);
+    users.createGroup(groupNameZeroMembers);
+    users.createUser("user", "user");
+    users.createUser("admin", "admin");
+    users.addMemberToGroup(groupNameTwoMembers, "user");
+    users.addMemberToGroup(groupNameTwoMembers, "admin");
+
+    assertEquals(users.getGroupMembers(groupNameTwoMembers).size(), 2);
+    assertEquals(users.getGroupMembers(groupNameZeroMembers).size(), 0);
+  }
+
+  @Test
+  public void testGetGroupMembersUnexistingGroup() throws Exception {
+    assertEquals(users.getGroupMembers("unexisting"), null);
+  }
+
   @Test(expected = AmbariException.class)
   public void testModifyPassword() throws Exception {
     users.createUser("user", "user");
@@ -132,12 +224,12 @@ public class TestUsers {
 
     user = users.getLocalUser("admin");
     assertFalse(user.getRoles().contains(users.getAdminRole()));
-    
+
     user = users.getLocalUser("admin2");
     users.demoteAdmin(user);
 
   }
-  
+
   @Test(expected = AmbariException.class)
   public void testRemoveUser() throws Exception {
     users.createUser("admin", "admin");


[2/2] git commit: AMBARI-6343. Views : Admin - Add Group and Group Member Resources.

Posted by sw...@apache.org.
AMBARI-6343. Views : Admin - Add Group and Group Member Resources.


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

Branch: refs/heads/trunk
Commit: 00a4991eecb205f7936929e7010856661f0ba59e
Parents: 01e9e76
Author: Siddharth Wagle <sw...@hortonworks.com>
Authored: Tue Jul 1 13:42:32 2014 -0700
Committer: Siddharth Wagle <sw...@hortonworks.com>
Committed: Tue Jul 1 13:42:32 2014 -0700

----------------------------------------------------------------------
 .../api/resources/GroupResourceDefinition.java  |  49 ++++
 .../api/resources/MemberResourceDefinition.java |  47 ++++
 .../resources/ResourceInstanceFactoryImpl.java  |  10 +-
 .../server/api/services/GroupService.java       | 144 ++++++++++++
 .../server/api/services/MemberService.java      | 148 ++++++++++++
 .../controller/AmbariManagementController.java  | 209 +++++++++++------
 .../AmbariManagementControllerImpl.java         | 230 ++++++++++++++-----
 .../server/controller/ControllerModule.java     |   5 +
 .../ambari/server/controller/GroupRequest.java  |  38 +++
 .../ambari/server/controller/GroupResponse.java |  62 +++++
 .../ambari/server/controller/MemberRequest.java |  45 ++++
 .../server/controller/MemberResponse.java       |  64 ++++++
 .../controller/ResourceProviderFactory.java     |   8 +-
 .../AbstractControllerResourceProvider.java     |   6 +-
 .../internal/GroupResourceProvider.java         | 188 +++++++++++++++
 .../internal/MemberResourceProvider.java        | 191 +++++++++++++++
 .../ambari/server/controller/spi/Resource.java  |   4 +
 .../apache/ambari/server/orm/dao/GroupDAO.java  |  84 +++++++
 .../apache/ambari/server/orm/dao/MemberDAO.java |  70 ++++++
 .../ambari/server/orm/entities/GroupEntity.java | 120 ++++++++++
 .../server/orm/entities/MemberEntity.java       | 100 ++++++++
 .../ambari/server/orm/entities/UserEntity.java  |  27 ++-
 .../server/security/authorization/Group.java    |  53 +++++
 .../server/security/authorization/Member.java   |  53 +++++
 .../server/security/authorization/Users.java    | 166 ++++++++++++-
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |   8 +
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |   8 +
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |  10 +
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |  12 +
 .../src/main/resources/META-INF/persistence.xml |   2 +
 .../src/main/resources/key_properties.json      |   7 +
 .../src/main/resources/properties.json          |  10 +
 .../server/api/services/GroupServiceTest.java   | 100 ++++++++
 .../server/api/services/MemberServiceTest.java  | 104 +++++++++
 .../internal/AbstractResourceProviderTest.java  |  93 +++++++-
 .../internal/GroupResourceProviderTest.java     | 158 +++++++++++++
 .../internal/MemberResourceProviderTest.java    | 128 +++++++++++
 .../ambari/server/orm/dao/GroupDAOTest.java     |  83 +++++++
 .../security/authorization/TestUsers.java       | 114 ++++++++-
 39 files changed, 2797 insertions(+), 161 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/api/resources/GroupResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/GroupResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/GroupResourceDefinition.java
new file mode 100644
index 0000000..783e04b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/GroupResourceDefinition.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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.api.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Group Resource Definition.
+ */
+public class GroupResourceDefinition extends BaseResourceDefinition {
+  public GroupResourceDefinition() {
+    super(Resource.Type.Group);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "groups";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "group";
+  }
+
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+    final Set<SubResourceDefinition> subResourceDefinitions = new HashSet<SubResourceDefinition>();
+    subResourceDefinitions.add(new SubResourceDefinition(Resource.Type.Member));
+    return subResourceDefinitions;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/api/resources/MemberResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/MemberResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/MemberResourceDefinition.java
new file mode 100644
index 0000000..ad209f2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/MemberResourceDefinition.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.api.resources;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Member Resource Definition.
+ */
+public class MemberResourceDefinition extends BaseResourceDefinition {
+  public MemberResourceDefinition() {
+    super(Resource.Type.Member);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "members";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "member";
+  }
+
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+    return Collections.emptySet();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 e1428d8..9bf589f 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
@@ -117,6 +117,14 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new UserResourceDefinition();
         break;
 
+      case Group:
+        resourceDefinition = new GroupResourceDefinition();
+        break;
+
+      case Member:
+        resourceDefinition = new MemberResourceDefinition();
+        break;
+
       case Request:
         resourceDefinition = new RequestResourceDefinition();
         break;
@@ -217,7 +225,7 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
       case Blueprint:
         resourceDefinition = new BlueprintResourceDefinition();
         break;
-        
+
       case HostComponentProcess:
         resourceDefinition = new HostComponentProcessResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/api/services/GroupService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/GroupService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/GroupService.java
new file mode 100644
index 0000000..ee71719
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/GroupService.java
@@ -0,0 +1,144 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.api.services;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+import java.util.Collections;
+
+/**
+ * Service responsible for user groups requests.
+ */
+@Path("/groups/")
+public class GroupService extends BaseService {
+  /**
+   * Gets all groups.
+   * Handles: GET /groups requests.
+   *
+   * @param headers    http headers
+   * @param ui         uri info
+   */
+  @GET
+  @Produces("text/plain")
+  public Response getGroups(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createGroupResource(null));
+  }
+
+  /**
+   * Gets a single group.
+   * Handles: GET /groups/{groupName} requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @param groupName    the group name
+   * @return information regarding the specified group
+   */
+  @GET
+  @Path("{groupName}")
+  @Produces("text/plain")
+  public Response getGroup(@Context HttpHeaders headers, @Context UriInfo ui,
+      @PathParam("groupName") String groupName) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createGroupResource(groupName));
+  }
+
+  /**
+   * Creates a group.
+   * Handles: POST /groups requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @return information regarding the created group
+   */
+   @POST
+   @Produces("text/plain")
+   public Response createGroup(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createGroupResource(null));
+  }
+
+  /**
+   * Creates a group.
+   * Handles: POST /groups/{groupName} requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @param groupName    the group name
+   * @return information regarding the created group
+   *
+   * @deprecated Use requests to /groups instead.
+   */
+   @POST
+   @Deprecated
+   @Path("{groupName}")
+   @Produces("text/plain")
+   public Response createGroup(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                 @PathParam("groupName") String groupName) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createGroupResource(groupName));
+  }
+
+  /**
+   * Deletes a group.
+   * Handles:  DELETE /groups/{groupName} requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @param groupName    the group name
+   * @return information regarding the deleted group
+   */
+  @DELETE
+  @Path("{groupName}")
+  @Produces("text/plain")
+  public Response deleteGroup(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("groupName") String groupName) {
+    return handleRequest(headers, null, ui, Request.Type.DELETE, createGroupResource(groupName));
+  }
+
+  /**
+   * Get the members sub-resource.
+   *
+   * @param groupName    the group name
+   * @return the members service
+   */
+  @Path("{groupName}/members")
+  public MemberService getMemberHandler(@PathParam("groupName") String groupName) {
+    return new MemberService(groupName);
+  }
+
+  /**
+   * Create a group resource instance.
+   *
+   * @param groupName group name
+   *
+   * @return a group resource instance
+   */
+  private ResourceInstance createGroupResource(String groupName) {
+    return createResource(Resource.Type.Group,
+        Collections.singletonMap(Resource.Type.Group, groupName));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/api/services/MemberService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/MemberService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/MemberService.java
new file mode 100644
index 0000000..72e194f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/MemberService.java
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.api.services;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Service responsible for user membership requests.
+ */
+public class MemberService extends BaseService {
+  /**
+   * Name of the group.
+   */
+  private String groupName;
+
+  /**
+   * Constructor.
+   *
+   * @param groupName name of the group
+   */
+  public MemberService(String groupName) {
+    this.groupName = groupName;
+  }
+
+  /**
+   * Creates new members.
+   * Handles: POST /groups/{groupname}/members requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @return information regarding the created member
+   */
+   @POST
+   @Produces("text/plain")
+   public Response createMember(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createMemberResource(groupName, null));
+  }
+
+  /**
+   * Creates a new member.
+   * Handles: POST /groups/{groupname}/members/{username} requests.
+   *
+   * @param headers      http headers
+   * @param ui           uri info
+   * @param userName     the user name
+   * @return information regarding the created member
+   */
+   @POST
+   @Path("{userName}")
+   @Produces("text/plain")
+   public Response createMember(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                 @PathParam("userName") String userName) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createMemberResource(groupName, userName));
+  }
+
+   /**
+    * Deletes a member.
+    * Handles:  DELETE /groups/{groupname}/members/{username} requests.
+    *
+    * @param headers      http headers
+    * @param ui           uri info
+    * @param userName     the user name
+    * @return information regarding the deleted group
+    */
+   @DELETE
+   @Path("{userName}")
+   @Produces("text/plain")
+   public Response deleteMember(@Context HttpHeaders headers, @Context UriInfo ui,
+                                 @PathParam("userName") String userName) {
+     return handleRequest(headers, null, ui, Request.Type.DELETE, createMemberResource(groupName, userName));
+   }
+
+  /**
+   * Gets all members.
+   * Handles: GET /groups/{groupname}/members requests.
+   *
+   * @param headers    http headers
+   * @param ui         uri info
+   * @return information regarding all members
+   */
+  @GET
+  @Produces("text/plain")
+  public Response getMembers(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createMemberResource(groupName, null));
+  }
+
+  /**
+   * Gets member.
+   * Handles: GET /groups/{groupname}/members/{username} requests.
+   *
+   * @param headers    http headers
+   * @param ui         uri info
+   * @param userName   the user name
+   * @return information regarding the specific member
+   */
+  @GET
+  @Path("{userName}")
+  @Produces("text/plain")
+  public Response getMember(@Context HttpHeaders headers, @Context UriInfo ui,
+      @PathParam("userName") String userName) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createMemberResource(groupName, userName));
+  }
+
+  /**
+   * Create a member resource instance.
+   *
+   * @param groupName  group name
+   * @param userName   user name
+   *
+   * @return a member resource instance
+   */
+  private ResourceInstance createMemberResource(String groupName, String userName) {
+    final Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Group, groupName);
+    mapIds.put(Resource.Type.Member, userName);
+    return createResource(Resource.Type.Member, mapIds);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 a18468a..dce77c5 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
@@ -75,16 +75,34 @@ public interface AmbariManagementController {
    */
   public void createConfiguration(ConfigurationRequest request)
       throws AmbariException;
-  
+
   /**
    * Creates users.
-   * 
-   * @param requests the request objects which defines the user.
-   * 
+   *
+   * @param requests the request objects which define the user.
+   *
    * @throws AmbariException when the user cannot be created.
    */
   public void createUsers(Set<UserRequest> requests) throws AmbariException;
 
+  /**
+   * Creates groups.
+   *
+   * @param requests the request objects which define the groups.
+   *
+   * @throws AmbariException when the groups cannot be created.
+   */
+  public void createGroups(Set<GroupRequest> requests) throws AmbariException;
+
+  /**
+   * Creates members of the group.
+   *
+   * @param requests the request objects which define the members.
+   *
+   * @throws AmbariException when the members cannot be created.
+   */
+  public void createMembers(Set<MemberRequest> requests) throws AmbariException;
+
 
   // ----- Read -------------------------------------------------------------
 
@@ -140,15 +158,39 @@ public interface AmbariManagementController {
   /**
    * Gets the users identified by the given request objects.
    *
-   * @param requests  the request objects
-   * 
-   * @return  a set of user responses
-   * 
+   * @param requests the request objects
+   *
+   * @return a set of user responses
+   *
    * @throws AmbariException if the users could not be read
    */
   public Set<UserResponse> getUsers(Set<UserRequest> requests)
       throws AmbariException;
-  
+
+  /**
+   * Gets the user groups identified by the given request objects.
+   *
+   * @param requests the request objects
+   *
+   * @return a set of group responses
+   *
+   * @throws AmbariException if the groups could not be read
+   */
+  public Set<GroupResponse> getGroups(Set<GroupRequest> requests)
+      throws AmbariException;
+
+  /**
+   * Gets the group members identified by the given request objects.
+   *
+   * @param requests the request objects
+   *
+   * @return a set of member responses
+   *
+   * @throws AmbariException if the members could not be read
+   */
+  public Set<MemberResponse> getMembers(Set<MemberRequest> requests)
+      throws AmbariException;
+
 
   // ----- Update -----------------------------------------------------------
 
@@ -186,16 +228,25 @@ public interface AmbariManagementController {
    */
   public RequestStatusResponse updateHostComponents(
       Set<ServiceComponentHostRequest> requests, Map<String, String> requestProperties, boolean runSmokeTest) throws AmbariException;
-  
+
   /**
    * Updates the users specified.
-   * 
-   * @param requests  the users to modify
-   * 
-   * @throws  AmbariException if the resources cannot be updated
+   *
+   * @param requests the users to modify
+   *
+   * @throws AmbariException if the resources cannot be updated
    */
   public void updateUsers(Set<UserRequest> requests) throws AmbariException;
 
+  /**
+   * Updates the groups specified.
+   *
+   * @param requests the groups to modify
+   *
+   * @throws AmbariException if the resources cannot be updated
+   */
+  public void updateGroups(Set<GroupRequest> requests) throws AmbariException;
+
 
   // ----- Delete -----------------------------------------------------------
 
@@ -219,16 +270,34 @@ public interface AmbariManagementController {
    */
   public RequestStatusResponse deleteHostComponents(
       Set<ServiceComponentHostRequest> requests) throws AmbariException;
-  
+
   /**
    * Deletes the users specified.
-   * 
-   * @param requests  the users to delete
-   * 
-   * @throws  AmbariException if the resources cannot be deleted
+   *
+   * @param requests the users to delete
+   *
+   * @throws AmbariException if the resources cannot be deleted
    */
   public void deleteUsers(Set<UserRequest> requests) throws AmbariException;
-  
+
+  /**
+   * Deletes the user groups specified.
+   *
+   * @param requests the groups to delete
+   *
+   * @throws AmbariException if the resources cannot be deleted
+   */
+  public void deleteGroups(Set<GroupRequest> requests) throws AmbariException;
+
+  /**
+   * Deletes the group members specified.
+   *
+   * @param requests the members to delete
+   *
+   * @throws AmbariException if the resources cannot be deleted
+   */
+  public void deleteMembers(Set<MemberRequest> requests) throws AmbariException;
+
   /**
    * Create the action defined by the attributes in the given request object.
    *
@@ -239,14 +308,14 @@ public interface AmbariManagementController {
    */
   public RequestStatusResponse createAction(ExecuteActionRequest actionRequest, Map<String, String> requestProperties)
       throws AmbariException;
-  
+
   /**
    * Get supported stacks.
-   * 
+   *
    * @param requests the stacks
-   * 
+   *
    * @return a set of stacks responses
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<StackResponse> getStacks(Set<StackRequest> requests) throws AmbariException;
@@ -258,105 +327,105 @@ public interface AmbariManagementController {
    * @throws AmbariException if
    */
   public RequestStatusResponse updateStacks() throws AmbariException;
-  
+
   /**
    * Get supported stacks versions.
-   * 
+   *
    * @param requests the stacks versions
-   * 
+   *
    * @return a set of stacks versions responses
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<StackVersionResponse> getStackVersions(Set<StackVersionRequest> requests) throws AmbariException;
 
-  
+
   /**
    * Get repositories by stack name, version and operating system.
-   * 
-   * @param requests the repositories 
-   * 
+   *
+   * @param requests the repositories
+   *
    * @return a set of repositories
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<RepositoryResponse> getRepositories(Set<RepositoryRequest> requests) throws AmbariException;
 
   /**
    * Updates repositories by stack name, version and operating system.
-   * 
+   *
    * @param requests the repositories
-   * 
+   *
    * @throws AmbariException
    */
   void updateRespositories(Set<RepositoryRequest> requests) throws AmbariException;
 
-  
+
   /**
    * Get repositories by stack name, version.
-   * 
-   * @param requests the services 
-   * 
+   *
+   * @param requests the services
+   *
    * @return a set of services
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<StackServiceResponse> getStackServices(Set<StackServiceRequest> requests) throws AmbariException;
 
-  
+
   /**
    * Get configurations by stack name, version and service.
-   * 
-   * @param requests the configurations 
-   * 
+   *
+   * @param requests the configurations
+   *
    * @return a set of configurations
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<StackConfigurationResponse> getStackConfigurations(Set<StackConfigurationRequest> requests) throws AmbariException;
-  
-  
+
+
   /**
    * Get components by stack name, version and service.
-   * 
-   * @param requests the components 
-   * 
+   *
+   * @param requests the components
+   *
    * @return a set of components
-   * 
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<StackServiceComponentResponse> getStackComponents(Set<StackServiceComponentRequest> requests) throws AmbariException;
-  
-  
+
+
   /**
    * Get operating systems by stack name, version.
-   * 
-   * @param requests the operating systems 
-   * 
-   * @return a set of operating systems 
-   * 
+   *
+   * @param requests the operating systems
+   *
+   * @return a set of operating systems
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<OperatingSystemResponse> getStackOperatingSystems(Set<OperatingSystemRequest> requests) throws AmbariException;
 
   /**
    * Get all top-level services of Ambari, not related to certain cluster.
-   * 
-   * @param requests the top-level services 
-   * 
-   * @return a set of top-level services 
-   * 
+   *
+   * @param requests the top-level services
+   *
+   * @return a set of top-level services
+   *
    * @throws  AmbariException if the resources cannot be read
    */
-  
+
   public Set<RootServiceResponse> getRootServices(Set<RootServiceRequest> requests) throws AmbariException;
   /**
    * Get all components of top-level services of Ambari, not related to certain cluster.
-   * 
-   * @param requests the components of top-level services 
-   * 
-   * @return a set of components 
-   * 
+   *
+   * @param requests the components of top-level services
+   *
+   * @return a set of components
+   *
    * @throws  AmbariException if the resources cannot be read
    */
   public Set<RootServiceComponentResponse> getRootServiceComponents(Set<RootServiceComponentRequest> requests) throws AmbariException;
@@ -561,4 +630,4 @@ public interface AmbariManagementController {
   public MaintenanceState getEffectiveMaintenanceState(ServiceComponentHost sch)
       throws AmbariException;
 }
-  
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 1b81ce9..e392a71 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
@@ -85,6 +85,7 @@ import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
+import org.apache.ambari.server.security.authorization.Group;
 import org.apache.ambari.server.security.authorization.User;
 import org.apache.ambari.server.security.authorization.Users;
 import org.apache.ambari.server.stageplanner.RoleGraph;
@@ -197,7 +198,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
   final private static String JDK_RESOURCE_LOCATION =
       "/resources/";
-  
+
   final private static int REPO_URL_CONNECT_TIMEOUT = 3000;
   final private static int REPO_URL_READ_TIMEOUT = 2000;
 
@@ -234,19 +235,19 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       } else {
         this.masterProtocol = "http";
         this.masterPort = configs.getClientApiPort();
-      }  
+      }
       this.jdkResourceUrl = getAmbariServerURI(JDK_RESOURCE_LOCATION);
       this.javaHome = configs.getJavaHome();
       this.jdkName = configs.getJDKName();
       this.jceName = configs.getJCEName();
       this.ojdbcUrl = getAmbariServerURI(JDK_RESOURCE_LOCATION + "/" + configs.getOjdbcJarName());
       this.mysqljdbcUrl = getAmbariServerURI(JDK_RESOURCE_LOCATION + "/" + configs.getMySQLJarName());
-     
+
       this.serverDB = configs.getServerDBName();
     } else {
       this.masterProtocol = null;
       this.masterPort = null;
-      
+
       this.jdkResourceUrl = null;
       this.javaHome = null;
       this.jdkName = null;
@@ -256,17 +257,17 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       this.serverDB = null;
     }
   }
-  
+
   public String getAmbariServerURI(String path) {
     if(masterProtocol==null || masterHostname==null || masterPort==null)
       return null;
-    
+
     URIBuilder uriBuilder = new URIBuilder();
     uriBuilder.setScheme(masterProtocol);
     uriBuilder.setHost(masterHostname);
     uriBuilder.setPort(masterPort);
     uriBuilder.setPath(path);
-    
+
     return uriBuilder.toString();
   }
 
@@ -563,8 +564,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         }
       }
     }
-  } 
-  
+  }
+
   private void setRestartRequiredServices(
           Service service, String hostName) throws AmbariException {
 
@@ -661,6 +662,54 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
   }
 
+  @Override
+  public void createGroups(Set<GroupRequest> requests) throws AmbariException {
+    for (GroupRequest request : requests) {
+      if (StringUtils.isBlank(request.getGroupName())) {
+        throw new AmbariException("Group name must be supplied.");
+      }
+      final Group group = users.getGroup(request.getGroupName());
+      if (group != null) {
+        throw new AmbariException("Group already exists.");
+      }
+      users.createGroup(request.getGroupName());
+    }
+  }
+
+  @Override
+  public void createMembers(Set<MemberRequest> requests) throws AmbariException {
+    for (MemberRequest request : requests) {
+      if (StringUtils.isBlank(request.getGroupName()) || StringUtils.isBlank(request.getUserName())) {
+        throw new AmbariException("Both group name and user name must be supplied.");
+      }
+      users.addMemberToGroup(request.getGroupName(), request.getUserName());
+    }
+  }
+
+  @Override
+  public Set<MemberResponse> getMembers(Set<MemberRequest> requests)
+      throws AmbariException {
+    final Set<MemberResponse> responses = new HashSet<MemberResponse>();
+    for (MemberRequest request: requests) {
+      LOG.debug("Received a getMembers request, " + request.toString());
+      final Group group = users.getGroup(request.getGroupName());
+      if (null == group) {
+        if (requests.size() == 1) {
+          // only throw exception if there is a single request
+          // if there are multiple requests, this indicates an OR predicate
+          throw new ObjectNotFoundException("Cannot find group '"
+              + request.getGroupName() + "'");
+        }
+      } else {
+        for (User user: users.getGroupMembers(group.getGroupName())) {
+          final MemberResponse response = new MemberResponse(group.getGroupName(), user.getUserName());
+          responses.add(response);
+        }
+      }
+    }
+    return responses;
+  }
+
   private Stage createNewStage(long id, Cluster cluster, long requestId, String requestContext, String clusterHostInfo) {
     String logDir = BASE_LOG_DIR + File.pathSeparator + requestId;
     Stage stage =
@@ -672,7 +721,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     return stage;
   }
 
-
   private Set<ClusterResponse> getClusters(ClusterRequest request)
       throws AmbariException {
 
@@ -684,7 +732,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           + ", clusterId=" + request.getClusterId()
           + ", stackInfo=" + request.getStackVersion());
     }
-    
+
     if (request.getClusterName() != null) {
       Cluster c = clusters.getCluster(request.getClusterName());
       ClusterResponse cr = c.convertToResponse();
@@ -900,7 +948,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
             if (host == null) {
               throw new HostNotFoundException(cluster.getClusterName(), sch.getHostName());
             }
-            
+
             r.setMaintenanceState(maintenanceStateHelper.getEffectiveState(sch, host).name());
             response.add(r);
           }
@@ -909,14 +957,14 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
     return response;
   }
-  
+
   @Override
   public MaintenanceState getEffectiveMaintenanceState(ServiceComponentHost sch)
       throws AmbariException {
-    
+
     return maintenanceStateHelper.getEffectiveState(sch);
   }
-  
+
 
   private Set<ConfigurationResponse> getConfigurations(
       ConfigurationRequest request) throws AmbariException {
@@ -1008,7 +1056,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       ConfigurationRequest cr = request.getDesiredConfig();
 
       Config oldConfig = cluster.getDesiredConfigByType(cr.getType());
-      
+
       if (null != cr.getProperties()) {
         // !!! empty property sets are supported, and need to be able to use
         // previously-defined configs (revert)
@@ -1016,11 +1064,11 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         if (null == all ||                              // none set
             !all.containsKey(cr.getVersionTag()) ||     // tag not set
             cr.getProperties().size() > 0) {            // properties to set
-          
+
           LOG.info(MessageFormat.format("Applying configuration with tag ''{0}'' to cluster ''{1}''",
               cr.getVersionTag(),
               request.getClusterName()));
-  
+
           cr.setClusterName(cluster.getClusterName());
           createConfiguration(cr);
         }
@@ -1029,7 +1077,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       Config baseConfig = cluster.getConfig(cr.getType(), cr.getVersionTag());
       if (null != baseConfig) {
         String authName = getAuthName();
-        
+
         if (cluster.addDesiredConfig(authName, baseConfig)) {
           Logger logger = LoggerFactory.getLogger("configchange");
           logger.info("cluster '" + request.getClusterName() + "' "
@@ -1056,7 +1104,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       clusters.mapHostsToCluster(
           request.getHostNames(), request.getClusterName());
     }
-    
+
     // set the provisioning state of the cluster
     if (null != request.getProvisioningState()) {
       State oldProvisioningState = cluster.getProvisioningState();
@@ -1077,12 +1125,12 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       if (provisioningState != oldProvisioningState) {
         boolean isStateTransitionValid = State.isValidDesiredStateTransition(
             oldProvisioningState, provisioningState);
-        
+
         if (!isStateTransitionValid) {
           LOG.warn(
               "Invalid cluster provisioning state {} cannot be set on the cluster {} because the current state is {}",
               provisioningState, request.getClusterName(), oldProvisioningState);
-          
+
           throw new AmbariException("Invalid transition for"
               + " cluster provisioning state" + ", clusterName="
               + cluster.getClusterName() + ", clusterId="
@@ -1091,10 +1139,10 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
               + provisioningState);
         }
       }
-      
+
       cluster.setProvisioningState(provisioningState);
     }
-    
+
     return null;
   }
 
@@ -1694,7 +1742,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
                 requestParameters = new HashMap<String, String>();
               requestParameters.put(keyName, requestProperties.get(keyName));
             }
-            
+
             createHostAction(cluster, stage, scHost, configurations, configTags,
               roleCommand, requestParameters, event);
           }
@@ -1746,7 +1794,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     hostLevelParams.put(ORACLE_JDBC_URL, getOjdbcUrl());
     hostLevelParams.put(DB_DRIVER_FILENAME, configs.getMySQLJarName());
     hostLevelParams.putAll(getRcaParameters());
-    
+
     return hostLevelParams;
   }
 
@@ -1857,7 +1905,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     Set<State> seenNewStates = new HashSet<State>();
     Map<ServiceComponentHost, State> directTransitionScHosts = new HashMap<ServiceComponentHost, State>();
     Set<String> maintenanceClusters = new HashSet<String>();
-    
+
     for (ServiceComponentHostRequest request : requests) {
       validateServiceComponentHostRequest(request);
 
@@ -1921,13 +1969,13 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
               + " desired state, desiredState=" + newState.toString());
         }
       }
-      
+
       if (null != request.getMaintenanceState()) {
         MaintenanceStateHelper psh = injector.getInstance(MaintenanceStateHelper.class);
-        
+
         MaintenanceState newMaint = MaintenanceState.valueOf(request.getMaintenanceState());
         MaintenanceState oldMaint = psh.getEffectiveState(sch);
-        
+
         if (newMaint != oldMaint) {
           if (sc.isClientComponent()) {
             throw new IllegalArgumentException("Invalid arguments, cannot set " +
@@ -1938,7 +1986,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
               "maintenance state to one of " + EnumSet.of(MaintenanceState.OFF, MaintenanceState.ON));
           } else {
             sch.setMaintenanceState(newMaint);
-            
+
             maintenanceClusters.add(sch.getClusterName());
           }
         }
@@ -2062,7 +2110,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         throw new AmbariException("Internal error - not supported transition", e);
       }
     }
-    
+
     if (maintenanceClusters.size() > 0) {
       try {
         maintenanceStateHelper.createRequests(this, requestProperties, maintenanceClusters);
@@ -2158,7 +2206,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       for (String role : roolesToDelete) {
         users.removeRoleFromUser(u, role);
         u.getRoles().remove(role);
-      }      
+      }
       roolesToAdd.removeAll(u.getRoles());
       for (String role : roolesToAdd) {
         users.addRoleToUser(u, role);
@@ -2191,7 +2239,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       Set<ServiceComponentHostRequest> requests) throws AmbariException {
 
     Set<ServiceComponentHostRequest> expanded = new HashSet<ServiceComponentHostRequest>();
-    
+
     // if any request are for the whole host, they need to be expanded
     for (ServiceComponentHostRequest request : requests) {
       if (null == request.getComponentName()) {
@@ -2200,7 +2248,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           throw new IllegalArgumentException("Cluster name and hostname must be specified.");
         }
         Cluster cluster = clusters.getCluster(request.getClusterName());
-        
+
         for (ServiceComponentHost sch : cluster.getServiceComponentHosts(request.getHostname())) {
           ServiceComponentHostRequest schr = new ServiceComponentHostRequest(request.getClusterName(),
               sch.getServiceName(), sch.getServiceComponentName(), sch.getHostName(), null);
@@ -2211,9 +2259,9 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         expanded.add(request);
       }
     }
-    
+
     Map<ServiceComponent, Set<ServiceComponentHost>> safeToRemoveSCHs = new HashMap<ServiceComponent, Set<ServiceComponentHost>>();
-    
+
     for (ServiceComponentHostRequest request : expanded) {
 
       validateServiceComponentHostRequest(request);
@@ -2256,7 +2304,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       }
 
       setRestartRequiredServices(service, request.getHostname());
-              
+
       if (!safeToRemoveSCHs.containsKey(component)) {
         safeToRemoveSCHs.put(component, new HashSet<ServiceComponentHost>());
       }
@@ -2276,7 +2324,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
     return null;
   }
-  
+
   @Override
   public void deleteUsers(Set<UserRequest> requests)
     throws AmbariException {
@@ -2292,6 +2340,25 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
   }
 
+  @Override
+  public void deleteGroups(Set<GroupRequest> requests) throws AmbariException {
+    for (GroupRequest request: requests) {
+      LOG.debug("Received a delete group request, groupname=" + request.getGroupName());
+      final Group group = users.getGroup(request.getGroupName());
+      if (group != null) {
+        users.removeGroup(group);
+      }
+    }
+  }
+
+  @Override
+  public void deleteMembers(java.util.Set<MemberRequest> requests) throws AmbariException {
+    for (MemberRequest request : requests) {
+      LOG.debug("Received a delete member request, " + request);
+      users.removeMemberFromGroup(request.getGroupName(), request.getUserName());
+    }
+  }
+
   /**
    * Get a request response for the given request ids.  Note that this method
    * fully populates a request resource including the set of task sub-resources
@@ -2470,6 +2537,47 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     return responses;
   }
 
+  @Override
+  public Set<GroupResponse> getGroups(Set<GroupRequest> requests)
+      throws AmbariException {
+    final Set<GroupResponse> responses = new HashSet<GroupResponse>();
+    for (GroupRequest request: requests) {
+      LOG.debug("Received a getGroups request, groupRequest=" + request.toString());
+      // get them all
+      if (null == request.getGroupName()) {
+        for (Group group: users.getAllGroups()) {
+          final GroupResponse response = new GroupResponse(group.getGroupName(), group.isLdapGroup());
+          responses.add(response);
+        }
+      } else {
+        final Group group = users.getGroup(request.getGroupName());
+        if (null == group) {
+          if (requests.size() == 1) {
+            // only throw exception if there is a single request
+            // if there are multiple requests, this indicates an OR predicate
+            throw new ObjectNotFoundException("Cannot find group '"
+                + request.getGroupName() + "'");
+          }
+        } else {
+          final GroupResponse response = new GroupResponse(group.getGroupName(), group.isLdapGroup());
+          responses.add(response);
+        }
+      }
+    }
+    return responses;
+  }
+
+  @Override
+  public void updateGroups(Set<GroupRequest> requests) throws AmbariException {
+    for (GroupRequest request: requests) {
+      final Group group = users.getGroup(request.getGroupName());
+      if (group == null) {
+        continue;
+      }
+      // currently no group updates are supported
+    }
+  }
+
   private String getClientHostForRunningAction(Cluster cluster,
       Service service) throws AmbariException {
     StackId stackId = service.getDesiredStackVersion();
@@ -2527,7 +2635,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       Map<String, String> requestProperties)
       throws AmbariException {
     String clusterName = actionRequest.getClusterName();
-        
+
     String requestContext = "";
 
     if (requestProperties != null) {
@@ -2546,25 +2654,25 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         + ", clusterName=" + actionRequest.getClusterName()
         + ", request=" + actionRequest.toString());
     }
-    
+
     ActionExecutionContext actionExecContext = getActionExecutionContext(actionRequest);
     if (actionRequest.isCommand()) {
       customCommandExecutionHelper.validateAction(actionRequest);
     } else {
       actionExecutionHelper.validateAction(actionRequest);
     }
-    
+
     Map<String, String> params = new HashMap<String, String>();
     Map<String, Set<String>> clusterHostInfo = new HashMap<String, Set<String>>();
     String clusterHostInfoJson = "{}";
-    
+
     if (null != cluster) {
       clusterHostInfo = StageUtils.getClusterHostInfo(
         clusters.getHostsForCluster(cluster.getClusterName()), cluster);
       params = createDefaultHostParams(cluster);
       clusterHostInfoJson = StageUtils.getGson().toJson(clusterHostInfo);
     }
-    
+
     Stage stage = createNewStage(0, cluster, actionManager.getNextRequestId(), requestContext, clusterHostInfoJson);
 
     if (actionRequest.isCommand()) {
@@ -2573,7 +2681,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     } else {
       actionExecutionHelper.addExecutionCommandsToStage(actionExecContext, stage, params);
     }
-    
+
     RoleGraph rg = null;
     if (null != cluster) {
       RoleCommandOrder rco = getRoleCommandOrder(cluster);
@@ -2581,10 +2689,10 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     } else {
       rg = new RoleGraph();
     }
-    
-    rg.build(stage);    
+
+    rg.build(stage);
     List<Stage> stages = rg.getStages();
-    
+
     if (stages != null && !stages.isEmpty()) {
       actionManager.sendActions(stages, actionRequest);
       return getRequestStatusResponse(stage.getRequestId());
@@ -2671,7 +2779,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
     return response;
   }
-  
+
   private Set<RepositoryResponse> getRepositories(RepositoryRequest request) throws AmbariException {
 
     String stackName = request.getStackName();
@@ -2693,22 +2801,22 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       RepositoryInfo repository = this.ambariMetaInfo.getRepository(stackName, stackVersion, osType, repoId);
       response = Collections.singleton(repository.convertToResponse());
     }
-    
+
     return response;
   }
-  
+
   @Override
   public void updateRespositories(Set<RepositoryRequest> requests) throws AmbariException {
     for (RepositoryRequest rr : requests) {
       if (null == rr.getStackName() || rr.getStackName().isEmpty())
         throw new AmbariException("Stack name must be specified.");
-      
+
       if (null == rr.getStackVersion() || rr.getStackVersion().isEmpty())
         throw new AmbariException("Stack version must be specified.");
-      
+
       if (null == rr.getOsType() || rr.getOsType().isEmpty())
         throw new AmbariException("OS type must be specified.");
-      
+
       if (null == rr.getRepoId() || rr.getRepoId().isEmpty())
         throw new AmbariException("Repo ID must be specified.");
 
@@ -2725,19 +2833,19 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           String repoName = repositoryInfo.getRepoName();
 
           boolean bFound = false;
-          
+
           String[] suffixes = configs.getRepoValidationSuffixes(rr.getOsType());
           for (int i = 0; i < suffixes.length && !bFound; i++) {
             String suffix = String.format(suffixes[i], repoName);
             String spec = rr.getBaseUrl();
-            
+
             if (spec.charAt(spec.length()-1) != '/' && suffix.charAt(0) != '/')
               spec = rr.getBaseUrl() + "/" + suffix;
             else if (spec.charAt(spec.length()-1) == '/' && suffix.charAt(0) == '/')
               spec = rr.getBaseUrl() + suffix.substring(1);
             else
               spec = rr.getBaseUrl() + suffix;
-            
+
             try {
               IOUtils.readLines(usp.readFrom(spec));
               bFound = true;
@@ -2745,7 +2853,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
               LOG.error("IOException loading the base URL", ioe);
             }
           }
-            
+
           if (bFound) {
             ambariMetaInfo.updateRepoBaseURL(rr.getStackName(),
                 rr.getStackVersion(), rr.getOsType(), rr.getRepoId(),
@@ -3008,7 +3116,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
     return response;
   }
-  
+
   @Override
   public String getAuthName() {
     return AuthorizationHelper.getAuthenticatedName(configs.getAnonymousAuditName());
@@ -3137,7 +3245,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   public String getMysqljdbcUrl() {
     return mysqljdbcUrl;
   }
-  
+
   public Map<String, String> getRcaParameters() {
 
     String hostName = StageUtils.getHostName();
@@ -3157,5 +3265,5 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
     return rcaParameters;
   }
-  
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 c132b1d..fc049a4 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
@@ -26,6 +26,7 @@ import com.google.inject.assistedinject.FactoryModuleBuilder;
 import com.google.inject.name.Names;
 import com.google.inject.persist.PersistModule;
 import com.google.inject.persist.jpa.AmbariJpaPersistModule;
+
 import org.apache.ambari.server.actionmanager.ActionDBAccessor;
 import org.apache.ambari.server.actionmanager.ActionDBAccessorImpl;
 import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
@@ -37,6 +38,7 @@ import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.internal.ComponentResourceProvider;
 import org.apache.ambari.server.controller.internal.HostComponentResourceProvider;
 import org.apache.ambari.server.controller.internal.HostResourceProvider;
+import org.apache.ambari.server.controller.internal.MemberResourceProvider;
 import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.orm.DBAccessor;
@@ -74,10 +76,12 @@ import org.apache.ambari.server.state.scheduler.RequestExecutionImpl;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostImpl;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.StandardPasswordEncoder;
+
 import java.security.SecureRandom;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
+
 import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_JDBC_DDL_FILE;
 import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_ONLY;
 import static org.eclipse.persistence.config.PersistenceUnitProperties.CREATE_OR_EXTEND;
@@ -252,6 +256,7 @@ public class ControllerModule extends AbstractModule {
         .implement(ResourceProvider.class, Names.named("hostComponent"), HostComponentResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("service"), ServiceResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("component"), ComponentResourceProvider.class)
+        .implement(ResourceProvider.class, Names.named("member"), MemberResourceProvider.class)
         .build(ResourceProviderFactory.class));
 
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupRequest.java
new file mode 100644
index 0000000..1bc18cc
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupRequest.java
@@ -0,0 +1,38 @@
+/**
+ * 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;
+
+/**
+ * Represents a group maintenance request.
+ */
+public class GroupRequest {
+  private final String groupName;
+
+  public GroupRequest(String groupName) {
+    this.groupName = groupName;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  @Override
+  public String toString() {
+    return "GroupRequest [groupName=" + groupName + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupResponse.java
new file mode 100644
index 0000000..ef28f61
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/GroupResponse.java
@@ -0,0 +1,62 @@
+/**
+ * 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;
+
+/**
+ * Represents a user group maintenance response.
+ */
+public class GroupResponse {
+  private final String groupName;
+  private final boolean ldapGroup;
+
+  public GroupResponse(String groupName, boolean ldapGroup) {
+    this.groupName = groupName;
+    this.ldapGroup = ldapGroup;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public boolean isLdapGroup() {
+    return ldapGroup;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (o == null || getClass() != o.getClass())
+      return false;
+
+    GroupResponse that = (GroupResponse) o;
+
+    if (groupName != null ? !groupName.equals(that.groupName)
+        : that.groupName != null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = groupName != null ? groupName.hashCode() : 0;
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberRequest.java
new file mode 100644
index 0000000..0245f36
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberRequest.java
@@ -0,0 +1,45 @@
+/**
+ * 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;
+
+/**
+ * Represents a member maintenance request.
+ */
+public class MemberRequest {
+  private final String groupName;
+  private final String userName;
+
+  public MemberRequest(String groupName, String userName) {
+    this.groupName = groupName;
+    this.userName  = userName;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  @Override
+  public String toString() {
+    return "MemberRequest [groupName=" + groupName + ", userName=" + userName
+        + "]";
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberResponse.java
new file mode 100644
index 0000000..3dc6558
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/MemberResponse.java
@@ -0,0 +1,64 @@
+/**
+ * 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;
+
+public class MemberResponse {
+  private final String groupName;
+  private final String userName;
+
+  public MemberResponse(String groupName, String userName) {
+    this.groupName = groupName;
+    this.userName = userName;
+  }
+
+  public String getGroupName() {
+    return groupName;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (o == null || getClass() != o.getClass())
+      return false;
+
+    MemberResponse that = (MemberResponse) o;
+
+    if (groupName != null ? !groupName.equals(that.groupName)
+        : that.groupName != null) {
+      return false;
+    }
+    if (userName != null ? !userName.equals(that.userName)
+        : that.userName != null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = groupName != null ? groupName.hashCode() : 0;
+    result = 31 * result + (userName != null ? userName.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 0113d75..a5f5b05 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
@@ -37,15 +37,19 @@ public interface ResourceProviderFactory {
   ResourceProvider getHostComponentResourceProvider(Set<String> propertyIds,
       Map<Type, String> keyPropertyIds,
       AmbariManagementController managementController);
-  
+
   @Named("service")
   ResourceProvider getServiceResourceProvider(Set<String> propertyIds,
       Map<Type, String> keyPropertyIds,
       AmbariManagementController managementController);
-  
+
   @Named("component")
   ResourceProvider getComponentResourceProvider(Set<String> propertyIds,
       Map<Type, String> keyPropertyIds,
       AmbariManagementController managementController);
 
+  @Named("member")
+  ResourceProvider getMemberResourceProvider(Set<String> propertyIds,
+      Map<Type, String> keyPropertyIds,
+      AmbariManagementController managementController);
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 a62b0d4..f68f21c 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
@@ -57,7 +57,7 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
     super(propertyIds, keyPropertyIds);
     this.managementController = managementController;
   }
-  
+
   public static void init(ResourceProviderFactory factory) {
     resourceProviderFactory = factory;
   }
@@ -112,6 +112,10 @@ public abstract class AbstractControllerResourceProvider extends AbstractResourc
         return new TaskResourceProvider(propertyIds, keyPropertyIds, managementController);
       case User:
         return new UserResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case Group:
+        return new GroupResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case Member:
+        return resourceProviderFactory.getMemberResourceProvider(propertyIds, keyPropertyIds, managementController);
       case Stack:
         return new StackResourceProvider(propertyIds, keyPropertyIds, managementController);
       case StackVersion:

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/GroupResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/GroupResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/GroupResourceProvider.java
new file mode 100644
index 0000000..36e1007
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/GroupResourceProvider.java
@@ -0,0 +1,188 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.GroupRequest;
+import org.apache.ambari.server.controller.GroupResponse;
+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;
+
+/**
+ * Resource provider for group resources.
+ */
+class GroupResourceProvider extends AbstractControllerResourceProvider {
+
+  // ----- Property ID constants ---------------------------------------------
+
+  // Groups
+  protected static final String GROUP_GROUPNAME_PROPERTY_ID  = PropertyHelper.getPropertyId("Groups", "group_name");
+  protected static final String GROUP_LDAP_GROUP_PROPERTY_ID = PropertyHelper.getPropertyId("Groups", "ldap_group");
+
+  private static Set<String> pkPropertyIds =
+      new HashSet<String>(Arrays.asList(new String[]{
+          GROUP_GROUPNAME_PROPERTY_ID}));
+
+  /**
+   * 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
+   */
+  GroupResourceProvider(Set<String> propertyIds,
+                       Map<Resource.Type, String> keyPropertyIds,
+                       AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(Request request)
+      throws SystemException,
+      UnsupportedPropertyException,
+      ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+    final Set<GroupRequest> requests = new HashSet<GroupRequest>();
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      requests.add(getRequest(propertyMap));
+    }
+
+    createResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        getManagementController().createGroups(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public Set<Resource> getResources(Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<GroupRequest> requests = new HashSet<GroupRequest>();
+
+    if (predicate == null) {
+      requests.add(getRequest(null));
+    } else {
+      for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+        requests.add(getRequest(propertyMap));
+      }
+    }
+
+    Set<GroupResponse> responses = getResources(new Command<Set<GroupResponse>>() {
+      @Override
+      public Set<GroupResponse> invoke() throws AmbariException {
+        return getManagementController().getGroups(requests);
+      }
+    });
+
+    LOG.debug("Found group responses matching get group request"
+        + ", groupRequestSize=" + requests.size() + ", groupResponseSize="
+        + responses.size());
+
+    Set<String>   requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources    = new HashSet<Resource>();
+
+    for (GroupResponse groupResponse : responses) {
+      ResourceImpl resource = new ResourceImpl(Resource.Type.Group);
+
+      setResourceProperty(resource, GROUP_GROUPNAME_PROPERTY_ID,
+          groupResponse.getGroupName(), requestedIds);
+
+      setResourceProperty(resource, GROUP_LDAP_GROUP_PROPERTY_ID,
+          groupResponse.isLdapGroup(), requestedIds);
+
+      resources.add(resource);
+    }
+
+    return resources;
+  }
+
+  @Override
+  public RequestStatus updateResources(Request request, Predicate predicate)
+    throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+    final Set<GroupRequest> requests = new HashSet<GroupRequest>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(request.getProperties().iterator().next(), predicate)) {
+      final GroupRequest req = getRequest(propertyMap);
+      requests.add(req);
+    }
+
+    modifyResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        getManagementController().updateGroups(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+    final Set<GroupRequest> requests = new HashSet<GroupRequest>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      final GroupRequest req = getRequest(propertyMap);
+      requests.add(req);
+    }
+
+    modifyResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        getManagementController().deleteGroups(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+  private GroupRequest getRequest(Map<String, Object> properties) {
+    if (properties == null) {
+      return new GroupRequest(null);
+    }
+
+    final GroupRequest request = new GroupRequest((String) properties.get(GROUP_GROUPNAME_PROPERTY_ID));
+    return request;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MemberResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MemberResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MemberResourceProvider.java
new file mode 100644
index 0000000..27b7e4b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MemberResourceProvider.java
@@ -0,0 +1,191 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.MemberRequest;
+import org.apache.ambari.server.controller.MemberResponse;
+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 com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import com.google.inject.persist.Transactional;
+
+/**
+ * Resource provider for member resources.
+ */
+public class MemberResourceProvider extends AbstractControllerResourceProvider {
+
+  // ----- Property ID constants ---------------------------------------------
+
+  // Members
+  protected static final String MEMBER_GROUP_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("MemberInfo", "group_name");
+  protected static final String MEMBER_USER_NAME_PROPERTY_ID  = PropertyHelper.getPropertyId("MemberInfo", "user_name");
+
+  private static Set<String> pkPropertyIds =
+      new HashSet<String>(Arrays.asList(new String[]{
+          MEMBER_GROUP_NAME_PROPERTY_ID,
+          MEMBER_USER_NAME_PROPERTY_ID}));
+
+  /**
+   * 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 MemberResourceProvider(@Assisted Set<String> propertyIds,
+                          @Assisted Map<Resource.Type, String> keyPropertyIds,
+                          @Assisted AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
+
+  @Override
+  public RequestStatus createResources(Request request)
+      throws SystemException,
+             UnsupportedPropertyException,
+             ResourceAlreadyExistsException,
+             NoSuchParentResourceException {
+
+    final Set<MemberRequest> requests = new HashSet<MemberRequest>();
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      requests.add(getRequest(propertyMap));
+    }
+    createResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        getManagementController().createMembers(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  @Transactional
+  public Set<Resource> getResources(Request request, Predicate predicate) throws
+      SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<MemberRequest> requests = new HashSet<MemberRequest>();
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      requests.add(getRequest(propertyMap));
+    }
+
+    Set<MemberResponse> responses = getResources(new Command<Set<MemberResponse>>() {
+      @Override
+      public Set<MemberResponse> invoke() throws AmbariException {
+        return getManagementController().getMembers(requests);
+      }
+    });
+
+    LOG.debug("Found member responses matching get members request"
+        + ", membersRequestSize=" + requests.size() + ", membersResponseSize="
+        + responses.size());
+
+    Set<String>   requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources    = new HashSet<Resource>();
+
+    for (MemberResponse memberResponse : responses) {
+      ResourceImpl resource = new ResourceImpl(Resource.Type.Member);
+
+      setResourceProperty(resource, MEMBER_GROUP_NAME_PROPERTY_ID,
+          memberResponse.getGroupName(), requestedIds);
+
+      setResourceProperty(resource, MEMBER_USER_NAME_PROPERTY_ID,
+          memberResponse.getUserName(), requestedIds);
+
+      resources.add(resource);
+    }
+
+    return resources;
+  }
+
+  @Override
+  public RequestStatus updateResources(final Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<MemberRequest> requests = new HashSet<MemberRequest>();
+    for (Map<String, Object> propertyMap : request.getProperties()) {
+      requests.add(getRequest(propertyMap));
+    }
+
+    modifyResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        // do nothing
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public RequestStatus deleteResources(Predicate predicate)
+      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<MemberRequest> requests = new HashSet<MemberRequest>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      final MemberRequest req = getRequest(propertyMap);
+      requests.add(req);
+    }
+
+    modifyResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        getManagementController().deleteMembers(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return pkPropertyIds;
+  }
+
+  private MemberRequest getRequest(Map<String, Object> properties) {
+    if (properties == null) {
+      return new MemberRequest(null, null);
+    }
+
+    final MemberRequest request = new MemberRequest(
+        (String) properties.get(MEMBER_GROUP_NAME_PROPERTY_ID),
+        (String) properties.get(MEMBER_USER_NAME_PROPERTY_ID));
+    return request;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/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 13e7c77..363ee0d 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
@@ -86,6 +86,8 @@ public interface Resource {
     RequestSchedule,
     Task,
     User,
+    Group,
+    Member,
     Stack,
     StackVersion,
     OperatingSystem,
@@ -154,6 +156,8 @@ public interface Resource {
     public static final Type RequestSchedule = InternalType.RequestSchedule.getType();
     public static final Type Task = InternalType.Task.getType();
     public static final Type User = InternalType.User.getType();
+    public static final Type Group = InternalType.Group.getType();
+    public static final Type Member = InternalType.Member.getType();
     public static final Type Stack = InternalType.Stack.getType();
     public static final Type StackVersion = InternalType.StackVersion.getType();
     public static final Type OperatingSystem = InternalType.OperatingSystem.getType();

http://git-wip-us.apache.org/repos/asf/ambari/blob/00a4991e/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java
new file mode 100644
index 0000000..b54b935
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.dao;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+
+@Singleton
+public class GroupDAO {
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
+  @Inject
+  DaoUtils daoUtils;
+
+  @RequiresSession
+  public GroupEntity findByPK(Integer groupPK) {
+    return entityManagerProvider.get().find(GroupEntity.class, groupPK);
+  }
+
+  @RequiresSession
+  public List<GroupEntity> findAll() {
+    final TypedQuery<GroupEntity> query = entityManagerProvider.get().createQuery("SELECT group_entity FROM GroupEntity group_entity", GroupEntity.class);
+    return daoUtils.selectList(query);
+  }
+
+  @RequiresSession
+  public GroupEntity findGroupByName(String groupName) {
+    final TypedQuery<GroupEntity> query = entityManagerProvider.get().createNamedQuery("groupByName", GroupEntity.class);
+    query.setParameter("groupname", groupName.toLowerCase());
+    try {
+      return query.getSingleResult();
+    } catch (NoResultException e) {
+      return null;
+    }
+  }
+
+  @Transactional
+  public void create(GroupEntity group) {
+    group.setGroupName(group.getGroupName().toLowerCase());
+    entityManagerProvider.get().persist(group);
+  }
+
+  @Transactional
+  public GroupEntity merge(GroupEntity group) {
+    group.setGroupName(group.getGroupName().toLowerCase());
+    return entityManagerProvider.get().merge(group);
+  }
+
+  @Transactional
+  public void remove(GroupEntity group) {
+    entityManagerProvider.get().remove(merge(group));
+  }
+
+  @Transactional
+  public void removeByPK(Integer groupPK) {
+    remove(findByPK(groupPK));
+  }
+}