You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by pr...@apache.org on 2015/02/20 09:44:44 UTC

incubator-sentry git commit: SENTRY-648: Add e2e tests for Sentry HA (Prasad Mujumdar reviewed by Lenni Kuff)

Repository: incubator-sentry
Updated Branches:
  refs/heads/master 427705ff8 -> 36274c25c


SENTRY-648: Add e2e tests for Sentry HA (Prasad Mujumdar reviewed by Lenni Kuff)


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

Branch: refs/heads/master
Commit: 36274c25c14370917981a53f63d8348a0c4a8bc4
Parents: 427705f
Author: Prasad Mujumdar <pr...@apache.org>
Authored: Fri Feb 20 00:38:32 2015 -0800
Committer: Prasad Mujumdar <pr...@apache.org>
Committed: Fri Feb 20 00:38:32 2015 -0800

----------------------------------------------------------------------
 .../db/service/persistent/ServiceManager.java   |  24 ++-
 .../thrift/HAClientInvocationHandler.java       |  50 +++---
 .../sentry/service/thrift/SentryService.java    |  24 ++-
 .../sentry/service/thrift/ServiceConstants.java |   2 +-
 .../sentry/tests/e2e/ha/TestHaEnd2End.java      | 171 +++++++++++++++++++
 .../AbstractTestWithStaticConfiguration.java    |  62 ++++---
 .../tests/e2e/minisentry/InternalSentrySrv.java | 164 ++++++++++++++++++
 .../sentry/tests/e2e/minisentry/SentrySrv.java  |  79 +++++++++
 .../tests/e2e/minisentry/SentrySrvFactory.java  |  45 +++++
 9 files changed, 564 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/ServiceManager.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/ServiceManager.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/ServiceManager.java
