SENTRY-2023: Add sentry-shell support for hbase-indexer permissions (Mano 
Kovacs, reviewed by Sergio Pena)


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

Branch: refs/heads/master
Commit: 5a7b076435dfe54f9d093fbf892d4b7141634d66
Parents: 01c76e7
Author: Sergio Pena <[email protected]>
Authored: Tue Jan 16 14:29:31 2018 -0600
Committer: Sergio Pena <[email protected]>
Committed: Tue Jan 16 14:29:31 2018 -0600

----------------------------------------------------------------------
 sentry-provider/sentry-provider-db/pom.xml      |   4 +
 .../persistent/PrivilegeOperatePersistence.java |   2 +
 .../tools/GenericPrivilegeConverter.java        |  10 +-
 .../generic/tools/SentryConfigToolIndexer.java  | 340 ++++++++++++
 .../db/generic/tools/SentryShellGeneric.java    |  17 +-
 .../db/generic/tools/SentryShellIndexer.java    | 124 +++++
 .../provider/db/tools/SentryShellCommon.java    | 224 ++++----
 .../tools/TestSentryConfigToolIndexer.java      | 263 ++++++++++
 .../generic/tools/TestSentryShellIndexer.java   | 526 +++++++++++++++++++
 .../src/test/resources/indexer_case.ini         |  26 +
 .../resources/indexer_config_import_tool.ini    |  29 +
 .../src/test/resources/indexer_invalid.ini      |  21 +
 12 files changed, 1477 insertions(+), 109 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/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 192f8c8..db4808d 100644
--- a/sentry-provider/sentry-provider-db/pom.xml
+++ b/sentry-provider/sentry-provider-db/pom.xml
@@ -112,6 +112,10 @@ limitations under the License.
     </dependency>
     <dependency>
       <groupId>org.apache.sentry</groupId>
+      <artifactId>sentry-core-model-indexer</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sentry</groupId>
       <artifactId>sentry-core-model-sqoop</artifactId>
     </dependency>
     <dependency>

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
index c13e000..9dcfc03 100644
--- 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
+++ 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
@@ -34,6 +34,7 @@ import org.apache.sentry.core.common.Action;
 import org.apache.sentry.core.common.Authorizable;
 import org.apache.sentry.core.common.BitFieldAction;
 import org.apache.sentry.core.common.BitFieldActionFactory;
+import org.apache.sentry.core.model.indexer.IndexerActionFactory;
 import org.apache.sentry.core.model.kafka.KafkaActionFactory;
 import org.apache.sentry.core.model.solr.SolrActionFactory;
 import org.apache.sentry.core.model.sqoop.SqoopActionFactory;
@@ -70,6 +71,7 @@ public class PrivilegeOperatePersistence {
     actionFactories.put("solr", new SolrActionFactory());
     actionFactories.put("sqoop", new SqoopActionFactory());
     actionFactories.put("kafka", KafkaActionFactory.getInstance());
+    actionFactories.put("hbaseindexer", new IndexerActionFactory());
   }
 
   private final Configuration conf;

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
index c65b66d..8de543c 100644
--- 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
+++ 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
@@ -35,6 +35,8 @@ import 
org.apache.sentry.core.common.utils.PolicyFileConstants;
 import org.apache.sentry.core.common.utils.SentryConstants;
 import org.apache.sentry.core.common.validator.PrivilegeValidator;
 import org.apache.sentry.core.common.validator.PrivilegeValidatorContext;
+import org.apache.sentry.core.model.indexer.IndexerModelAuthorizables;
+import org.apache.sentry.core.model.indexer.IndexerPrivilegeModel;
 import org.apache.sentry.core.model.kafka.KafkaAuthorizable;
 import org.apache.sentry.core.model.kafka.KafkaModelAuthorizables;
 import org.apache.sentry.core.model.kafka.KafkaPrivilegeModel;
@@ -158,25 +160,29 @@ public class GenericPrivilegeConverter implements 
TSentryPrivilegeConverter {
     }
   }
 
