You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by gc...@apache.org on 2016/01/28 01:49:47 UTC

incubator-sentry git commit: SENTRY-995: Simple Solr Shell (Gregory Chanan, reviewed by Colin Ma, Sravya Tirukkovalur)

Repository: incubator-sentry
Updated Branches:
  refs/heads/master 0f0fd359b -> 8529f8e12


SENTRY-995: Simple Solr Shell (Gregory Chanan, reviewed by Colin Ma, Sravya Tirukkovalur)


Project: http://git-wip-us.apache.org/repos/asf/incubator-sentry/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-sentry/commit/8529f8e1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-sentry/tree/8529f8e1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-sentry/diff/8529f8e1

Branch: refs/heads/master
Commit: 8529f8e121144d715986a485abb204aa036caa19
Parents: 0f0fd35
Author: Gregory Chanan <gc...@cloudera.com>
Authored: Wed Jan 20 15:34:10 2016 -0800
Committer: Gregory Chanan <gc...@cloudera.com>
Committed: Wed Jan 27 16:49:29 2016 -0800

----------------------------------------------------------------------
 sentry-provider/sentry-provider-db/pom.xml      |   4 +
 .../db/generic/tools/SentryShellSolr.java       | 101 +++++
 .../tools/SolrTSentryPrivilegeConvertor.java    | 128 ++++++
 .../db/generic/tools/command/Command.java       |  27 ++
 .../db/generic/tools/command/CreateRoleCmd.java |  39 ++
 .../db/generic/tools/command/DropRoleCmd.java   |  39 ++
 .../tools/command/GrantPrivilegeToRoleCmd.java  |  47 ++
 .../tools/command/ListPrivilegesByRoleCmd.java  |  54 +++
 .../db/generic/tools/command/ListRolesCmd.java  |  53 +++
 .../command/RevokePrivilegeFromRoleCmd.java     |  47 ++
 .../command/TSentryPrivilegeConvertor.java      |  33 ++
 .../provider/db/tools/SentryShellCommon.java    |   7 +-
 .../SentryGenericServiceIntegrationBase.java    |  76 ++++
 .../TestSentryGenericServiceIntegration.java    |  53 +--
 .../db/generic/tools/TestSentryShellSolr.java   | 446 +++++++++++++++++++
 15 files changed, 1101 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/pom.xml
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/pom.xml b/sentry-provider/sentry-provider-db/pom.xml
index 7514a7c..38e0924 100644
--- a/sentry-provider/sentry-provider-db/pom.xml
+++ b/sentry-provider/sentry-provider-db/pom.xml
@@ -100,6 +100,10 @@ limitations under the License.
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.sentry</groupId>
+      <artifactId>sentry-policy-search</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.hive</groupId>
       <artifactId>hive-shims</artifactId>
       <scope>provided</scope>

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellSolr.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellSolr.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellSolr.java
new file mode 100644
index 0000000..ec786a5
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellSolr.java
@@ -0,0 +1,101 @@
+/**
+ * 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.sentry.provider.db.generic.tools;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClientFactory;
+import org.apache.sentry.provider.db.generic.tools.command.*;
+import org.apache.sentry.provider.db.tools.SentryShellCommon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * SentryShellSolr is an admin tool, and responsible for the management of repository.
+ * The following commands are supported:
+ * create role, drop role, add group to role, grant privilege to role,
+ * revoke privilege from role, list roles, list privilege for role.
+ */
+public class SentryShellSolr extends SentryShellCommon {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(SentryShellSolr.class);
+  public static final String SOLR_SERVICE_NAME = "sentry.service.client.solr.service.name";
+
+  @Override
+  public void run() throws Exception {
+    Command command = null;
+    String requestorName = System.getProperty("user.name", "");
+    String component = "SOLR";
+    Configuration conf = getSentryConf();
+    String service = conf.get(SOLR_SERVICE_NAME, "service1");
+    SentryGenericServiceClient client = SentryGenericServiceClientFactory.create(conf);
+
+    if (isCreateRole) {
+      command = new CreateRoleCmd(roleName, component);
+    } else if (isDropRole) {
+      command = new DropRoleCmd(roleName, component);
+    } else if (isAddRoleGroup) {
+      throw new UnsupportedOperationException("Add group to role not supported for Solr client");
+    } else if (isDeleteRoleGroup) {
+      throw new UnsupportedOperationException("Delete group from role not supported for Solr client");
+    } else if (isGrantPrivilegeRole) {
+      command = new GrantPrivilegeToRoleCmd(roleName, component,
+          privilegeStr, new SolrTSentryPrivilegeConvertor(component, service));
+    } else if (isRevokePrivilegeRole) {
+      command = new RevokePrivilegeFromRoleCmd(roleName, component,
+          privilegeStr, new SolrTSentryPrivilegeConvertor(component, service));
+    } else if (isListRole) {
+      command = new ListRolesCmd(groupName, component);
+    } else if (isListPrivilege) {
+      command = new ListPrivilegesByRoleCmd(roleName, component,
+          service, new SolrTSentryPrivilegeConvertor(component, service));
+    }
+
+    // check the requestor name
+    if (StringUtils.isEmpty(requestorName)) {
+      // The exception message will be recorded in log file.
+      throw new Exception("The requestor name is empty.");
+    }
+
+    if (command != null) {
+      command.execute(client, requestorName);
+    }
+  }
+
+  private Configuration getSentryConf() {
+    Configuration conf = new Configuration();
+    conf.addResource(new Path(confPath));
+    return conf;
+  }
+
+  public static void main(String[] args) throws Exception {
+    SentryShellSolr sentryShell = new SentryShellSolr();
+    try {
+      if (sentryShell.executeShell(args)) {
+        System.out.println("The operation completed successfully.");
+      }
+    } catch (Exception e) {
+      LOGGER.error(e.getMessage(), e);
+      System.out.println("The operation failed, please refer to log file for the root cause.");
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SolrTSentryPrivilegeConvertor.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SolrTSentryPrivilegeConvertor.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SolrTSentryPrivilegeConvertor.java
new file mode 100644
index 0000000..f6a4f15
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SolrTSentryPrivilegeConvertor.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.sentry.provider.db.generic.tools;
+
+import com.google.common.collect.Lists;
+
+import org.apache.sentry.core.model.search.Collection;
+import org.apache.sentry.core.model.search.SearchModelAuthorizable;
+import org.apache.sentry.core.model.search.SearchModelAuthorizable.AuthorizableType;
+import org.apache.sentry.policy.search.SearchModelAuthorizables;
+import org.apache.sentry.provider.common.KeyValue;
+import org.apache.sentry.provider.common.PolicyFileConstants;
+import org.apache.sentry.provider.common.ProviderConstants;
+import org.apache.sentry.provider.db.generic.service.thrift.TAuthorizable;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryGrantOption;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+import org.apache.sentry.provider.db.generic.tools.command.TSentryPrivilegeConvertor;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public  class SolrTSentryPrivilegeConvertor implements TSentryPrivilegeConvertor {
+  private String component;
+  private String service;
+
+  public SolrTSentryPrivilegeConvertor(String component, String service) {
+    this.component = component;
+    this.service = service;
+  }
+
+  public TSentryPrivilege fromString(String privilegeStr) throws Exception {
+    TSentryPrivilege tSentryPrivilege = new TSentryPrivilege();
+    List<TAuthorizable> authorizables = new LinkedList<TAuthorizable>();
+    for (String authorizable : ProviderConstants.AUTHORIZABLE_SPLITTER.split(privilegeStr)) {
+      KeyValue keyValue = new KeyValue(authorizable);
+      String key = keyValue.getKey();
+      String value = keyValue.getValue();
+
+      // is it an authorizable?
+      SearchModelAuthorizable authz = SearchModelAuthorizables.from(keyValue);
+      if (authz != null) {
+        if (authz instanceof Collection) {
+          Collection coll = (Collection)authz;
+          authorizables.add(new TAuthorizable(coll.getTypeName(), coll.getName()));
+        } else {
+          throw new IllegalArgumentException("Unknown authorizable type: " + authz.getTypeName());
+        }
+      } else if (PolicyFileConstants.PRIVILEGE_ACTION_NAME.equalsIgnoreCase(key)) {
+        tSentryPrivilege.setAction(value);
+      // Limitation: don't support grant at this time, since the existing solr use cases don't need it.
+      } else {
+        throw new IllegalArgumentException("Unknown key: " + key);
+      }
+    }
+    tSentryPrivilege.setComponent(component);
+    tSentryPrivilege.setServiceName(service);
+    tSentryPrivilege.setAuthorizables(authorizables);
+    validatePrivilegeHierarchy(tSentryPrivilege);
+    return tSentryPrivilege;
+  }
+
+  public String toString(TSentryPrivilege tSentryPrivilege) {
+    List<String> privileges = Lists.newArrayList();
+    if (tSentryPrivilege != null) {
+      List<TAuthorizable> authorizables = tSentryPrivilege.getAuthorizables();
+      String action = tSentryPrivilege.getAction();
+      String grantOption = (tSentryPrivilege.getGrantOption() == TSentryGrantOption.TRUE ? "true"
+              : "false");
+
+      Iterator<TAuthorizable> it = authorizables.iterator();
+      if (it != null) {
+        while (it.hasNext()) {
+          TAuthorizable tAuthorizable = it.next();
+          privileges.add(ProviderConstants.KV_JOINER.join(
+              tAuthorizable.getType(), tAuthorizable.getName()));
+        }
+      }
+
+      if (!authorizables.isEmpty()) {
+        privileges.add(ProviderConstants.KV_JOINER.join(
+            PolicyFileConstants.PRIVILEGE_ACTION_NAME, action));
+      }
+
+      // only append the grant option to privilege string if it's true
+      if ("true".equals(grantOption)) {
+        privileges.add(ProviderConstants.KV_JOINER.join(
+            PolicyFileConstants.PRIVILEGE_GRANT_OPTION_NAME, grantOption));
+      }
+    }
+    return ProviderConstants.AUTHORIZABLE_JOINER.join(privileges);
+  }
+
+  private static void validatePrivilegeHierarchy(TSentryPrivilege tSentryPrivilege) throws Exception {
+    boolean foundCollection = false;
+    Iterator<TAuthorizable> it = tSentryPrivilege.getAuthorizablesIterator();
+    if (it != null) {
+      while (it.hasNext()) {
+        TAuthorizable authorizable = it.next();
+        if (AuthorizableType.Collection.name().equals(authorizable.getType())) {
+          foundCollection = true;
+          break;
+        }
+      }
+    }
+
+    if (!foundCollection) {
+      String msg = "Missing collection object in privilege";
+      throw new IllegalArgumentException(msg);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/Command.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/Command.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/Command.java
new file mode 100644
index 0000000..e824fb3
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/Command.java
@@ -0,0 +1,27 @@
+/**
+ * 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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+
+/**
+ * The interface for all admin commands, eg, CreateRoleCmd.
+ */
+public interface Command {
+  void execute(SentryGenericServiceClient client, String requestorName) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/CreateRoleCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/CreateRoleCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/CreateRoleCmd.java
new file mode 100644
index 0000000..da60a64
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/CreateRoleCmd.java
@@ -0,0 +1,39 @@
+/**
+ * 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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+
+/**
+ * The class for admin command to create role.
+ */
+public class CreateRoleCmd implements Command {
+
+  private String roleName;
+  private String component;
+
+  public CreateRoleCmd(String roleName, String component) {
+    this.roleName = roleName;
+    this.component = component;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    client.createRole(requestorName, roleName, component);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/DropRoleCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/DropRoleCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/DropRoleCmd.java
new file mode 100644
index 0000000..ac2a328
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/DropRoleCmd.java
@@ -0,0 +1,39 @@
+/**
+ * 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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+
+/**
+ * The class for admin command to drop role.
+ */
+public class DropRoleCmd implements Command {
+
+  private String roleName;
+  private String component;
+
+  public DropRoleCmd(String roleName, String component) {
+    this.roleName = roleName;
+    this.component = component;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    client.dropRole(requestorName, roleName, component);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/GrantPrivilegeToRoleCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/GrantPrivilegeToRoleCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/GrantPrivilegeToRoleCmd.java
new file mode 100644
index 0000000..5867983
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/GrantPrivilegeToRoleCmd.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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+
+/**
+ * The class for admin command to grant privilege to role.
+ */
+public class GrantPrivilegeToRoleCmd implements Command {
+
+  private String roleName;
+  private String component;
+  private String privilegeStr;
+  private TSentryPrivilegeConvertor convertor;
+
+  public GrantPrivilegeToRoleCmd(String roleName, String component, String privilegeStr,
+      TSentryPrivilegeConvertor convertor) {
+    this.roleName = roleName;
+    this.component = component;
+    this.privilegeStr = privilegeStr;
+    this.convertor = convertor;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    TSentryPrivilege privilege = convertor.fromString(privilegeStr);
+    client.grantPrivilege(requestorName, roleName, component, privilege);
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListPrivilegesByRoleCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListPrivilegesByRoleCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListPrivilegesByRoleCmd.java
new file mode 100644
index 0000000..8420291
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListPrivilegesByRoleCmd.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+
+import java.util.Set;
+
+/**
+ * The class for admin command to list privileges by role.
+ */
+public class ListPrivilegesByRoleCmd implements Command {
+
+  private String roleName;
+  private String component;
+  private String serviceName;
+  private TSentryPrivilegeConvertor convertor;
+
+  public ListPrivilegesByRoleCmd(String roleName, String component, String serviceName,
+      TSentryPrivilegeConvertor convertor) {
+    this.roleName = roleName;
+    this.component = component;
+    this.serviceName = serviceName;
+    this.convertor = convertor;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    Set<TSentryPrivilege> privileges = client
+            .listPrivilegesByRoleName(requestorName, roleName, component, serviceName);
+    if (privileges != null) {
+      for (TSentryPrivilege privilege : privileges) {
+        String privilegeStr = convertor.toString(privilege);
+        System.out.println(privilegeStr);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListRolesCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListRolesCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListRolesCmd.java
new file mode 100644
index 0000000..bad47ef
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/ListRolesCmd.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.sentry.provider.db.generic.tools.command;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole;
+
+import java.util.Set;
+
+/**
+ * The class for admin command to list roles.
+ */
+public class ListRolesCmd implements Command {
+
+  private String groupName;
+  private String component;
+
+  public ListRolesCmd(String groupName, String component) {
+    this.groupName = groupName;
+    this.component = component;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    Set<TSentryRole> roles;
+    if (StringUtils.isEmpty(groupName)) {
+      roles = client.listAllRoles(requestorName, component);
+    } else {
+      throw new UnsupportedOperationException("List roles by group name not supported");
+    }
+    if (roles != null) {
+      for (TSentryRole role : roles) {
+        System.out.println(role.getRoleName());
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/RevokePrivilegeFromRoleCmd.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/RevokePrivilegeFromRoleCmd.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/RevokePrivilegeFromRoleCmd.java
new file mode 100644
index 0000000..fba17e6
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/RevokePrivilegeFromRoleCmd.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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+
+/**
+ * The class for admin command to revoke privileges from role.
+ */
+public class RevokePrivilegeFromRoleCmd implements Command {
+
+  private String roleName;
+  private String component;
+  private String privilegeStr;
+  private TSentryPrivilegeConvertor convertor;
+
+  public RevokePrivilegeFromRoleCmd(String roleName, String component, String privilegeStr,
+      TSentryPrivilegeConvertor convertor) {
+    this.roleName = roleName;
+    this.component = component;
+    this.privilegeStr = privilegeStr;
+    this.convertor = convertor;
+  }
+
+  @Override
+  public void execute(SentryGenericServiceClient client, String requestorName) throws Exception {
+    TSentryPrivilege privilege = convertor.fromString(privilegeStr);
+    client.revokePrivilege(requestorName, roleName, component, privilege);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/TSentryPrivilegeConvertor.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/TSentryPrivilegeConvertor.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/TSentryPrivilegeConvertor.java
new file mode 100644
index 0000000..f872341
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/command/TSentryPrivilegeConvertor.java
@@ -0,0 +1,33 @@
+/**
+ * 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.sentry.provider.db.generic.tools.command;
+
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+
+public interface TSentryPrivilegeConvertor {
+
+  /**
+   * Convert string to privilege
+   */
+  TSentryPrivilege fromString(String privilegeStr) throws Exception;
+
+  /**
+   * Convert privilege to string
+   */
+  String toString(TSentryPrivilege tSentryPrivilege);
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/tools/SentryShellCommon.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/tools/SentryShellCommon.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/tools/SentryShellCommon.java
index b1353c5..3b2e233 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/tools/SentryShellCommon.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/tools/SentryShellCommon.java
@@ -18,6 +18,8 @@
 
 package org.apache.sentry.provider.db.tools;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.GnuParser;
 import org.apache.commons.cli.HelpFormatter;
@@ -228,9 +230,10 @@ abstract public class SentryShellCommon {
   }
 
   // hive model and generic model should implement this method
-  abstract void run() throws Exception;
+  public abstract void run() throws Exception;
 
-  protected boolean executeShell(String[] args) throws Exception {
+  @VisibleForTesting
+  public boolean executeShell(String[] args) throws Exception {
     boolean result = true;
     if (parseArgs(args)) {
       run();

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/SentryGenericServiceIntegrationBase.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/SentryGenericServiceIntegrationBase.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/SentryGenericServiceIntegrationBase.java
new file mode 100644
index 0000000..e55f711
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/SentryGenericServiceIntegrationBase.java
@@ -0,0 +1,76 @@
+/**
+ * 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.sentry.provider.db.generic.service.thrift;
+
+import java.security.PrivilegedExceptionAction;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.apache.sentry.service.thrift.SentryServiceIntegrationBase;
+import org.junit.After;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SentryGenericServiceIntegrationBase extends SentryServiceIntegrationBase {
+  private static final Logger LOGGER = LoggerFactory.getLogger(SentryGenericServiceIntegrationBase.class);
+  protected static final String SOLR = "SOLR";
+  protected SentryGenericServiceClient client;
+
+ /**
+   * use the generic client to connect sentry service
+   */
+  @Override
+  public void connectToSentryService() throws Exception {
+    // The client should already be logged in when running in solr
+    // therefore we must manually login in the integration tests
+    final SentryGenericServiceClientFactory clientFactory;
+    if (kerberos) {
+      this.client = Subject.doAs(clientSubject, new PrivilegedExceptionAction<SentryGenericServiceClient>() {
+        @Override
+        public SentryGenericServiceClient run() throws Exception {
+          return SentryGenericServiceClientFactory.create(conf);
+        }
+      });
+    } else {
+      this.client = SentryGenericServiceClientFactory.create(conf);
+    }
+  }
+
+  @After
+  public void after() {
+    try {
+      runTestAsSubject(new TestOperation(){
+        @Override
+        public void runTestAsSubject() throws Exception {
+          Set<TSentryRole> tRoles = client.listAllRoles(ADMIN_USER, SOLR);
+          for (TSentryRole tRole : tRoles) {
+            client.dropRole(ADMIN_USER, tRole.getRoleName(), SOLR);
+          }
+          if(client != null) {
+            client.close();
+          }
+        }
+      });
+    } catch (Exception e) {
+      LOGGER.error(e.getMessage(), e);
+    } finally {
+      policyFilePath.delete();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/TestSentryGenericServiceIntegration.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/TestSentryGenericServiceIntegration.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/TestSentryGenericServiceIntegration.java
index 4732ea2..7f8f916 100644
--- a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/TestSentryGenericServiceIntegration.java
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/service/thrift/TestSentryGenericServiceIntegration.java
@@ -21,21 +21,16 @@ import static junit.framework.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
-import javax.security.auth.Subject;
-
 import org.apache.sentry.SentryUserException;
 import org.apache.sentry.core.common.ActiveRoleSet;
 import org.apache.sentry.core.common.Authorizable;
 import org.apache.sentry.core.model.search.Collection;
 import org.apache.sentry.core.model.search.Field;
 import org.apache.sentry.core.model.search.SearchConstants;
-import org.apache.sentry.service.thrift.SentryServiceIntegrationBase;
-import org.junit.After;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,53 +38,9 @@ import org.slf4j.LoggerFactory;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
-public class TestSentryGenericServiceIntegration extends SentryServiceIntegrationBase {
-
-  private static final Logger LOGGER = LoggerFactory.getLogger(SentryServiceIntegrationBase.class);
-  private static final String SOLR = "SOLR";
-  private SentryGenericServiceClient client;
-
-  /**
-   * use the generic client to connect sentry service
-   */
-  @Override
-  public void connectToSentryService() throws Exception {
-    // The client should already be logged in when running in solr
-    // therefore we must manually login in the integration tests
-    final SentryGenericServiceClientFactory clientFactory;
-    if (kerberos) {
-      this.client = Subject.doAs(clientSubject, new PrivilegedExceptionAction<SentryGenericServiceClient>() {
-        @Override
-        public SentryGenericServiceClient run() throws Exception {
-          return SentryGenericServiceClientFactory.create(conf);
-        }
-      });
-    } else {
-      this.client = SentryGenericServiceClientFactory.create(conf);
-    }
-  }
+public class TestSentryGenericServiceIntegration extends SentryGenericServiceIntegrationBase {
 
-  @After
-  public void after() {
-    try {
-      runTestAsSubject(new TestOperation(){
-        @Override
-        public void runTestAsSubject() throws Exception {
-          Set<TSentryRole> tRoles = client.listAllRoles(ADMIN_USER, SOLR);
-          for (TSentryRole tRole : tRoles) {
-            client.dropRole(ADMIN_USER, tRole.getRoleName(), SOLR);
-          }
-          if(client != null) {
-            client.close();
-          }
-        }
-      });
-    } catch (Exception e) {
-      LOGGER.error(e.getMessage(), e);
-    } finally {
-      policyFilePath.delete();
-    }
-  }
+  private static final Logger LOGGER = LoggerFactory.getLogger(TestSentryGenericServiceIntegration.class);
 
   @Test
   public void testCreateDropShowRole() throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/8529f8e1/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellSolr.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellSolr.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellSolr.java
new file mode 100644
index 0000000..354cf35
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellSolr.java
@@ -0,0 +1,446 @@
+/**
+ * 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.sentry.provider.db.generic.tools;
+
+import com.google.common.io.Files;
+import com.google.common.collect.Sets;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.security.PrivilegedExceptionAction;
+import java.util.Set;
+import javax.security.auth.Subject;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.sentry.SentryUserException;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClientFactory;
+import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceIntegrationBase;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+import org.apache.sentry.provider.db.tools.SentryShellCommon;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestSentryShellSolr extends SentryGenericServiceIntegrationBase {
+  private static final Logger LOGGER = LoggerFactory.getLogger(TestSentryShellSolr.class);
+  private File confDir;
+  private File confPath;
+  private static String TEST_ROLE_NAME_1 = "testRole1";
+  private static String TEST_ROLE_NAME_2 = "testRole2";
+  private String requestorName = "";
+  private String service = "service1";
+
+  @Before
+  public void prepareForTest() throws Exception {
+    confDir = Files.createTempDir();
+    confPath = new File(confDir, "sentry-site.xml");
+    if (confPath.createNewFile()) {
+      FileOutputStream to = new FileOutputStream(confPath);
+      conf.writeXml(to);
+      to.close();
+    }
+    requestorName = System.getProperty("user.name", "");
+    Set<String> requestorUserGroupNames = Sets.newHashSet(ADMIN_GROUP);
+    setLocalGroupMapping(requestorName, requestorUserGroupNames);
+    // add ADMIN_USER for the after() in SentryServiceIntegrationBase
+    setLocalGroupMapping(ADMIN_USER, requestorUserGroupNames);
+    writePolicyFile();
+  }
+
+  @After
+  public void clearTestData() throws Exception {
+    FileUtils.deleteQuietly(confDir);
+  }
+
+  @Test
+  public void testCreateDropRole() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+        // test: create role with -cr
+        String[] args = { "-cr", "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        SentryShellSolr.main(args);
+        // test: create role with --create_role
+        args = new String[] { "--create_role", "-r", TEST_ROLE_NAME_2, "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellSolr.main(args);
+
+        // validate the result, list roles with -lr
+        args = new String[] { "-lr", "-conf", confPath.getAbsolutePath() };
+        SentryShellSolr sentryShell = new SentryShellSolr();
+        Set<String> roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        assertEquals("Incorrect number of roles", 2, roleNames.size());
+        for (String roleName : roleNames) {
+          assertTrue(TEST_ROLE_NAME_1.equalsIgnoreCase(roleName)
+              || TEST_ROLE_NAME_2.equalsIgnoreCase(roleName));
+        }
+
+        // validate the result, list roles with --list_role
+        args = new String[] { "--list_role", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        assertEquals("Incorrect number of roles", 2, roleNames.size());
+        for (String roleName : roleNames) {
+          assertTrue(TEST_ROLE_NAME_1.equalsIgnoreCase(roleName)
+              || TEST_ROLE_NAME_2.equalsIgnoreCase(roleName));
+        }
+
+        // test: drop role with -dr
+        args = new String[] { "-dr", "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        SentryShellSolr.main(args);
+        // test: drop role with --drop_role
+        args = new String[] { "--drop_role", "-r", TEST_ROLE_NAME_2, "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellSolr.main(args);
+
+        // validate the result
+        Set<TSentryRole> roles = client.listAllRoles(requestorName, SOLR);
+        assertEquals("Incorrect number of roles", 0, roles.size());
+      }
+    });
+  }
+
+  // this is not supported, just check that all the permutations
+  // give a reasonable error
+  @Test
+  public void testAddDeleteRoleForGroup() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+         // test: add role to multiple groups
+        String[] args = new String[] { "-arg", "-r", TEST_ROLE_NAME_1, "-g", "testGroup2,testGroup3",
+            "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellSolr sentryShell = new SentryShellSolr();
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        // test: add role to group with --add_role_group
+        args = new String[] { "--add_role_group", "-r", TEST_ROLE_NAME_2, "-g", "testGroup1",
+            "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        args = new String[] { "-lr", "-g", "testGroup1", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        // list roles with --list_role and -g
+        args = new String[] { "--list_role", "-g", "testGroup2", "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        // test: delete group from role with -drg
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_1, "-g", "testGroup1", "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_1, "-g", "testGroup2,testGroup3",
+            "-conf",
+            confPath.getAbsolutePath() };
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+
+        // test: delete group from role with --delete_role_group
+        args = new String[] { "--delete_role_group", "-r", TEST_ROLE_NAME_2, "-g", "testGroup1",
+            "-conf", confPath.getAbsolutePath() };
+        try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+          // expected
+        }
+      }
+    });
+  }
+
+  public static String grant(boolean shortOption) {
+    return shortOption ? "-gpr" : "--grant_privilege_role";
+  }
+
+  public static String revoke(boolean shortOption) {
+    return shortOption ? "-rpr" : "--revoke_privilege_role";
+  }
+
+  public static String list(boolean shortOption) {
+    return shortOption ? "-lp" : "--list_privilege";
+  }
+
+  private void assertGrantRevokePrivilege(final boolean shortOption) throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+        // create the role for test
+        client.createRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+        client.createRole(requestorName, TEST_ROLE_NAME_2, SOLR);
+
+        String [] privs = {
+          "Collection=*->action=*",
+          "Collection=collection2->action=update",
+          "Collection=collection3->action=query",
+        };
+        for (int i = 0; i < privs.length; ++i) {
+          // test: grant privilege to role
+          String [] args = new String [] { grant(shortOption), "-r", TEST_ROLE_NAME_1, "-p",
+            privs[ i ],
+            "-conf", confPath.getAbsolutePath() };
+          SentryShellSolr.main(args);
+        }
+
+        // test the list privilege
+        String [] args = new String[] { list(shortOption), "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        SentryShellSolr sentryShell = new SentryShellSolr();
+        Set<String> privilegeStrs = getShellResultWithOSRedirect(sentryShell, args, true);
+        assertEquals("Incorrect number of privileges", privs.length, privilegeStrs.size());
+        for (int i = 0; i < privs.length; ++i) {
+          assertTrue("Expected privilege: " + privs[ i ], privilegeStrs.contains(privs[ i ]));
+        }
+
+        for (int i = 0; i < privs.length; ++i) {
+          args = new String[] { revoke(shortOption), "-r", TEST_ROLE_NAME_1, "-p",
+            privs[ i ], "-conf",
+            confPath.getAbsolutePath() };
+          SentryShellSolr.main(args);
+          Set<TSentryPrivilege> privileges = client.listPrivilegesByRoleName(requestorName,
+            TEST_ROLE_NAME_1, SOLR, service);
+          assertEquals("Incorrect number of privileges", privs.length - (i + 1), privileges.size());
+        }
+
+        // clear the test data
+        client.dropRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+        client.dropRole(requestorName, TEST_ROLE_NAME_2, SOLR);
+      }
+    });
+  }
+
+
+  @Test
+  public void testGrantRevokePrivilegeWithShortOption() throws Exception {
+    assertGrantRevokePrivilege(true);
+  }
+
+  @Test
+  public void testGrantRevokePrivilegeWithLongOption() throws Exception {
+    assertGrantRevokePrivilege(false);
+  }
+
+
+  @Test
+  public void testNegativeCaseWithInvalidArgument() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+        client.createRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+        // test: create duplicate role with -cr
+        String[] args = { "-cr", "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        SentryShellSolr sentryShell = new SentryShellSolr();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for creating duplicate role");
+        } catch (SentryUserException e) {
+          // expected exception
+        }
+
+        // test: drop non-exist role with -dr
+        args = new String[] { "-dr", "-r", TEST_ROLE_NAME_2, "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for dropping non-exist role");
+        } catch (SentryUserException e) {
+          // excepted exception
+        }
+
+        // test: grant privilege to role with the error privilege format
+        args = new String[] { "-gpr", "-r", TEST_ROLE_NAME_1, "-p", "serverserver1->action=*",
+            "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for the error privilege format, invalid key value.");
+        } catch (IllegalArgumentException e) {
+          // excepted exception
+        }
+
+        // test: grant privilege to role with the error privilege hierarchy
+        args = new String[] { "-gpr", "-r", TEST_ROLE_NAME_1, "-p",
+            "server=server1->table=tbl1->column=col2->action=insert", "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for the error privilege format, invalid key value.");
+        } catch (IllegalArgumentException e) {
+          // expected exception
+        }
+
+        // clear the test data
+        client.dropRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+      }
+    });
+  }
+
+  @Test
+  public void testNegativeCaseWithoutRequiredArgument() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+        String strOptionConf = "conf";
+        client.createRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+        // test: the conf is required argument
+        String[] args = { "-cr", "-r", TEST_ROLE_NAME_1 };
+        SentryShellSolr sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + strOptionConf);
+
+        // test: -r is required when create role
+        args = new String[] { "-cr", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -r is required when drop role
+        args = new String[] { "-dr", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -r is required when add group to role
+        args = new String[] { "-arg", "-g", "testGroup1", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -g is required when add group to role
+        args = new String[] { "-arg", "-r", TEST_ROLE_NAME_2, "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_GROUP_NAME);
+
+        // test: -r is required when delete group from role
+        args = new String[] { "-drg", "-g", "testGroup1", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -g is required when delete group from role
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_2, "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_GROUP_NAME);
+
+        // test: -r is required when grant privilege to role
+        args = new String[] { "-gpr", "-p", "server=server1", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -p is required when grant privilege to role
+        args = new String[] { "-gpr", "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_PRIVILEGE);
+
+        // test: -r is required when revoke privilege from role
+        args = new String[] { "-rpr", "-p", "server=server1", "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -p is required when revoke privilege from role
+        args = new String[] { "-rpr", "-r", TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + SentryShellCommon.OPTION_DESC_PRIVILEGE);
+
+        // test: command option is required for shell
+        args = new String[] {"-conf", confPath.getAbsolutePath() };
+        sentryShell = new SentryShellSolr();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + "[-arg Add group to role," +
+                        " -cr Create role, -rpr Revoke privilege from role, -drg Delete group from role," +
+                        " -lr List role, -lp List privilege, -gpr Grant privilege to role, -dr Drop role]");
+
+        // clear the test data
+        client.dropRole(requestorName, TEST_ROLE_NAME_1, SOLR);
+      }
+    });
+  }
+
+  // redirect the System.out to ByteArrayOutputStream, then execute the command and parse the result.
+  private Set<String> getShellResultWithOSRedirect(SentryShellSolr sentryShell,
+      String[] args, boolean expectedExecuteResult) throws Exception {
+    PrintStream oldOut = System.out;
+    ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+    System.setOut(new PrintStream(outContent));
+    assertEquals(expectedExecuteResult, sentryShell.executeShell(args));
+    Set<String> resultSet = Sets.newHashSet(outContent.toString().split("\n"));
+    System.setOut(oldOut);
+    return resultSet;
+  }
+
+  private void validateMissingParameterMsg(SentryShellSolr sentryShell, String[] args,
+      String exceptedErrorMsg) throws Exception {
+    Set<String> errorMsgs = getShellResultWithOSRedirect(sentryShell, args, false);
+    assertTrue(errorMsgs.contains(exceptedErrorMsg));
+  }
+}