index 2274f00..a970b92 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/ServiceManager.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/ServiceManager.java
@@ -29,11 +29,20 @@ import org.apache.curator.x.discovery.ServiceInstance;
 import org.apache.curator.x.discovery.ServiceProvider;
 import org.apache.curator.x.discovery.details.InstanceSerializer;
 import org.apache.hadoop.net.NetUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+/***
+ * ServerManager handles registration of the Sentry service for Curator service
+ * discovery. Each server registers with ZK and add its host:port details which
+ * is used by the clients to discover available servers
+ */
 public class ServiceManager {
-
+  private static final Logger LOGGER = LoggerFactory
+      .getLogger(ServiceManager.class);
   private HAContext haContext;
   private ServiceProvider<Void> serviceProvider;
+  private ServiceDiscovery<Void> serviceDiscovery;
 
   public ServiceManager(HAContext haContext) throws IOException {
     this.haContext = haContext;
@@ -47,7 +56,7 @@ public class ServiceManager {
         curatorFramework.start();
       }
       InstanceSerializer<Void> instanceSerializer = new FixedJsonInstanceSerializer<Void>(Void.class);
-      ServiceDiscovery<Void> serviceDiscovery = ServiceDiscoveryBuilder.<Void>builder(Void.class)
+      serviceDiscovery = ServiceDiscoveryBuilder.<Void>builder(Void.class)
                 .basePath(HAContext.SENTRY_SERVICE_REGISTER_NAMESPACE)
                 .serializer(instanceSerializer)
                 .client(curatorFramework)
@@ -80,4 +89,15 @@ public class ServiceManager {
   public static InetSocketAddress convertServiceInstance(ServiceInstance<?> service) {
     return NetUtils.createSocketAddr(service.getAddress(),service.getPort());
   }
+
+  public void close() {
+    try {
+      serviceProvider.close();
+      serviceDiscovery.close();
+      haContext.getCuratorFramework().close();
+      LOGGER.debug("Closed ZK resources");
+    } catch (IOException e) {
+      LOGGER.warn("Error closing the service manager", e);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java
index c6e265f..1dea938 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/HAClientInvocationHandler.java
@@ -25,7 +25,6 @@ import java.net.InetSocketAddress;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.SecurityUtil;
-
 import org.apache.curator.x.discovery.ServiceInstance;
 import org.apache.sentry.SentryUserException;
 import org.apache.sentry.provider.db.service.persistent.HAContext;
@@ -43,7 +42,7 @@ public class HAClientInvocationHandler implements InvocationHandler {
   private static final Logger LOGGER = LoggerFactory.getLogger(HAClientInvocationHandler.class);
 
   private final Configuration conf;
-  private final ServiceManager manager;
+  private ServiceManager manager;
   private ServiceInstance<Void> currentServiceInstance;
   private SentryPolicyServiceClient client = null;
 
@@ -51,7 +50,6 @@ public class HAClientInvocationHandler implements InvocationHandler {
 
   public HAClientInvocationHandler(Configuration conf) throws Exception {
     this.conf = conf;
-    manager = new ServiceManager(new HAContext(conf));
     checkClientConf();
     renewSentryClient();
   }
@@ -87,25 +85,35 @@ public class HAClientInvocationHandler implements InvocationHandler {
   }
 
   private void renewSentryClient() throws IOException {
-    while (true) {
-      if (client != null) {
-        client.close();
-      }
-      currentServiceInstance = manager.getServiceInstance();
-      if (currentServiceInstance == null) {
-        throw new IOException("No avaiable node.");
-      }
-      InetSocketAddress serverAddress =
-          ServiceManager.convertServiceInstance(currentServiceInstance);
-      conf.set(ServiceConstants.ClientConfig.SERVER_RPC_ADDRESS, serverAddress.getHostName());
-      conf.setInt(ServiceConstants.ClientConfig.SERVER_RPC_PORT, serverAddress.getPort());
-      try {
-        client = new SentryPolicyServiceClientDefaultImpl(conf);
-        break;
-      } catch (IOException e) {
-        manager.reportError(currentServiceInstance);
-        LOGGER.info("Transport exception while opening transport:", e, e.getMessage());
+    try {
+      manager = new ServiceManager(new HAContext(conf));
+    } catch (Exception e1) {
+      throw new IOException("Failed to extract Sentry node info from zookeeper", e1);
+    }
+
+    try {
+      while (true) {
+        if (client != null) {
+          client.close();
+        }
+        currentServiceInstance = manager.getServiceInstance();
+        if (currentServiceInstance == null) {
+          throw new IOException("No avaiable node.");
+        }
+        InetSocketAddress serverAddress =
+            ServiceManager.convertServiceInstance(currentServiceInstance);
+        conf.set(ServiceConstants.ClientConfig.SERVER_RPC_ADDRESS, serverAddress.getHostName());
+        conf.setInt(ServiceConstants.ClientConfig.SERVER_RPC_PORT, serverAddress.getPort());
+        try {
+          client = new SentryPolicyServiceClientDefaultImpl(conf);
+          break;
+        } catch (IOException e) {
+          manager.reportError(currentServiceInstance);
+          LOGGER.info("Transport exception while opening transport:", e, e.getMessage());
+        }
       }
+    } finally {
+      manager.close();
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/SentryService.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/SentryService.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/SentryService.java
index 02788aa..0b4b39a 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/SentryService.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/SentryService.java
@@ -85,7 +85,7 @@ public class SentryService implements Callable {
   private final String[] principalParts;
   private final String keytab;
   private final ExecutorService serviceExecutor;
-  private Future future;
+  private Future serviceStatus;
   private TServer thriftServer;
   private Status status;
   private int webServerPort;
@@ -239,6 +239,7 @@ public class SentryService implements Callable {
   private void stopSentryWebServer() throws Exception{
     if( sentryWebServer != null) {
       sentryWebServer.stop();
+      sentryWebServer = null;
     }
   }
 
@@ -257,7 +258,7 @@ public class SentryService implements Callable {
     }
     LOGGER.info("Attempting to start...");
     status = Status.STARTED;
-    future = serviceExecutor.submit(this);
+    serviceStatus = serviceExecutor.submit(this);
   }
 
   public synchronized void stop() throws Exception{
@@ -295,6 +296,16 @@ public class SentryService implements Callable {
     LOGGER.info("Stopped...");
   }
 
+  // wait for the service thread to finish execution
+  public synchronized void waitForShutDown() {
+    LOGGER.info("Waiting on future.get()");
+    try {
+      serviceStatus.get();
+    } catch (Exception e) {
+      LOGGER.debug("Error during the shutdown", e);
+    }
+  }
+
   private MultiException addMultiException(MultiException exception, Exception e) {
     if(exception == null){
       exception = new MultiException();
@@ -355,9 +366,9 @@ public class SentryService implements Callable {
       File configFile = null;
       if (configFileName == null || commandLine.hasOption("h") || commandLine.hasOption("help")) {
         // print usage
-          HelpFormatter formatter = new HelpFormatter();
-          formatter.printHelp("sentry --command service", options);
-          System.exit(-1);
+        HelpFormatter formatter = new HelpFormatter();
+        formatter.printHelp("sentry --command service", options);
+        System.exit(-1);
       } else if(!((configFile = new File(configFileName)).isFile() && configFile.canRead())) {
         throw new IllegalArgumentException("Cannot read configuration file " + configFile);
       }
@@ -378,8 +389,7 @@ public class SentryService implements Callable {
 
       // Let's wait on the service to stop
       try {
-        LOGGER.info("Waiting on future.get()");
-        server.future.get();
+        server.waitForShutDown();
       } finally {
         server.serviceExecutor.shutdown();
       }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java
----------------------------------------------------------------------
diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java
index ddc5930..77e1f60 100644
--- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java
+++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/ServiceConstants.java
@@ -121,7 +121,7 @@ public class ServiceConstants {
     public static final String SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS = SENTRY_HA_ZK_PROPERTY_PREFIX + "session.sleep.between.retries.ms";
     public static final int SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS_DEFAULT = 100;
     public static final String SENTRY_HA_ZOOKEEPER_NAMESPACE = SENTRY_HA_ZK_PROPERTY_PREFIX + "namespace";
-    public static final String SENTRY_HA_ZOOKEEPER_NAMESPACE_DEFAULT = "/sentry";
+    public static final String SENTRY_HA_ZOOKEEPER_NAMESPACE_DEFAULT = "sentry";
 
     public static final ImmutableMap<String, String> SENTRY_STORE_DEFAULTS =
         ImmutableMap.<String, String>builder()

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/ha/TestHaEnd2End.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/ha/TestHaEnd2End.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/ha/TestHaEnd2End.java
new file mode 100644
index 0000000..78894d1
--- /dev/null
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/ha/TestHaEnd2End.java
@@ -0,0 +1,171 @@
+/**
+ * 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.tests.e2e.ha;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import org.apache.sentry.provider.db.SentryAccessDeniedException;
+import org.apache.sentry.provider.file.PolicyFile;
+import org.apache.sentry.tests.e2e.hive.AbstractTestWithStaticConfiguration;
+import org.apache.sentry.tests.e2e.hive.StaticUserGroup;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.io.Resources;
+
+/**
+ * End2End tests with Sentry service HA enabled.
+ */
+public class TestHaEnd2End extends AbstractTestWithStaticConfiguration {
+
+  private final String SINGLE_TYPE_DATA_FILE_NAME = "kv1.dat";
+  private File dataFile;
+  private PolicyFile policyFile;
+
+  @BeforeClass
+  public static void setupTestStaticConfiguration() throws Exception {
+    useSentryService = true;
+    enableSentryHA = true;
+    AbstractTestWithStaticConfiguration.setupTestStaticConfiguration();
+  }
+
+  @Override
+  @Before
+  public void setup() throws Exception {
+    super.setupAdmin();
+    super.setup();
+    dataFile = new File(dataDir, SINGLE_TYPE_DATA_FILE_NAME);
+    FileOutputStream to = new FileOutputStream(dataFile);
+    Resources.copy(Resources.getResource(SINGLE_TYPE_DATA_FILE_NAME), to);
+    to.close();
+    policyFile = PolicyFile.setAdminOnServer1(ADMINGROUP);
+  }
+
+  /**
+   * Basic test with two Sentry service running.
+   * @throws Exception
+   */
+  @Test
+  public void testBasic() throws Exception {
+    Connection connection = context.createConnection(ADMIN1);
+    Statement statement = context.createStatement(connection);
+    statement.execute("CREATE TABLE t1 (c1 string)");
+    statement.execute("CREATE ROLE user_role");
+    statement.execute("GRANT SELECT ON TABLE t1 TO ROLE user_role");
+    statement.execute("GRANT ROLE user_role TO GROUP " + USERGROUP1);
+    statement.close();
+    connection.close();
+    connection = context.createConnection(USER1_1);
+    statement = context.createStatement(connection);
+    context.assertSentryException(statement, "CREATE ROLE r2",
+        SentryAccessDeniedException.class.getSimpleName());
+    // test default of ALL
+    statement.execute("SELECT * FROM t1");
+    // test a specific role
+    statement.execute("SET ROLE user_role");
+    statement.execute("SELECT * FROM t1");
+
+    // test ALL
+    statement.execute("SET ROLE ALL");
+    statement.execute("SELECT * FROM t1");
+    statement.close();
+    connection.close();
+
+    // cleanup
+    connection = context.createConnection(ADMIN1);
+    statement = context.createStatement(connection);
+    statement.execute("DROP ROLE user_role");
+    statement.close();
+    connection.close();
+  }
+
+  /**
+   * Test service failover. Run Sentry operations with shutting down one or more
+   * of the services.
+   * @throws Exception
+   */
+  @Test
+  public void testFailover() throws Exception {
+    String roleName1 = "test_role_1";
+    String roleName2 = "test_role_2";
+    String roleName3 = "test_role_3";
+
+    dropDb(ADMIN1, DB1);
+    createDb(ADMIN1, DB1);
+    createTable(ADMIN1, DB1, dataFile, TBL1);
+
+    Connection adminCon = context.createConnection(ADMIN1);
+    Statement adminStmt = context.createStatement(adminCon);
+    // access the new databases
+    adminStmt.execute("use " + DB1);
+
+    // stop server1 and verify sentry continues to work
+    getSentrySrv().stop(0);
+    adminStmt.execute("CREATE ROLE " + roleName1);
+    verifyRoleExists(adminStmt, roleName1);
+
+    // restart server1 and stop server2
+    getSentrySrv().start(0);
+    getSentrySrv().stop(1);
+    adminStmt.execute("CREATE ROLE " + roleName2);
+    verifyRoleExists(adminStmt, roleName2);
+
+    // stop both servers and verify it fails
+    getSentrySrv().stop(0);
+    getSentrySrv().stop(1);
+    context.assertAuthzException(adminStmt, "CREATE ROLE " + roleName3);
+
+    getSentrySrv().start(0);
+    getSentrySrv().start(1);
+    adminStmt.execute("CREATE ROLE " + roleName3);
+    verifyRoleExists(adminStmt, roleName3);
+
+    // cleanup
+
+    dropDb(ADMIN1, DB1);
+    adminStmt.execute("DROP ROLE " + roleName1);
+    adminStmt.execute("DROP ROLE " + roleName2);
+    adminStmt.execute("DROP ROLE " + roleName3);
+    adminStmt.close();
+    adminCon.close();
+
+  }
+
+  private void verifyRoleExists(Statement statement, String roleName)
+      throws Exception {
+    ResultSet resultSet = null;
+    try {
+      resultSet = statement.executeQuery("SHOW ROLES ");
+      while (resultSet.next()) {
+        if (roleName.equalsIgnoreCase(resultSet.getString(1))) {
+          return;
+        }
+      }
+      throw new Exception("Role " + roleName + " does not exist");
+    } finally {
+      if (resultSet != null) {
+        resultSet.close();
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/hive/AbstractTestWithStaticConfiguration.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/hive/AbstractTestWithStaticConfiguration.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/hive/AbstractTestWithStaticConfiguration.java
index c68873e..3a8a6ef 100644
--- a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/hive/AbstractTestWithStaticConfiguration.java
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/hive/AbstractTestWithStaticConfiguration.java
@@ -49,15 +49,16 @@ import org.apache.sentry.policy.db.DBModelAuthorizables;
 import org.apache.sentry.provider.db.SimpleDBProviderBackend;
 import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient;
 import org.apache.sentry.provider.file.PolicyFile;
-import org.apache.sentry.service.thrift.SentryService;
 import org.apache.sentry.service.thrift.SentryServiceClientFactory;
-import org.apache.sentry.service.thrift.SentryServiceFactory;
 import org.apache.sentry.service.thrift.ServiceConstants.ClientConfig;
 import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
 import org.apache.sentry.tests.e2e.hive.fs.DFS;
 import org.apache.sentry.tests.e2e.hive.fs.DFSFactory;
 import org.apache.sentry.tests.e2e.hive.hiveserver.HiveServer;
 import org.apache.sentry.tests.e2e.hive.hiveserver.HiveServerFactory;
+import org.apache.sentry.tests.e2e.minisentry.SentrySrvFactory;
+import org.apache.sentry.tests.e2e.minisentry.SentrySrvFactory.SentrySrvType;
+import org.apache.sentry.tests.e2e.minisentry.SentrySrv;
 import org.apache.tools.ant.util.StringUtils;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -108,6 +109,7 @@ public abstract class AbstractTestWithStaticConfiguration {
   protected static final String SERVER_HOST = "localhost";
   private static final String EXTERNAL_SENTRY_SERVICE = "sentry.e2etest.external.sentry";
   protected static final String EXTERNAL_HIVE_LIB = "sentry.e2etest.hive.lib";
+  private static final String ENABLE_SENTRY_HA = "sentry.e2etest.enable.service.ha";
 
   protected static boolean policyOnHdfs = false;
   protected static boolean useSentryService = false;
@@ -127,8 +129,9 @@ public abstract class AbstractTestWithStaticConfiguration {
   protected static HiveServerFactory.HiveServer2Type hiveServer2Type;
   protected static DFS dfs;
   protected static Map<String, String> properties;
-  protected static SentryService sentryServer;
+  protected static SentrySrv sentryServer;
   protected static Configuration sentryConf;
+  protected static boolean enableSentryHA = false;
   protected static Context context;
   protected final String semanticException = "SemanticException No valid privileges";
 
@@ -209,7 +212,7 @@ public abstract class AbstractTestWithStaticConfiguration {
     PolicyFile policyFile = PolicyFile.setAdminOnServer1(ADMIN1)
         .setUserGroupMapping(StaticUserGroup.getStaticMapping());
     policyFile.write(policyFileLocation);
-    
+
     String policyURI;
     if (policyOnHdfs) {
       String dfsUri = FileSystem.getDefaultUri(fileSystem.getConf()).toString();
@@ -220,8 +223,11 @@ public abstract class AbstractTestWithStaticConfiguration {
     } else {
       policyURI = policyFileLocation.getPath();
     }
-    
+
     boolean startSentry = new Boolean(System.getProperty(EXTERNAL_SENTRY_SERVICE, "false"));
+    if ("true".equalsIgnoreCase(System.getProperty(ENABLE_SENTRY_HA, "false"))) {
+      enableSentryHA = true;
+    }
     if (useSentryService && (!startSentry)) {
       setupSentryService();
     }
@@ -240,8 +246,8 @@ public abstract class AbstractTestWithStaticConfiguration {
   }
 
   public static HiveServer create(Map<String, String> properties,
-                                  File baseDir, File confDir, File logDir, String policyFile,
-                                  FileSystem fileSystem) throws Exception {
+      File baseDir, File confDir, File logDir, String policyFile,
+      FileSystem fileSystem) throws Exception {
     String type = properties.get(HiveServerFactory.HIVESERVER2_TYPE);
     if(type == null) {
       type = System.getProperty(HiveServerFactory.HIVESERVER2_TYPE);
@@ -291,7 +297,7 @@ public abstract class AbstractTestWithStaticConfiguration {
         .entrySet()) {
       for (String roleNames : groupEntry.getValue()) {
         for (String roleName : roleNames.split(",")) {
-            statement.execute("GRANT ROLE " + roleName + " TO GROUP " + groupEntry.getKey());
+          statement.execute("GRANT ROLE " + roleName + " TO GROUP " + groupEntry.getKey());
         }
       }
     }
@@ -359,7 +365,7 @@ public abstract class AbstractTestWithStaticConfiguration {
     properties.put(ConfVars.HIVE_AUTHORIZATION_TASK_FACTORY.varname,
         SentryHiveAuthorizationTaskFactoryImpl.class.getName());
     properties
-        .put(ConfVars.HIVE_SERVER2_THRIFT_MIN_WORKER_THREADS.varname, "2");
+    .put(ConfVars.HIVE_SERVER2_THRIFT_MIN_WORKER_THREADS.varname, "2");
     properties.put(ServerConfig.SECURITY_MODE, ServerConfig.SECURITY_MODE_NONE);
     properties.put(ServerConfig.ADMIN_GROUPS, ADMINGROUP);
     properties.put(ServerConfig.RPC_ADDRESS, SERVER_HOST);
@@ -368,22 +374,30 @@ public abstract class AbstractTestWithStaticConfiguration {
 
     properties.put(ServerConfig.SENTRY_STORE_JDBC_URL,
         "jdbc:derby:;databaseName=" + baseDir.getPath()
-            + "/sentrystore_db;create=true");
+        + "/sentrystore_db;create=true");
     properties.put(ServerConfig.SENTRY_STORE_GROUP_MAPPING, ServerConfig.SENTRY_STORE_LOCAL_GROUP_MAPPING);
     properties.put(ServerConfig.SENTRY_STORE_GROUP_MAPPING_RESOURCE, policyFileLocation.getPath());
     properties.put(ServerConfig.RPC_MIN_THREADS, "3");
     for (Map.Entry<String, String> entry : properties.entrySet()) {
       sentryConf.set(entry.getKey(), entry.getValue());
     }
-    sentryServer = new SentryServiceFactory().create(sentryConf);
-    properties.put(ClientConfig.SERVER_RPC_ADDRESS, sentryServer.getAddress()
+    sentryServer = SentrySrvFactory.create(
+        SentrySrvType.INTERNAL_SERVER, sentryConf, enableSentryHA ? 2 : 1);
+    properties.put(ClientConfig.SERVER_RPC_ADDRESS, sentryServer.get(0)
+        .getAddress()
         .getHostName());
-    sentryConf.set(ClientConfig.SERVER_RPC_ADDRESS, sentryServer.getAddress()
+    sentryConf.set(ClientConfig.SERVER_RPC_ADDRESS, sentryServer.get(0)
+        .getAddress()
         .getHostName());
     properties.put(ClientConfig.SERVER_RPC_PORT,
-        String.valueOf(sentryServer.getAddress().getPort()));
+        String.valueOf(sentryServer.get(0).getAddress().getPort()));
     sentryConf.set(ClientConfig.SERVER_RPC_PORT,
-        String.valueOf(sentryServer.getAddress().getPort()));
+        String.valueOf(sentryServer.get(0).getAddress().getPort()));
+    if (enableSentryHA) {
+      properties.put(ClientConfig.SERVER_HA_ENABLED, "true");
+      properties.put(ClientConfig.SENTRY_HA_ZOOKEEPER_QUORUM,
+          sentryServer.getZKQuorum());
+    }
     startSentryService();
     if (setMetastoreListener) {
       properties.put(HiveConf.ConfVars.METASTORE_EVENT_LISTENERS.varname,
@@ -393,21 +407,14 @@ public abstract class AbstractTestWithStaticConfiguration {
   }
 
   private static void startSentryService() throws Exception {
-    sentryServer.start();
-    final long start = System.currentTimeMillis();
-    while (!sentryServer.isRunning()) {
-      Thread.sleep(1000);
-      if (System.currentTimeMillis() - start > 60000L) {
-        throw new TimeoutException("Server did not start after 60 seconds");
-      }
-    }
+    sentryServer.startAll();
   }
 
   public static SentryPolicyServiceClient getSentryClient() throws Exception {
     if (sentryServer == null) {
       throw new IllegalAccessException("Sentry service not initialized");
     }
-    return SentryServiceClientFactory.create(sentryServer.getConf());
+    return SentryServiceClientFactory.create(sentryServer.get(0).getConf());
   }
 
   @Before
@@ -475,8 +482,7 @@ public abstract class AbstractTestWithStaticConfiguration {
     }
 
     if (sentryServer != null) {
-      sentryServer.stop();
-      sentryServer = null;
+      sentryServer.close();
       sentryServer = null;
     }
 
@@ -497,4 +503,8 @@ public abstract class AbstractTestWithStaticConfiguration {
       context.close();
     }
   }
+
+  public static SentrySrv getSentrySrv() {
+    return sentryServer;
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/InternalSentrySrv.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/InternalSentrySrv.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/InternalSentrySrv.java
new file mode 100644
index 0000000..68bc9ee
--- /dev/null
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/InternalSentrySrv.java
@@ -0,0 +1,164 @@
+/**
+ * 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.tests.e2e.minisentry;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.curator.test.TestingServer;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.service.thrift.SentryService;
+import org.apache.sentry.service.thrift.SentryServiceFactory;
+import org.apache.sentry.service.thrift.ServiceConstants.ClientConfig;
+import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+public class InternalSentrySrv implements SentrySrv {
+
+  private List<SentryService> sentryServers = Lists.newArrayList();
+  private static TestingServer zkServer; // created only if in case of HA
+  private static final Logger LOGGER = LoggerFactory
+      .getLogger(InternalSentrySrv.class);
+  private boolean isActive = false;
+
+  public InternalSentrySrv(Configuration sentryConf, int numServers)
+      throws Exception {
+    // Enable HA when numServers is more that 1, start Curator TestingServer
+    if (numServers > 1) {
+      zkServer = new TestingServer();
+      zkServer.start();
+      sentryConf.setBoolean(ServerConfig.SENTRY_HA_ENABLED, true);
+      sentryConf.set(ServerConfig.SENTRY_HA_ZOOKEEPER_QUORUM,
+          zkServer.getConnectString());
+    } else if (numServers <= 0) {
+      throw new IllegalArgumentException("Invalid number of Servers: "
+          + numServers + " ,must be > 0");
+    }
+    for (int count = 0; count < numServers; count++) {
+      Configuration servConf = new Configuration(sentryConf);
+      SentryService sentryServer = new SentryServiceFactory().create(servConf);
+      servConf.set(ClientConfig.SERVER_RPC_ADDRESS, sentryServer.getAddress()
+          .getHostName());
+      servConf.setInt(ClientConfig.SERVER_RPC_PORT, sentryServer.getAddress()
+          .getPort());
+      sentryServers.add(sentryServer);
+    }
+    isActive = true;
+  }
+
+  @Override
+  public void startAll() throws Exception {
+    if (!isActive) {
+      throw new IllegalStateException("SentrySrv is no longer active");
+    }
+    for (int sentryServerNum = 0; sentryServerNum < sentryServers.size(); sentryServerNum++) {
+      start(sentryServerNum);
+    }
+  }
+
+  @Override
+  public void start(int serverNum) throws Exception {
+    if (!isActive) {
+      throw new IllegalStateException("SentrySrv is no longer active");
+    }
+    SentryService sentryServer = sentryServers.get(serverNum);
+    sentryServer.start();
+
+    // wait for startup
+    final long start = System.currentTimeMillis();
+    while (!sentryServer.isRunning()) {
+      Thread.sleep(1000);
+      if (System.currentTimeMillis() - start > 60000L) {
+        throw new TimeoutException("Server did not start after 60 seconds");
+      }
+    }
+  }
+
+  @Override
+  public void stopAll() throws Exception {
+    boolean cleanStop = true;
+    if (!isActive) {
+      throw new IllegalStateException("SentrySrv is no longer active");
+    }
+    for (int sentryServerNum = 0; sentryServerNum < sentryServers.size(); sentryServerNum++) {
+      try {
+        stop(sentryServerNum);
+      } catch (Exception e) {
+        LOGGER.error("Sentry Server " + sentryServerNum + " failed to stop");
+        cleanStop = false;
+      }
+    }
+    if (!cleanStop) {
+      throw new IllegalStateException(
+          "At least one of the servers failed to stop cleanly");
+    }
+  }
+
+  @Override
+  public void stop(int serverNum) throws Exception {
+    if (!isActive) {
+      throw new IllegalStateException("SentrySrv is no longer active");
+    }
+    SentryService sentryServer = sentryServers.get(serverNum);
+    sentryServer.stop();
+    sentryServer.waitForShutDown();
+  }
+
+  @Override
+  public void close() {
+    for (SentryService sentryServer : sentryServers) {
+      try {
+        sentryServer.stop();
+      } catch (Exception e) {
+        LOGGER.error("Error stoping Sentry service ", e);
+      }
+    }
+    if (zkServer != null) {
+      try {
+        zkServer.stop();
+      } catch (IOException e) {
+        LOGGER.warn("Error stoping ZK service ", e);
+      }
+    }
+    sentryServers.clear();
+    isActive = false;
+  }
+
+  @Override
+  public SentryService get(int serverNum) {
+    return sentryServers.get(serverNum);
+  }
+
+  @Override
+  public String getZKQuorum() throws Exception {
+    if (zkServer == null) {
+      throw new IOException("Sentry HA is not enabled");
+    }
+    return zkServer.getConnectString();
+  }
+
+  @Override
+  public boolean isHaEnabled() {
+    return zkServer != null;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrv.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrv.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrv.java
new file mode 100644
index 0000000..e9ae5fa
--- /dev/null
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrv.java
@@ -0,0 +1,79 @@
+/**
+ * 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.tests.e2e.minisentry;
+
+import org.apache.sentry.service.thrift.SentryService;
+
+public interface SentrySrv {
+
+  /**
+   * Start all the sentry services
+   * @throws Exception
+   */
+  public void startAll() throws Exception;
+
+  /**
+   * Start the given server
+   * @param serverNum
+   *          - Server number (0 to N-1)
+   * @throws Exception
+   */
+  public void start(int serverNum) throws Exception ;
+
+  /**
+   * Stop all the Sentry servers
+   * @throws Exception
+   */
+  public void stopAll() throws Exception;
+
+  /**
+   * Stop the specified Sentry server
+   * @param serverNum
+   *          - Server number (0 to N-1)
+   * @throws Exception
+   */
+  public void stop(int serverNum) throws Exception ;
+
+  /**
+   * Get the underlying Sentry service object
+   * @param serverNum
+   *          - Server number (0 to N-1)
+   * @return
+   */
+  public SentryService get(int serverNum);
+
+  /**
+   * Get the ZK connection string
+   * @return
+   * @throws Exception
+   *           - If HA is not enabled
+   */
+  public String getZKQuorum() throws Exception;
+
+  /**
+   * Stop all the nodes and ZK if started. The SentrySrv can't be reused once
+   * closed.
+   */
+  public void close();
+
+  /**
+   * Check if the sentry server is created with HA enabled.
+   * @return True - HA is enabled False - HA is not enabled
+   */
+  public boolean isHaEnabled();
+}

http://git-wip-us.apache.org/repos/asf/incubator-sentry/blob/36274c25/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrvFactory.java
----------------------------------------------------------------------
diff --git a/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrvFactory.java b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrvFactory.java
new file mode 100644
index 0000000..9381e88
--- /dev/null
+++ b/sentry-tests/sentry-tests-hive/src/test/java/org/apache/sentry/tests/e2e/minisentry/SentrySrvFactory.java
@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sentry.tests.e2e.minisentry;
+
+import java.io.IOException;
+
+import org.apache.hadoop.conf.Configuration;
+
+public class SentrySrvFactory {
+
+  public static enum SentrySrvType {
+    INTERNAL_SERVER, EXTERNAL_SERVER
+  }
+
+  public static SentrySrv create(SentrySrvType srvType,
+      Configuration sentryConf)
+          throws Exception {
+    return create(srvType, sentryConf, 1);
+  }
+
+  public static SentrySrv create(SentrySrvType srvType,
+      Configuration sentryConf,
+      int numServers) throws Exception {
+    if (!srvType.equals(SentrySrvType.INTERNAL_SERVER)) {
+      throw new IOException("Server type " + srvType.name()
+          + " is not supported");
+    }
+    return new InternalSentrySrv(sentryConf, numServers);
+  }
+}