-  private List<PrivilegeValidator> getPrivilegeValidators() throws 
SentryUserException {
+  protected List<PrivilegeValidator> getPrivilegeValidators() throws 
SentryUserException {
     if (AuthorizationComponent.KAFKA.equals(component)) {
       return KafkaPrivilegeModel.getInstance().getPrivilegeValidators();
     } else if ("SOLR".equals(component)) {
       return SolrPrivilegeModel.getInstance().getPrivilegeValidators();
     } else if (AuthorizationComponent.SQOOP.equals(component)) {
       return SqoopPrivilegeModel.getInstance().getPrivilegeValidators(service);
+    } else if (AuthorizationComponent.HBASE_INDEXER.equals(component)) {
+      return IndexerPrivilegeModel.getInstance().getPrivilegeValidators();
     }
 
     throw new SentryUserException("Invalid component specified for 
GenericPrivilegeCoverter: " + component);
   }
 
-  private Authorizable getAuthorizable(KeyValue keyValue) throws 
SentryUserException {
+  protected Authorizable getAuthorizable(KeyValue keyValue) throws 
SentryUserException {
     if (AuthorizationComponent.KAFKA.equals(component)) {
       return KafkaModelAuthorizables.from(keyValue);
     } else if ("SOLR".equals(component)) {
       return SolrModelAuthorizables.from(keyValue);
     } else if (AuthorizationComponent.SQOOP.equals(component)) {
       return SqoopModelAuthorizables.from(keyValue);
+    } else if (AuthorizationComponent.HBASE_INDEXER.equals(component)) {
+      return IndexerModelAuthorizables.from(keyValue);
     }
 
     throw new SentryUserException("Invalid component specified for 
GenericPrivilegeCoverter: " + component);

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryConfigToolIndexer.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryConfigToolIndexer.java
 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryConfigToolIndexer.java
new file mode 100644
index 0000000..c2341d3
--- /dev/null
+++ 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryConfigToolIndexer.java
@@ -0,0 +1,340 @@
+/**
+ * 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 com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.sentry.core.common.Action;
+import org.apache.sentry.core.common.exception.SentryConfigurationException;
+import org.apache.sentry.core.common.utils.KeyValue;
+import org.apache.sentry.core.model.indexer.IndexerPrivilegeModel;
+import org.apache.sentry.provider.common.ProviderBackend;
+import org.apache.sentry.provider.common.ProviderBackendContext;
+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.file.SimpleFileProviderBackend;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import static 
org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_SPLITTER;
+import static 
org.apache.sentry.provider.common.AuthorizationComponent.HBASE_INDEXER;
+import static 
org.apache.sentry.service.thrift.ServiceConstants.ClientConfig.SERVICE_NAME;
+
+/**
+ * SentryConfigToolIndexer is an administrative tool used to parse a HBase 
Indexer policy file
+ * and add the role, group mappings, and privileges therein to the Sentry 
service.
+ */
+public class SentryConfigToolIndexer {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(SentryConfigToolIndexer.class);
+
+  private String policyFile;
+  private boolean validate;
+  private boolean importPolicy;
+  private boolean checkCompat;
+  private String confPath;
+
+  private String serviceName;
+
+
+
+  public String getPolicyFile() { return policyFile; }
+
+  public boolean getValidate() { return validate; }
+  public boolean getImportPolicy() { return importPolicy; }
+  public boolean getCheckCompat() { return checkCompat; }
+  public String getConfPath() { return confPath; }
+  public String getServiceName() {
+    return serviceName;
+  }
+
+  /**
+   * Adds command line options for the tool to the passed Options object. Used 
to extend existing options.
+   * @param options
+   */
+  public void setupOptions(Options options) {
+    Option globalPolicyPath = new Option("f", "policy_ini", true,
+            "Policy file path");
+    globalPolicyPath.setRequired(false);
+    options.addOption(globalPolicyPath);
+
+    Option validateOpt = new Option("v", "validate", false,
+            "Validate policy file");
+    validateOpt.setRequired(false);
+    options.addOption(validateOpt);
+
+    Option checkCompatOpt = new Option("c","checkcompat",false,
+            "Check compatibility with Sentry Service");
+    checkCompatOpt.setRequired(false);
+    options.addOption(checkCompatOpt);
+
+    Option importOpt = new Option("i", "import", false,
+            "Import policy file");
+    importOpt.setRequired(false);
+    options.addOption(importOpt);
+
+  }
+
+  /**
+   * Parses and processes the arguments from the given command line object.
+   * @param cmd
+   */
+  public void parseOptions(CommandLine cmd) {
+    boolean isToolActive = false;
+    for (Option opt : cmd.getOptions()) {
+      if (opt.getOpt().equals("mgr")) {
+        isToolActive = true;
+      }
+    }
+    if (!isToolActive) {
+      return;
+    }
+    for (Option opt : cmd.getOptions()) {
+      if (opt.getOpt().equals("f")) {
+        policyFile = opt.getValue();
+      } else if (opt.getOpt().equals("v")) {
+        validate = true;
+      } else if (opt.getOpt().equals("i")) {
+        importPolicy = true;
+      } else if (opt.getOpt().equals("c")) {
+        checkCompat = true;
+      } else if (opt.getOpt().equals("conf")) {
+        confPath = opt.getValue();
+      } else if (opt.getOpt().equals("s")) {
+        serviceName = opt.getValue();
+      }
+    }
+    if (policyFile == null) {
+      throw new IllegalArgumentException("Missing required option: f");
+    }
+    if (!validate && !importPolicy) {
+      throw new IllegalArgumentException("No action specified; at least one of 
action or import must be specified");
+    }
+  }
+
+
+  /**
+   * Processes the necessary command based on the arguments parsed earlier.
+   * @throws Exception
+   */
+  public void run() throws Exception {
+    String component = HBASE_INDEXER;
+    Configuration conf = getSentryConf();
+
+    String service = conf.get(SERVICE_NAME, getServiceName());
+
+    if (service == null) {
+      throw new IllegalArgumentException("Service was not defined. Please, use 
-s command option, or sentry.provider.backend.generic.service-name 
configuration entry.");
+    }
+
+    LOGGER.info(String.format("Context: component=%s, service=%s", component, 
service));
+    // instantiate a solr client for sentry service.  This sets the ugi, so 
must
+    // be done before getting the ugi below.
+    try(SentryGenericServiceClient client =
+                SentryGenericServiceClientFactory.create(conf)) {
+      UserGroupInformation ugi = UserGroupInformation.getLoginUser();
+      String requestorName = ugi.getShortUserName();
+
+      convertINIToSentryServiceCmds(component, service, requestorName, conf, 
client,
+              getPolicyFile(), getValidate(), getImportPolicy(), 
getCheckCompat());
+    }
+  }
+
+  private Configuration getSentryConf() {
+    Configuration conf = new Configuration();
+    conf.addResource(new Path(getConfPath()));
+    return conf;
+  }
+
+  private void convertINIToSentryServiceCmds(String component,
+      String service, String requestorName,
+      Configuration conf, SentryGenericServiceClient client,
+      String policyFile, boolean validate, boolean importPolicy,
+      boolean checkCompat) throws Exception {
+
+    //instantiate a file providerBackend for parsing
+    LOGGER.info("Reading policy file at: " + policyFile);
+    SimpleFileProviderBackend policyFileBackend =
+        new SimpleFileProviderBackend(conf, policyFile);
+    ProviderBackendContext context = new ProviderBackendContext();
+    
context.setValidators(IndexerPrivilegeModel.getInstance().getPrivilegeValidators());
+    policyFileBackend.initialize(context);
+    if (validate) {
+      validatePolicy(policyFileBackend);
+    }
+
+    if (checkCompat) {
+      checkCompat(policyFileBackend);
+    }
+
+    //import the relations about group,role and privilege into the DB store
+    Set<String> roles = Sets.newHashSet();
+    Table<String, String, Set<String>> groupRolePrivilegeTable =
+        policyFileBackend.getGroupRolePrivilegeTable();
+    GenericPrivilegeConverter converter = new 
GenericPrivilegeConverter(component, service, false);
+
+    for (String groupName : groupRolePrivilegeTable.rowKeySet()) {
+      for (String roleName : groupRolePrivilegeTable.columnKeySet()) {
+        if (!roles.contains(roleName)) {
+          LOGGER.info(dryRunMessage(importPolicy) + "Creating role: " + 
roleName.toLowerCase(Locale.US));
+          if (importPolicy) {
+            client.createRoleIfNotExist(requestorName, roleName, component);
+          }
+          roles.add(roleName);
+        }
+
+        Set<String> privileges = groupRolePrivilegeTable.get(groupName, 
roleName);
+        if (privileges == null) {
+          continue;
+        }
+        LOGGER.info(dryRunMessage(importPolicy) + "Adding role: " + 
roleName.toLowerCase(Locale.US) + " to group: " + groupName);
+        if (importPolicy) {
+          client.grantRoleToGroups(requestorName, roleName, component, 
Sets.newHashSet(groupName));
+        }
+
+        for (String permission : privileges) {
+          String action = null;
+
+          for (String authorizable : AUTHORIZABLE_SPLITTER.
+              trimResults().split(permission)) {
+            KeyValue kv = new KeyValue(authorizable);
+            String key = kv.getKey();
+            String value = kv.getValue();
+            if ("action".equalsIgnoreCase(key)) {
+              action = value;
+            }
+          }
+
+          // Service doesn't support not specifying action
+          if (action == null) {
+            permission += "->action=" + Action.ALL;
+          }
+          LOGGER.info(dryRunMessage(importPolicy) + "Adding permission: " + 
permission + " to role: " + roleName.toLowerCase(Locale.US));
+          if (importPolicy) {
+            client.grantPrivilege(requestorName, roleName, component, 
converter.fromString(permission));
+          }
+        }
+      }
+    }
+  }
+
+  private void validatePolicy(ProviderBackend backend) throws Exception {
+    try {
+      backend.validatePolicy(true);
+    } catch (SentryConfigurationException e) {
+      printConfigErrorsWarnings(e);
+      throw e;
+    }
+  }
+
+  private void printConfigErrorsWarnings(SentryConfigurationException 
configException) {
+    System.out.println(" *** Found configuration problems *** ");
+    for (String errMsg : configException.getConfigErrors()) {
+      System.out.println("ERROR: " + errMsg);
+    }
+    for (String warnMsg : configException.getConfigWarnings()) {
+      System.out.println("Warning: " + warnMsg);
+    }
+  }
+
+  private void checkCompat(SimpleFileProviderBackend backend) throws Exception 
{
+    Map<String, Set<String>> rolesCaseMapping = new HashMap<String, 
Set<String>>();
+    Table<String, String, Set<String>> groupRolePrivilegeTable =
+      backend.getGroupRolePrivilegeTable();
+
+    for (String roleName : groupRolePrivilegeTable.columnKeySet()) {
+      String roleNameLower = roleName.toLowerCase(Locale.US);
+      if (!roleName.equals(roleNameLower)) {
+        if (!rolesCaseMapping.containsKey(roleNameLower)) {
+          rolesCaseMapping.put(roleNameLower, Sets.newHashSet(roleName));
+        } else {
+          rolesCaseMapping.get(roleNameLower).add(roleName);
+        }
+      }
+    }
+
+    List<String> errors = new LinkedList<String>();
+    StringBuilder warningString = new StringBuilder();
+    if (!rolesCaseMapping.isEmpty()) {
+      warningString.append("The following roles names will be lower cased when 
added to the Sentry Service.\n");
+      warningString.append("This will cause document-level security to fail to 
match the role tokens.\n");
+      warningString.append("Role names: ");
+    }
+    boolean firstWarning = true;
+
+    for (Map.Entry<String, Set<String>> entry : rolesCaseMapping.entrySet()) {
+      Set<String> caseMapping = entry.getValue();
+      if (caseMapping.size() > 1) {
+        StringBuilder errorString = new StringBuilder();
+        errorString.append("The following (cased) roles map to the same role 
in the sentry service: ");
+        boolean first = true;
+        for (String casedRole : caseMapping) {
+          errorString.append(first ? "" : ", ");
+          errorString.append(casedRole);
+          first = false;
+        }
+        errorString.append(".  Role in service: ").append(entry.getKey());
+        errors.add(errorString.toString());
+      }
+
+      for (String casedRole : caseMapping) {
+        warningString.append(firstWarning? "" : ", ");
+        warningString.append(casedRole);
+        firstWarning = false;
+      }
+    }
+
+    for (String error : errors) {
+      System.out.println("ERROR: " + error);
+    }
+    System.out.println("\n");
+
+    System.out.println("Warning: " + warningString.toString());
+    if (errors.size() > 0) {
+      SentryConfigurationException ex =
+          new SentryConfigurationException("Compatibility check failure");
+      ex.setConfigErrors(errors);
+      ex.setConfigWarnings(Lists.<String>asList(warningString.toString(), new 
String[0]));
+      throw ex;
+    }
+  }
+
+  private String dryRunMessage(boolean importPolicy) {
+    if (importPolicy) {
+      return "";
+    } else {
+      return "[Dry Run] ";
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellGeneric.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellGeneric.java
 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellGeneric.java
index 1623f38..907e146 100644
--- 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellGeneric.java
+++ 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellGeneric.java
@@ -18,9 +18,7 @@
 
 package org.apache.sentry.provider.db.generic.tools;
 
-import java.util.List;
-import java.util.Set;
-
+import com.google.common.collect.Sets;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
@@ -35,7 +33,8 @@ import org.apache.sentry.provider.db.tools.ShellCommand;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
 
 /**
  * SentryShellGeneric is an admin tool, and responsible for the management of 
repository.
@@ -60,7 +59,7 @@ public class SentryShellGeneric extends SentryShellCommon {
                 SentryGenericServiceClientFactory.create(conf)) {
       UserGroupInformation ugi = UserGroupInformation.getLoginUser();
       String requestorName = ugi.getShortUserName();
-      TSentryPrivilegeConverter converter = new 
GenericPrivilegeConverter(component, service);
+      TSentryPrivilegeConverter converter = getPrivilegeConverter(component, 
service);
       ShellCommand command = new GenericShellCommand(client, component, 
service, converter);
 
       // check the requestor name
@@ -102,7 +101,11 @@ public class SentryShellGeneric extends SentryShellCommon {
     }
   }
 
-  private String getComponent() throws Exception {
+  protected GenericPrivilegeConverter getPrivilegeConverter(String component, 
String service) {
+    return new GenericPrivilegeConverter(component, service);
+  }
+
+  protected String getComponent() throws Exception {
     if (type == TYPE.kafka) {
       return AuthorizationComponent.KAFKA;
     } else if (type == TYPE.solr) {
@@ -114,7 +117,7 @@ public class SentryShellGeneric extends SentryShellCommon {
     throw new Exception("Invalid type specified for SentryShellGeneric: " + 
type);
   }
 
-  private String getService(Configuration conf) throws Exception {
+  protected String getService(Configuration conf) throws Exception {
     if (type == TYPE.kafka) {
       return conf.get(KAFKA_SERVICE_NAME, AuthorizationComponent.KAFKA);
     } else if (type == TYPE.solr) {

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellIndexer.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellIndexer.java
 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellIndexer.java
new file mode 100644
index 0000000..5bbe772
--- /dev/null
+++ 
b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/SentryShellIndexer.java
@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sentry.provider.db.generic.tools;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.hadoop.conf.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.apache.sentry.provider.common.AuthorizationComponent.HBASE_INDEXER;
+import static 
org.apache.sentry.service.thrift.ServiceConstants.ClientConfig.SERVICE_NAME;
+
+/**
+ * SentryShellIndexer 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 SentryShellIndexer extends SentryShellGeneric {
+
+  protected boolean isMigration = false;
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(SentryShellIndexer.class);
+
+  private final SentryConfigToolIndexer configTool = new 
SentryConfigToolIndexer();
+
+  @Override
+  protected void setupOptions(Options simpleShellOptions) {
+    super.setupOptions(simpleShellOptions);
+    configTool.setupOptions(simpleShellOptions);
+  }
+
+  @Override
+  protected void parseOptions(CommandLine cmd) throws ParseException {
+    super.parseOptions(cmd);
+    configTool.parseOptions(cmd);
+    for (Option opt : cmd.getOptions()) {
+      if (opt.getOpt().equals("mgr")) {
+        isMigration = true;
+      }
+    }
+  }
+
+  @Override
+  protected OptionGroup getMainOptions() {
+    OptionGroup mainOptions = super.getMainOptions();
+    Option mgrOpt = new Option("mgr", "migrate", false, "Migrate ini file to 
Sentry service");
+    mgrOpt.setRequired(false);
+    mainOptions.addOption(mgrOpt);
+    return mainOptions;
+  }
+
+  /**
+   * Processes the necessary command based on the arguments parsed earlier.
+   * @throws Exception
+   */
+  @Override
+  public void run() throws Exception {
+
+    if (isMigration) {
+      configTool.run();
+      return;
+    }
+
+    super.run();
+  }
+
+  @Override
+  protected String getComponent() throws Exception {
+    return HBASE_INDEXER;
+  }
+
+  @Override
+  protected String getService(Configuration conf) throws Exception {
+    String service = conf.get(SERVICE_NAME, serviceName);
+    if (service == null) {
+      throw new IllegalArgumentException("Service was not defined. Please, use 
-s command option, or sentry.provider.backend.generic.service-name 
configuration entry.");
+    }
+    return service;
+  }
+
+  /**
+   * Entry-point for Hbase indexer cli tool.
+   * @param args
+   * @throws Exception
+   */
+  public static void main(String[] args) throws Exception {
+    SentryShellIndexer sentryShell = new SentryShellIndexer();
+    try {
+      sentryShell.executeShell(args);
+    } catch (Exception e) {
+      LOGGER.error(e.getMessage(), e);
+      Throwable current = e;
+      // find the first printable message;
+      while (current != null && current.getMessage() == null) {
+        current = current.getCause();
+      }
+      System.out.println("The operation failed." +
+              (current.getMessage() == null ? "" : "  Message: " + 
current.getMessage()));
+      System.exit(1);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/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 5fbc961..c8b2eef 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
@@ -43,11 +43,13 @@ abstract public class SentryShellCommon {
   public static final String OPTION_DESC_ROLE_NAME = "Role name";
   public static final String OPTION_DESC_GROUP_NAME = "Group name";
   public static final String OPTION_DESC_PRIVILEGE = "Privilege string";
+  public final static String OPTION_DESC_SERVICE = "Name of the service being 
managed";
   public static final String PREFIX_MESSAGE_MISSING_OPTION = "Missing required 
option: ";
 
   public static final String GROUP_SPLIT_CHAR = ",";
 
   protected String roleName;
+  protected String serviceName;
   protected String groupName;
   protected String privilegeStr;
   protected String confPath;
@@ -90,47 +92,52 @@ abstract public class SentryShellCommon {
   protected boolean parseArgs(String[] args) {
     Options simpleShellOptions = new Options();
 
-    Option crOpt = new Option("cr", "create_role", false, "Create role");
-    crOpt.setRequired(false);
+    setupOptions(simpleShellOptions);
 
-    Option drOpt = new Option("dr", "drop_role", false, "Drop role");
-    drOpt.setRequired(false);
 
-    Option argOpt = new Option("arg", "add_role_group", false, "Add role to 
group");
-    argOpt.setRequired(false);
 
-    Option drgOpt = new Option("drg", "delete_role_group", false, "Delete role 
from group");
-    drgOpt.setRequired(false);
+    // help option
+    Option helpOpt = new Option("h", "help", false, OPTION_DESC_HELP);
+    helpOpt.setRequired(false);
+    simpleShellOptions.addOption(helpOpt);
 
-    Option gprOpt = new Option("gpr", "grant_privilege_role", false, "Grant 
privilege to role");
-    gprOpt.setRequired(false);
+    // this Options is parsed first for help option
+    Options helpOptions = new Options();
+    helpOptions.addOption(helpOpt);
 
-    Option rprOpt = new Option("rpr", "revoke_privilege_role", false, "Revoke 
privilege from role");
-    rprOpt.setRequired(false);
+    try {
+      Parser parser = new GnuParser();
 
-    Option lrOpt = new Option("lr", "list_role", false, "List role");
-    lrOpt.setRequired(false);
+      // parse help option first
+      CommandLine cmd = parser.parse(helpOptions, args, true);
+      for (Option opt : cmd.getOptions()) {
+        if (opt.getOpt().equals("h")) {
+          // get the help option, print the usage and exit
+          usage(simpleShellOptions);
+          return false;
+        }
+      }
 
-    Option lpOpt = new Option("lp", "list_privilege", false, "List privilege");
-    lpOpt.setRequired(false);
+      // without help option
+      cmd = parser.parse(simpleShellOptions, args);
 
-    Option lgOpt = new Option("lg", "list_group", false, "List groups");
-    lgOpt.setRequired(false);
+      parseOptions(cmd);
+    } catch (ParseException pe) {
+      System.out.println(pe.getMessage());
+      usage(simpleShellOptions);
+      return false;
+    }
+    return true;
+  }
 
-    // required args group
-    OptionGroup simpleShellOptGroup = new OptionGroup();
-    simpleShellOptGroup.addOption(crOpt);
-    simpleShellOptGroup.addOption(drOpt);
-    simpleShellOptGroup.addOption(argOpt);
-    simpleShellOptGroup.addOption(drgOpt);
-    simpleShellOptGroup.addOption(gprOpt);
-    simpleShellOptGroup.addOption(rprOpt);
-    simpleShellOptGroup.addOption(lrOpt);
-    simpleShellOptGroup.addOption(lpOpt);
-    simpleShellOptGroup.addOption(lgOpt);
-    simpleShellOptGroup.setRequired(true);
+  protected void setupOptions(Options simpleShellOptions) {
+    OptionGroup simpleShellOptGroup = getMainOptions();
     simpleShellOptions.addOptionGroup(simpleShellOptGroup);
 
+    Option sOpt = new Option("s", "service", true, OPTION_DESC_SERVICE);
+    sOpt.setRequired(false);
+    simpleShellOptions.addOption(sOpt);
+
     // optional args
     Option pOpt = new Option("p", "privilege", true, OPTION_DESC_PRIVILEGE);
     pOpt.setRequired(false);
@@ -153,86 +160,103 @@ abstract public class SentryShellCommon {
     Option sentrySitePathOpt = new Option("conf", "sentry_conf", true, 
OPTION_DESC_CONF);
     sentrySitePathOpt.setRequired(true);
     simpleShellOptions.addOption(sentrySitePathOpt);
+  }
 
-    // help option
-    Option helpOpt = new Option("h", "help", false, OPTION_DESC_HELP);
-    helpOpt.setRequired(false);
-    simpleShellOptions.addOption(helpOpt);
+  protected OptionGroup getMainOptions() {
+    OptionGroup simpleShellOptGroup = new OptionGroup();
+    Option crOpt = new Option("cr", "create_role", false, "Create role");
+    crOpt.setRequired(false);
 
-    // this Options is parsed first for help option
-    Options helpOptions = new Options();
-    helpOptions.addOption(helpOpt);
+    Option drOpt = new Option("dr", "drop_role", false, "Drop role");
+    drOpt.setRequired(false);
 
-    try {
-      Parser parser = new GnuParser();
+    Option argOpt = new Option("arg", "add_role_group", false, "Add role to 
group");
+    argOpt.setRequired(false);
 
-      // parse help option first
-      CommandLine cmd = parser.parse(helpOptions, args, true);
-      for (Option opt : cmd.getOptions()) {
-        if (opt.getOpt().equals("h")) {
-          // get the help option, print the usage and exit
-          usage(simpleShellOptions);
-          return false;
-        }
-      }
+    Option drgOpt = new Option("drg", "delete_role_group", false, "Delete role 
from group");
+    drgOpt.setRequired(false);
 
-      // without help option
-      cmd = parser.parse(simpleShellOptions, args);
+    Option gprOpt = new Option("gpr", "grant_privilege_role", false, "Grant 
privilege to role");
+    gprOpt.setRequired(false);
 
-      for (Option opt : cmd.getOptions()) {
-        if (opt.getOpt().equals("p")) {
-          privilegeStr = opt.getValue();
-        } else if (opt.getOpt().equals("g")) {
-          groupName = opt.getValue();
-        } else if (opt.getOpt().equals("r")) {
-          roleName = opt.getValue();
-        } else if (opt.getOpt().equals("cr")) {
-          isCreateRole = true;
-          roleNameRequired = true;
-        } else if (opt.getOpt().equals("dr")) {
-          isDropRole = true;
-          roleNameRequired = true;
-        } else if (opt.getOpt().equals("arg")) {
-          isAddRoleGroup = true;
-          roleNameRequired = true;
-          groupNameRequired = true;
-        } else if (opt.getOpt().equals("drg")) {
-          isDeleteRoleGroup = true;
-          roleNameRequired = true;
-          groupNameRequired = true;
-        } else if (opt.getOpt().equals("gpr")) {
-          isGrantPrivilegeRole = true;
-          roleNameRequired = true;
-          privilegeStrRequired = true;
-        } else if (opt.getOpt().equals("rpr")) {
-          isRevokePrivilegeRole = true;
-          roleNameRequired = true;
-          privilegeStrRequired = true;
-        } else if (opt.getOpt().equals("lr")) {
-          isListRole = true;
-        } else if (opt.getOpt().equals("lp")) {
-          isListPrivilege = true;
-          roleNameRequired = true;
-        } else if (opt.getOpt().equals("lg")) {
-          isListGroup = true;
-        } else if (opt.getOpt().equals("conf")) {
-          confPath = opt.getValue();
-        } else if (opt.getOpt().equals("t")) {
-          type = TYPE.valueOf(opt.getValue());
-        }
+    Option rprOpt = new Option("rpr", "revoke_privilege_role", false, "Revoke 
privilege from role");
+    rprOpt.setRequired(false);
+
+    Option lrOpt = new Option("lr", "list_role", false, "List role");
+    lrOpt.setRequired(false);
+
+    Option lpOpt = new Option("lp", "list_privilege", false, "List privilege");
+    lpOpt.setRequired(false);
+
+    Option lgOpt = new Option("lg", "list_group", false, "List groups");
+    lgOpt.setRequired(false);
+
+
+    // required args group
+    simpleShellOptGroup.addOption(crOpt);
+    simpleShellOptGroup.addOption(drOpt);
+    simpleShellOptGroup.addOption(argOpt);
+    simpleShellOptGroup.addOption(drgOpt);
+    simpleShellOptGroup.addOption(gprOpt);
+    simpleShellOptGroup.addOption(rprOpt);
+    simpleShellOptGroup.addOption(lrOpt);
+    simpleShellOptGroup.addOption(lpOpt);
+    simpleShellOptGroup.addOption(lgOpt);
+    simpleShellOptGroup.setRequired(true);
+    return simpleShellOptGroup;
+  }
+
+  protected void parseOptions(CommandLine cmd) throws ParseException {
+    for (Option opt : cmd.getOptions()) {
+      if (opt.getOpt().equals("p")) {
+        privilegeStr = opt.getValue();
+      } else if (opt.getOpt().equals("g")) {
+        groupName = opt.getValue();
+      } else if (opt.getOpt().equals("r")) {
+        roleName = opt.getValue();
+      } else if (opt.getOpt().equals("s")) {
+        serviceName = opt.getValue();
+      } else if (opt.getOpt().equals("cr")) {
+        isCreateRole = true;
+        roleNameRequired = true;
+      } else if (opt.getOpt().equals("dr")) {
+        isDropRole = true;
+        roleNameRequired = true;
+      } else if (opt.getOpt().equals("arg")) {
+        isAddRoleGroup = true;
+        roleNameRequired = true;
+        groupNameRequired = true;
+      } else if (opt.getOpt().equals("drg")) {
+        isDeleteRoleGroup = true;
+        roleNameRequired = true;
+        groupNameRequired = true;
+      } else if (opt.getOpt().equals("gpr")) {
+        isGrantPrivilegeRole = true;
+        roleNameRequired = true;
+        privilegeStrRequired = true;
+      } else if (opt.getOpt().equals("rpr")) {
+        isRevokePrivilegeRole = true;
+        roleNameRequired = true;
+        privilegeStrRequired = true;
+      } else if (opt.getOpt().equals("lr")) {
+        isListRole = true;
+      } else if (opt.getOpt().equals("lp")) {
+        isListPrivilege = true;
+        roleNameRequired = true;
+      } else if (opt.getOpt().equals("lg")) {
+        isListGroup = true;
+      } else if (opt.getOpt().equals("conf")) {
+        confPath = opt.getValue();
+      } else if (opt.getOpt().equals("t")) {
+        type = TYPE.valueOf(opt.getValue());
       }
-      checkRequiredParameter(roleNameRequired, roleName, 
OPTION_DESC_ROLE_NAME);
-      checkRequiredParameter(groupNameRequired, groupName, 
OPTION_DESC_GROUP_NAME);
-      checkRequiredParameter(privilegeStrRequired, privilegeStr, 
OPTION_DESC_PRIVILEGE);
-    } catch (ParseException pe) {
-      System.out.println(pe.getMessage());
-      usage(simpleShellOptions);
-      return false;
     }
-    return true;
+    checkRequiredParameter(roleNameRequired, roleName, OPTION_DESC_ROLE_NAME);
+    checkRequiredParameter(groupNameRequired, groupName, 
OPTION_DESC_GROUP_NAME);
+    checkRequiredParameter(privilegeStrRequired, privilegeStr, 
OPTION_DESC_PRIVILEGE);
   }
 
-  private void checkRequiredParameter(boolean isRequired, String paramValue, 
String paramName) throws ParseException {
+  protected void checkRequiredParameter(boolean isRequired, String paramValue, 
String paramName) throws ParseException {
     if (isRequired && StringUtils.isEmpty(paramValue)) {
       throw new ParseException(PREFIX_MESSAGE_MISSING_OPTION + paramName);
     }

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryConfigToolIndexer.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryConfigToolIndexer.java
 
b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryConfigToolIndexer.java
new file mode 100644
index 0000000..4dddf78
--- /dev/null
+++ 
b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryConfigToolIndexer.java
@@ -0,0 +1,263 @@
+ /**
+ * 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.Sets;
+ import com.google.common.io.Files;
+ import org.apache.commons.io.FileUtils;
+ import org.apache.sentry.core.common.exception.SentryConfigurationException;
+ import 
org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceIntegrationBase;
+ import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+ import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole;
+ import org.apache.sentry.service.thrift.ServiceConstants;
+ import org.junit.After;
+ import org.junit.Before;
+ import org.junit.Test;
+
+ import java.io.File;
+ import java.io.FileOutputStream;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.Map;
+ import java.util.Set;
+
+ import static 
org.apache.sentry.provider.common.AuthorizationComponent.HBASE_INDEXER;
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.assertTrue;
+ import static org.junit.Assert.fail;
+
+ public class TestSentryConfigToolIndexer extends 
SentryGenericServiceIntegrationBase {
+   private static String RESOURCES_DIR = "target" + File.separator + 
"test-classes" + File.separator;
+   private static String VALID_POLICY_INI = RESOURCES_DIR + 
"indexer_config_import_tool.ini";
+   private static String INVALID_POLICY_INI = RESOURCES_DIR + 
"indexer_invalid.ini";
+   private static String CASE_POLICY_INI = RESOURCES_DIR + "indexer_case.ini";
+   private File confDir;
+   private File confPath;
+   private String requestorName = "";
+   private String service = "service1";
+
+   @Before
+   public void prepareForTest() throws Exception {
+     confDir = Files.createTempDir();
+     confPath = new File(confDir, "sentry-site.xml");
+     conf.set(ServiceConstants.ClientConfig.SERVICE_NAME, service);
+     if (confPath.createNewFile()) {
+       FileOutputStream to = new FileOutputStream(confPath);
+       conf.writeXml(to);
+       to.close();
+     }
+     requestorName = 
clientUgi.getShortUserName();//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);
+
+     // clear roles and privileges
+     Set<TSentryRole> tRoles = client.listAllRoles(requestorName, 
HBASE_INDEXER);
+     for (TSentryRole tRole : tRoles) {
+       String role = tRole.getRoleName();
+       Set<TSentryPrivilege> privileges = client.listAllPrivilegesByRoleName(
+           requestorName, role, HBASE_INDEXER, service);
+       for (TSentryPrivilege privilege : privileges) {
+         client.revokePrivilege(requestorName, role, HBASE_INDEXER, privilege);
+       }
+       client.dropRole(requestorName, role, HBASE_INDEXER);
+     }
+   }
+
+   @Test
+   public void testConvertIni() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         String[] args = {"-mgr", "-f", VALID_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-v", "-i"};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         sentryTool.executeShell(args);
+
+         Map<String, Set<String>> groupMapping = new HashMap<String, 
Set<String>>();
+         groupMapping.put("corporal_role", Sets.newHashSet("corporal", 
"sergeant", "general", "commander_in_chief"));
+         groupMapping.put("sergeant_role", Sets.newHashSet("sergeant", 
"general", "commander_in_chief"));
+         groupMapping.put("general_role", Sets.newHashSet("general", 
"commander_in_chief"));
+         groupMapping.put("commander_in_chief_role", 
Sets.newHashSet("commander_in_chief"));
+
+
+         Map<String, Set<String>> privilegeMapping = new HashMap<String, 
Set<String>>();
+         privilegeMapping.put("corporal_role",
+             Sets.newHashSet("Indexer=info->action=read", 
"Indexer=info->action=write"));
+         privilegeMapping.put("sergeant_role",
+             Sets.newHashSet("Indexer=info->action=write"));
+         privilegeMapping.put("general_role",
+             Sets.newHashSet("Indexer=info->action=*"));
+         privilegeMapping.put("commander_in_chief_role",
+             Sets.newHashSet("Indexer=*->action=*"));
+
+         // check roles
+         Set<TSentryRole> tRoles = client.listAllRoles(requestorName, 
HBASE_INDEXER);
+         assertEquals("Unexpected number of roles", 
groupMapping.keySet().size(), tRoles.size());
+         Set<String> roles = new HashSet<String>();
+         for (TSentryRole tRole : tRoles) {
+           roles.add(tRole.getRoleName());
+         }
+
+         for (String expectedRole : groupMapping.keySet()) {
+           assertTrue("Didn't find expected role: " + expectedRole, 
roles.contains(expectedRole));
+         }
+
+         // check groups
+         for (TSentryRole tRole : tRoles) {
+           Set<String> expectedGroups = groupMapping.get(tRole.getRoleName());
+           assertEquals("Group size doesn't match for role: " + 
tRole.getRoleName(),
+               expectedGroups.size(), tRole.getGroups().size());
+           assertTrue("Group does not contain all expected members for role: " 
+ tRole.getRoleName(),
+               tRole.getGroups().containsAll(expectedGroups));
+         }
+
+         // check privileges
+         GenericPrivilegeConverter convert = new 
GenericPrivilegeConverter(HBASE_INDEXER, service);
+         for (String role : roles) {
+           Set<TSentryPrivilege> privileges = 
client.listAllPrivilegesByRoleName(
+               requestorName, role, HBASE_INDEXER, service);
+           Set<String> expectedPrivileges = privilegeMapping.get(role);
+           assertEquals("Privilege set size doesn't match for role: " + role,
+               expectedPrivileges.size(), privileges.size());
+
+           Set<String> privilegeStrs = new HashSet<String>();
+           for (TSentryPrivilege privilege : privileges) {
+             privilegeStrs.add(convert.toString(privilege));
+           }
+
+           for (String expectedPrivilege : expectedPrivileges) {
+             assertTrue("Did not find expected privilege: " + 
expectedPrivilege,
+                 privilegeStrs.contains(expectedPrivilege));
+           }
+         }
+       }
+     });
+   }
+
+   @Test
+   public void testNoPolicyFile() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         String[] args = { "-mgr", "-f", INVALID_POLICY_INI + "Foobar", 
"-conf", confPath.getAbsolutePath(), "-v", "-i"};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         try {
+           sentryTool.executeShell(args);
+           fail("Exception should be thrown for nonexistant ini");
+         } catch (SentryConfigurationException e) {
+           // expected exception
+         }
+       }
+     });
+   }
+
+   @Test
+   public void testNoValidateNorImport() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         String[] args = { "-mgr", "-f", INVALID_POLICY_INI, "-conf", 
confPath.getAbsolutePath()};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         try {
+           sentryTool.executeShell(args);
+           fail("Exception should be thrown for validating invalid ini");
+         } catch (IllegalArgumentException e) {
+           // expected exception
+         }
+       }
+     });
+   }
+
+   @Test
+   public void testConvertInvalidIni() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         // test: validate an invalid ini
+         String[] args = { "-mgr", "-f", INVALID_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-v", "-i"};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         try {
+           sentryTool.executeShell(args);
+           fail("Exception should be thrown for validating invalid ini");
+         } catch (SentryConfigurationException e) {
+           // expected exception
+         }
+
+         // test without validating, should not error
+         args = new String[] { "-mgr", "-f", INVALID_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-i"};
+         sentryTool = new SentryShellIndexer();
+         sentryTool.executeShell(args);
+       }
+     });
+   }
+
+   @Test
+   public void testCompatCheck() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         // test: validate an invalid ini
+         String[] args = { "-mgr", "-f", CASE_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-v", "-i", "-c"};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         try {
+           sentryTool.executeShell(args);
+           fail("Exception should be thrown for validating invalid ini");
+         } catch (SentryConfigurationException e) {
+           assertEquals("Expected error", 1, e.getConfigErrors().size());
+           String error = e.getConfigErrors().get(0);
+           assertCasedRoleNamesInMessage(error, "RoLe1", "rOlE1");
+           String warning = e.getConfigWarnings().get(0);
+           assertCasedRoleNamesInMessage(warning, "ROLE2", "RoLe1", "rOlE1");
+           assertEquals("Expected warning", 1, e.getConfigWarnings().size());
+         }
+
+         // test without compat checking
+         args = new String[] { "-mgr", "-f", CASE_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-i", "-v"};
+         sentryTool = new SentryShellIndexer();
+         sentryTool.executeShell(args);
+       }
+     });
+   }
+
+   // Test that a valid compat check doesn't throw an exception
+   @Test
+   public void testCompatCheckValid() throws Exception {
+     runTestAsSubject(new TestOperation() {
+       @Override
+       public void runTestAsSubject() throws Exception {
+         String[] args = { "-mgr", "-f", VALID_POLICY_INI, "-conf", 
confPath.getAbsolutePath(), "-v", "-i", "-c"};
+         SentryShellIndexer sentryTool = new SentryShellIndexer();
+         sentryTool.executeShell(args);
+       }
+     });
+   }
+
+   private void assertCasedRoleNamesInMessage(String message, String ... 
casedRoleNames) {
+     for (String casedRoleName : casedRoleNames) {
+       assertTrue("Expected cased role name: " + casedRoleName, 
message.contains(casedRoleName));
+     }
+   }
+ }

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellIndexer.java
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellIndexer.java
 
b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellIndexer.java
new file mode 100644
index 0000000..f66eb85
--- /dev/null
+++ 
b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestSentryShellIndexer.java
@@ -0,0 +1,526 @@
+/**
+ * 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.Sets;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import 
org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceIntegrationBase;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege;
+import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole;
+import org.apache.sentry.provider.db.tools.SentryShellCommon;
+import org.apache.sentry.service.thrift.ServiceConstants;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static junit.framework.Assert.assertEquals;
+import static 
org.apache.sentry.provider.common.AuthorizationComponent.HBASE_INDEXER;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestSentryShellIndexer extends 
SentryGenericServiceIntegrationBase {
+  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");
+    conf.set(ServiceConstants.ClientConfig.SERVICE_NAME, service);
+    if (confPath.createNewFile()) {
+      FileOutputStream to = new FileOutputStream(confPath);
+      conf.writeXml(to);
+      to.close();
+    }
+    requestorName = clientUgi.getShortUserName();
+    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() };
+        SentryShellIndexer.main(args);
+        // test: create role with --create_role
+        args = new String[] { "--create_role", "-r", TEST_ROLE_NAME_2, "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+
+        // validate the result, list roles with -lr
+        args = new String[] { "-lr", "-conf", confPath.getAbsolutePath() };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        Set<String> roleNames = getShellResultWithOSRedirect(sentryShell, 
args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1, TEST_ROLE_NAME_2);
+
+        // validate the result, list roles with --list_role
+        args = new String[] { "--list_role", "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1, TEST_ROLE_NAME_2);
+
+        // test: drop role with -dr
+        args = new String[] { "-dr", "-r", TEST_ROLE_NAME_1, "-conf", 
confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+        // test: drop role with --drop_role
+        args = new String[] { "--drop_role", "-r", TEST_ROLE_NAME_2, "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+
+        // validate the result
+        Set<TSentryRole> roles = client.listAllRoles(requestorName, 
HBASE_INDEXER);
+        assertEquals("Incorrect number of roles", 0, roles.size());
+      }
+    });
+  }
+
+  @Test
+  public void testAddDeleteRoleForGroup() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+        // Group names are case sensitive - mixed case names should work
+        String TEST_GROUP_1 = "testGroup1";
+        String TEST_GROUP_2 = "testGroup2";
+        String TEST_GROUP_3 = "testGroup3";
+
+        // create the role for test
+        client.createRole(requestorName, TEST_ROLE_NAME_1, HBASE_INDEXER);
+        client.createRole(requestorName, TEST_ROLE_NAME_2, HBASE_INDEXER);
+        // test: add role to group with -arg
+        String[] args = { "-arg", "-r", TEST_ROLE_NAME_1, "-g", TEST_GROUP_1, 
"-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+        // test: add role to multiple groups
+        args = new String[] { "-arg", "-r", TEST_ROLE_NAME_1, "-g", 
TEST_GROUP_2 + "," + TEST_GROUP_3,
+            "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+        // test: add role to group with --add_role_group
+        args = new String[] { "--add_role_group", "-r", TEST_ROLE_NAME_2, 
"-g", TEST_GROUP_1,
+            "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+
+        // validate the result list roles with -lr and -g
+        args = new String[] { "-lr", "-g", TEST_GROUP_1, "-conf", 
confPath.getAbsolutePath() };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        Set<String> roleNames = getShellResultWithOSRedirect(sentryShell, 
args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1, TEST_ROLE_NAME_2);
+
+        // list roles with --list_role and -g
+        args = new String[] { "--list_role", "-g", TEST_GROUP_2, "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1);
+
+        args = new String[] { "--list_role", "-g", TEST_GROUP_3, "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1);
+
+        // test: delete role from group with -drg
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_1, "-g", 
TEST_GROUP_1, "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+        // test: delete role to multiple groups
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_1, "-g", 
TEST_GROUP_2 + "," + TEST_GROUP_3,
+            "-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+        // test: delete role from group with --delete_role_group
+        args = new String[] { "--delete_role_group", "-r", TEST_ROLE_NAME_2, 
"-g", TEST_GROUP_1,
+            "-conf", confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+
+        // validate the result
+        Set<TSentryRole> roles = client.listRolesByGroupName(requestorName, 
TEST_GROUP_1, HBASE_INDEXER);
+        assertEquals("Incorrect number of roles", 0, roles.size());
+        roles = client.listRolesByGroupName(requestorName, TEST_GROUP_2, 
HBASE_INDEXER);
+        assertEquals("Incorrect number of roles", 0, roles.size());
+        roles = client.listRolesByGroupName(requestorName, TEST_GROUP_3, 
HBASE_INDEXER);
+        assertEquals("Incorrect number of roles", 0, roles.size());
+        // clear the test data
+        client.dropRole(requestorName, TEST_ROLE_NAME_1, HBASE_INDEXER);
+        client.dropRole(requestorName, TEST_ROLE_NAME_2, HBASE_INDEXER);
+      }
+    });
+  }
+
+  @Test
+  public void testCaseSensitiveGroupName() throws Exception {
+    runTestAsSubject(new TestOperation() {
+      @Override
+      public void runTestAsSubject() throws Exception {
+
+        // create the role for test
+        client.createRole(requestorName, TEST_ROLE_NAME_1, HBASE_INDEXER);
+        // add role to a group (lower case)
+        String[] args = { "-arg", "-r", TEST_ROLE_NAME_1, "-g", "group1", 
"-conf",
+            confPath.getAbsolutePath() };
+        SentryShellIndexer.main(args);
+
+        // validate the roles when group name is same case as above
+        args = new String[] { "-lr", "-g", "group1", "-conf", 
confPath.getAbsolutePath() };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        Set<String> roleNames = getShellResultWithOSRedirect(sentryShell, 
args, true);
+        validateRoleNames(roleNames, TEST_ROLE_NAME_1);
+
+        // roles should be empty when group name is different case than above
+        args = new String[] { "-lr", "-g", "GROUP1", "-conf", 
confPath.getAbsolutePath() };
+        roleNames = getShellResultWithOSRedirect(sentryShell, args, true);
+        validateRoleNames(roleNames);
+      }
+      });
+    }
+
+  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, HBASE_INDEXER);
+        client.createRole(requestorName, TEST_ROLE_NAME_2, HBASE_INDEXER);
+
+        String [] privs = {
+          "Indexer=*->action=*",
+          "Indexer=indexer1->action=read",
+          "Indexer=indexer2->action=write"
+        };
+        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() };
+          SentryShellIndexer.main(args);
+        }
+
+        // test the list privilege
+        String [] args = new String[] { list(shortOption), "-r", 
TEST_ROLE_NAME_1, "-conf", confPath.getAbsolutePath() };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        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() };
+          SentryShellIndexer.main(args);
+          Set<TSentryPrivilege> privileges = 
client.listAllPrivilegesByRoleName(requestorName,
+            TEST_ROLE_NAME_1, HBASE_INDEXER, service);
+          assertEquals("Incorrect number of privileges", privs.length - (i + 
1), privileges.size());
+        }
+
+        // clear the test data
+        client.dropRole(requestorName, TEST_ROLE_NAME_1, HBASE_INDEXER);
+        client.dropRole(requestorName, TEST_ROLE_NAME_2, HBASE_INDEXER);
+      }
+    });
+  }
+
+
+  @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, HBASE_INDEXER);
+        // test: create duplicate role with -cr
+        String[] args = { "-cr", "-r", TEST_ROLE_NAME_1, "-conf", 
confPath.getAbsolutePath() };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        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 SentryShellIndexer();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for dropping non-exist role");
+        } catch (SentryUserException e) {
+          // excepted exception
+        }
+
+        // test: add non-exist role to group with -arg
+        args = new String[] { "-arg", "-r", TEST_ROLE_NAME_2, "-g", 
"testGroup1", "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for granting non-exist role to 
group");
+        } catch (SentryUserException e) {
+          // excepted exception
+        }
+
+        // test: drop group from non-exist role with -drg
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_2, "-g", 
"testGroup1", "-conf",
+            confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        try {
+          sentryShell.executeShell(args);
+          fail("Exception should be thrown for drop group from 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 SentryShellIndexer();
+        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 SentryShellIndexer();
+        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, HBASE_INDEXER);
+      }
+    });
+  }
+
+  @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, HBASE_INDEXER);
+        // test: the conf is required argument
+        String[] args = { "-cr", "-r", TEST_ROLE_NAME_1 };
+        SentryShellIndexer sentryShell = new SentryShellIndexer();
+        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 SentryShellIndexer();
+        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 SentryShellIndexer();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + 
SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -r is required when add role to group
+        args = new String[] { "-arg", "-g", "testGroup1", "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + 
SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -g is required when add role to group
+        args = new String[] { "-arg", "-r", TEST_ROLE_NAME_2, "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + 
SentryShellCommon.OPTION_DESC_GROUP_NAME);
+
+        // test: -r is required when delete role from group
+        args = new String[] { "-drg", "-g", "testGroup1", "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + 
SentryShellCommon.OPTION_DESC_ROLE_NAME);
+
+        // test: -g is required when delete role from group
+        args = new String[] { "-drg", "-r", TEST_ROLE_NAME_2, "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        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", "indexer=Indexer1", "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        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 SentryShellIndexer();
+        validateMissingParameterMsg(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + 
SentryShellCommon.OPTION_DESC_PRIVILEGE);
+
+        // test: action is required in privilege
+        args = new String[] { "-gpr", "-r", TEST_ROLE_NAME_1, "-conf", 
confPath.getAbsolutePath(), "-p", "indexer=Indexer1" };
+        sentryShell = new SentryShellIndexer();
+         try {
+          getShellResultWithOSRedirect(sentryShell, args, false);
+          fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+          assert("Privilege is invalid: action required but not 
specified.".equals(e.getMessage()));
+        }
+
+        // test: -r is required when revoke privilege from role
+        args = new String[] { "-rpr", "-p", "indexer=Indexer1", "-conf", 
confPath.getAbsolutePath() };
+        sentryShell = new SentryShellIndexer();
+        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 SentryShellIndexer();
+        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 SentryShellIndexer();
+        validateMissingParameterMsgsContains(sentryShell, args,
+                SentryShellCommon.PREFIX_MESSAGE_MISSING_OPTION + "[",
+                "-arg Add role to group",
+                "-cr Create role",
+                "-rpr Revoke privilege from role",
+                "-drg Delete role from group",
+                "-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, HBASE_INDEXER);
+      }
+    });
+  }
+
+  // redirect the System.out to ByteArrayOutputStream, then execute the 
command and parse the result.
+  private Set<String> getShellResultWithOSRedirect(SentryShellIndexer 
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));
+    String outContentStr = outContent.toString();
+    Set<String> resultSet = outContentStr.length() > 0 ? 
Sets.<String>newHashSet(outContentStr.split("\n")) : Sets.<String>newHashSet();
+    System.setOut(oldOut);
+    return resultSet;
+  }
+
+  private void validateRoleNames(Set<String> roleNames, String ... 
expectedRoleNames) {
+    if (expectedRoleNames != null && expectedRoleNames.length > 0) {
+      assertEquals("Found: " + roleNames.size() + " roles, expected: " + 
expectedRoleNames.length,
+          expectedRoleNames.length, roleNames.size());
+      Set<String> lowerCaseRoles = new HashSet<String>();
+      for (String role : roleNames) {
+        lowerCaseRoles.add(role.toLowerCase());
+      }
+
+      for (String expectedRole : expectedRoleNames) {
+        assertTrue("Expected role: " + expectedRole,
+            lowerCaseRoles.contains(expectedRole.toLowerCase()));
+      }
+    }
+  }
+
+  private void validateMissingParameterMsg(SentryShellIndexer sentryShell, 
String[] args,
+      String expectedErrorMsg) throws Exception {
+    Set<String> errorMsgs = getShellResultWithOSRedirect(sentryShell, args, 
false);
+    assertTrue("Expected error message: " + expectedErrorMsg, 
errorMsgs.contains(expectedErrorMsg));
+  }
+
+  private void validateMissingParameterMsgsContains(SentryShellIndexer 
sentryShell, String[] args,
+      String ... expectedErrorMsgsContains) throws Exception {
+    Set<String> errorMsgs = getShellResultWithOSRedirect(sentryShell, args, 
false);
+    boolean foundAllMessages = false;
+    Iterator<String> it = errorMsgs.iterator();
+    while (it.hasNext()) {
+      String errorMessage = it.next();
+      boolean missingExpected = false;
+      for (String expectedContains : expectedErrorMsgsContains) {
+        if (!errorMessage.contains(expectedContains)) {
+          missingExpected = true;
+          break;
+        }
+      }
+      if (!missingExpected) {
+        foundAllMessages = true;
+        break;
+      }
+    }
+    assertTrue(foundAllMessages);
+  }
+}

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/test/resources/indexer_case.ini
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/test/resources/indexer_case.ini 
b/sentry-provider/sentry-provider-db/src/test/resources/indexer_case.ini
new file mode 100644
index 0000000..f1afe1f
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/test/resources/indexer_case.ini
@@ -0,0 +1,26 @@
+# 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.
+
+[groups]
+groupa = RoLe1
+groupb = rOlE1
+groupc = ROLE2
+
+[roles]
+RoLe1 = indexer=*
+rOlE1 = indexer=*
+ROLE2 = indexer=*

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/test/resources/indexer_config_import_tool.ini
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/test/resources/indexer_config_import_tool.ini
 
b/sentry-provider/sentry-provider-db/src/test/resources/indexer_config_import_tool.ini
new file mode 100644
index 0000000..c1bfe4b
--- /dev/null
+++ 
b/sentry-provider/sentry-provider-db/src/test/resources/indexer_config_import_tool.ini
@@ -0,0 +1,29 @@
+# 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.
+
+[groups]
+corporal = corporal_role
+sergeant = corporal_role, sergeant_role
+general = corporal_role, sergeant_role, general_role
+commander_in_chief = corporal_role, sergeant_role, general_role, 
commander_in_chief_role
+
+[roles]
+corporal_role = indexer=info->action=read, \
+  indexer=info->action=write
+sergeant_role = indexer=info->action=write
+general_role = indexer=info->action=*
+commander_in_chief_role = indexer=*

http://git-wip-us.apache.org/repos/asf/sentry/blob/5a7b0764/sentry-provider/sentry-provider-db/src/test/resources/indexer_invalid.ini
----------------------------------------------------------------------
diff --git 
a/sentry-provider/sentry-provider-db/src/test/resources/indexer_invalid.ini 
b/sentry-provider/sentry-provider-db/src/test/resources/indexer_invalid.ini
new file mode 100644
index 0000000..03083a7
--- /dev/null
+++ b/sentry-provider/sentry-provider-db/src/test/resources/indexer_invalid.ini
@@ -0,0 +1,21 @@
+# 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.
+
+[groups]
+
+[roles]
+

Reply via email to