accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From els...@apache.org
Subject [4/4] accumulo git commit: ACCUMULO-3599 Refactor tests to run over MAC using Kerberos
Date Tue, 03 Mar 2015 19:15:30 GMT
ACCUMULO-3599 Refactor tests to run over MAC using Kerberos

ITs should all be passing over a "normal" MAC instance and a MAC
instance which uses the MiniKdc from Hadoop. Refactors a bunch
of user mgmt to provide a non-implementation specific API for
tests to use.


Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo
Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/1c5bef32
Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/1c5bef32
Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/1c5bef32

Branch: refs/heads/master
Commit: 1c5bef324d15ce05e79347ee67d3017325320ea1
Parents: 31ab271
Author: Josh Elser <elserj@apache.org>
Authored: Wed Feb 18 13:07:03 2015 -0500
Committer: Josh Elser <elserj@apache.org>
Committed: Tue Mar 3 11:11:46 2015 -0800

----------------------------------------------------------------------
 .../apache/accumulo/core/cli/ClientOpts.java    |  36 ++-
 .../client/impl/SecurityOperationsImpl.java     |  10 +-
 .../client/security/tokens/KerberosToken.java   |  27 ++
 .../accumulo/cluster/AccumuloCluster.java       |   6 +
 .../apache/accumulo/cluster/ClusterUser.java    | 141 +++++++++
 .../apache/accumulo/cluster/ClusterUsers.java   |  39 +++
 .../standalone/StandaloneAccumuloCluster.java   |  37 ++-
 .../impl/MiniAccumuloClusterImpl.java           |  11 +
 .../server/client/ClientServiceHandler.java     |  14 +-
 .../server/security/SecurityOperation.java      |   2 +
 .../shell/commands/CreateUserCommand.java       |  36 ++-
 .../org/apache/accumulo/test/TestIngest.java    |   5 +-
 .../accumulo/test/TestMultiTableIngest.java     |   2 +-
 .../accumulo/harness/AccumuloClusterIT.java     | 180 ++++++++----
 .../accumulo/harness/MiniClusterHarness.java    |  12 +-
 .../accumulo/harness/SharedMiniClusterIT.java   |  68 ++++-
 .../org/apache/accumulo/harness/TestingKdc.java | 126 +++++---
 .../conf/AccumuloClusterConfiguration.java      |   6 +-
 .../AccumuloClusterPropertyConfiguration.java   |  19 +-
 .../conf/AccumuloMiniClusterConfiguration.java  |  32 ++-
 .../StandaloneAccumuloClusterConfiguration.java | 127 ++++++++-
 .../test/ArbitraryTablePropertiesIT.java        |  56 ++--
 .../accumulo/test/BulkImportVolumeIT.java       |   2 +-
 .../org/apache/accumulo/test/CleanWalIT.java    |   4 +-
 .../accumulo/test/ConditionalWriterIT.java      |  43 ++-
 .../apache/accumulo/test/ImportExportIT.java    |  63 ++--
 .../accumulo/test/InterruptibleScannersIT.java  |   2 +-
 .../accumulo/test/MetaConstraintRetryIT.java    |   4 +-
 .../org/apache/accumulo/test/MetaSplitIT.java   |  40 +--
 .../accumulo/test/MultiTableBatchWriterIT.java  |   2 +-
 .../org/apache/accumulo/test/NamespacesIT.java  |  94 +++---
 .../accumulo/test/NoMutationRecoveryIT.java     |   3 +-
 .../apache/accumulo/test/ScanIteratorIT.java    |  58 ++--
 .../org/apache/accumulo/test/ShellConfigIT.java |  70 ++++-
 .../org/apache/accumulo/test/ShellServerIT.java | 121 ++++++--
 .../apache/accumulo/test/SplitRecoveryIT.java   |   2 +-
 .../apache/accumulo/test/TableOperationsIT.java |   4 +-
 .../accumulo/test/TransportCachingIT.java       |   2 +-
 .../test/functional/AccumuloInputFormatIT.java  |  12 +-
 .../BalanceInPresenceOfOfflineTableIT.java      |  24 +-
 .../test/functional/BigRootTabletIT.java        |   3 +-
 .../test/functional/BinaryStressIT.java         |   2 +-
 .../accumulo/test/functional/BloomFilterIT.java |   3 +-
 .../accumulo/test/functional/BulkFileIT.java    |   2 +-
 .../apache/accumulo/test/functional/BulkIT.java |  62 ++--
 .../functional/BulkSplitOptimizationIT.java     |  21 +-
 .../test/functional/ChaoticBalancerIT.java      |  13 +-
 .../accumulo/test/functional/CleanTmpIT.java    |   4 +-
 .../accumulo/test/functional/CleanUpIT.java     |  39 +--
 .../accumulo/test/functional/CompactionIT.java  |  15 +-
 .../accumulo/test/functional/ConcurrencyIT.java |   6 +-
 .../accumulo/test/functional/CredentialsIT.java |  51 ++--
 .../test/functional/DeleteEverythingIT.java     |   6 +-
 .../accumulo/test/functional/DeleteIT.java      |  51 +++-
 .../test/functional/DeleteRowsSplitIT.java      |  18 +-
 .../test/functional/DynamicThreadPoolsIT.java   |  18 +-
 .../accumulo/test/functional/ExamplesIT.java    | 284 +++++++++++++++----
 .../test/functional/FateStarvationIT.java       |   8 +
 .../test/functional/GarbageCollectorIT.java     |   2 +
 .../test/functional/HalfDeadTServerIT.java      |   1 +
 .../accumulo/test/functional/KerberosIT.java    |  91 +++---
 .../test/functional/KerberosProxyIT.java        |  14 +-
 .../accumulo/test/functional/LargeRowIT.java    |   6 +-
 .../test/functional/MasterAssignmentIT.java     |   2 +-
 .../test/functional/MasterFailoverIT.java       |  19 +-
 .../accumulo/test/functional/MaxOpenIT.java     |  11 +-
 .../accumulo/test/functional/PermissionsIT.java | 240 ++++++++++------
 .../accumulo/test/functional/ReadWriteIT.java   | 104 +++++--
 .../functional/RecoveryWithEmptyRFileIT.java    |   4 +-
 .../accumulo/test/functional/RenameIT.java      |  17 ++
 .../accumulo/test/functional/RestartIT.java     |  92 +++++-
 .../test/functional/RestartStressIT.java        |  41 ++-
 .../accumulo/test/functional/RowDeleteIT.java   |   4 +-
 .../test/functional/ScanSessionTimeOutIT.java   |   6 +-
 .../functional/SimpleBalancerFairnessIT.java    |   1 +
 .../accumulo/test/functional/SplitIT.java       |  48 +++-
 .../accumulo/test/functional/TableIT.java       |  14 +-
 .../accumulo/test/functional/TabletIT.java      |   3 +-
 .../accumulo/test/functional/VisibilityIT.java  |  22 +-
 .../test/functional/WriteAheadLogIT.java        |  17 +-
 .../accumulo/test/functional/WriteLotsIT.java   |  13 +
 .../test/replication/StatusCombinerMacIT.java   |   4 +-
 test/src/test/resources/log4j.properties        |   2 +-
 83 files changed, 2144 insertions(+), 782 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/core/src/main/java/org/apache/accumulo/core/cli/ClientOpts.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/cli/ClientOpts.java b/core/src/main/java/org/apache/accumulo/core/cli/ClientOpts.java
index a7d98b3..b2eb857 100644
--- a/core/src/main/java/org/apache/accumulo/core/cli/ClientOpts.java
+++ b/core/src/main/java/org/apache/accumulo/core/cli/ClientOpts.java
@@ -18,6 +18,8 @@ package org.apache.accumulo.core.cli;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -48,6 +50,7 @@ import org.apache.accumulo.core.zookeeper.ZooUtil;
 import org.apache.commons.configuration.PropertiesConfiguration;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 
@@ -129,6 +132,26 @@ public class ClientOpts extends Help {
           props.put(loginOption.getKey(), loginOption.getValue());
       }
 
+      // If the user isn't currently logged in with Kerberos, they might have provided a keytab file
+      // instead which can be used to log them in. Check that before constructing the KerberosToken
+      // as it will expect the user is already logged in.
+      if (KerberosToken.CLASS_NAME.equals(tokenClassName)) {
+        if (null != keytabPath) {
+          File keytab = new File(keytabPath);
+          if (!keytab.exists() || !keytab.isFile()) {
+            throw new IllegalArgumentException("Keytab isn't a normal file: " + keytabPath);
+          }
+          if (null == principal) {
+            throw new IllegalArgumentException("Principal must be provided if logging in via Keytab");
+          }
+          try {
+            UserGroupInformation.loginUserFromKeytab(principal, keytab.getAbsolutePath());
+          } catch (IOException e) {
+            throw new RuntimeException("Failed to log in with keytab", e);
+          }
+        }
+      }
+
       try {
         AuthenticationToken token = Class.forName(tokenClassName).asSubclass(AuthenticationToken.class).newInstance();
         token.init(props);
@@ -185,6 +208,9 @@ public class ClientOpts extends Help {
   @Parameter(names = "--trace", description = "turn on distributed tracing")
   public boolean trace = false;
 
+  @Parameter(names = "--keytab", description = "Kerberos keytab on the local filesystem")
+  public String keytabPath = null;
+
   public void startTracing(String applicationName) {
     if (trace) {
       Trace.on(applicationName);
@@ -199,7 +225,7 @@ public class ClientOpts extends Help {
    * Automatically update the options to use a KerberosToken when SASL is enabled for RPCs. Don't overwrite the options if the user has provided something
    * specifically.
    */
-  protected void updateKerberosCredentials() {
+  public void updateKerberosCredentials() {
     ClientConfiguration clientConfig;
     try {
       if (clientConfigFile == null)
@@ -209,6 +235,14 @@ public class ClientOpts extends Help {
     } catch (Exception e) {
       throw new IllegalArgumentException(e);
     }
+    updateKerberosCredentials(clientConfig);
+  }
+
+  /**
+   * Automatically update the options to use a KerberosToken when SASL is enabled for RPCs. Don't overwrite the options if the user has provided something
+   * specifically.
+   */
+  public void updateKerberosCredentials(ClientConfiguration clientConfig) {
     final boolean clientConfSaslEnabled = Boolean.parseBoolean(clientConfig.get(ClientProperty.INSTANCE_RPC_SASL_ENABLED));
     if ((saslEnabled || clientConfSaslEnabled) && null == tokenClassName) {
       tokenClassName = KerberosToken.CLASS_NAME;

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/core/src/main/java/org/apache/accumulo/core/client/impl/SecurityOperationsImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/client/impl/SecurityOperationsImpl.java b/core/src/main/java/org/apache/accumulo/core/client/impl/SecurityOperationsImpl.java
index dbaa9d1..bfba270 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/impl/SecurityOperationsImpl.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/impl/SecurityOperationsImpl.java
@@ -105,11 +105,17 @@ public class SecurityOperationsImpl implements SecurityOperations {
   @Override
   public void createLocalUser(final String principal, final PasswordToken password) throws AccumuloException, AccumuloSecurityException {
     checkArgument(principal != null, "principal is null");
-    checkArgument(password != null, "password is null");
+    if (null == context.getSaslParams()) {
+      checkArgument(password != null, "password is null");
+    }
     execute(new ClientExec<ClientService.Client>() {
       @Override
       public void execute(ClientService.Client client) throws Exception {
-        client.createLocalUser(Tracer.traceInfo(), context.rpcCreds(), principal, ByteBuffer.wrap(password.getPassword()));
+        if (null == context.getSaslParams()) {
+          client.createLocalUser(Tracer.traceInfo(), context.rpcCreds(), principal, ByteBuffer.wrap(password.getPassword()));
+        } else {
+          client.createLocalUser(Tracer.traceInfo(), context.rpcCreds(), principal, ByteBuffer.wrap(new byte[0]));
+        }
       }
     });
   }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/core/src/main/java/org/apache/accumulo/core/client/security/tokens/KerberosToken.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/KerberosToken.java b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/KerberosToken.java
index d7d2e15..c122a46 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/KerberosToken.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/KerberosToken.java
@@ -18,6 +18,7 @@ package org.apache.accumulo.core.client.security.tokens;
 
 import java.io.DataInput;
 import java.io.DataOutput;
+import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Set;
@@ -40,6 +41,7 @@ public class KerberosToken implements AuthenticationToken {
   private static final int VERSION = 1;
 
   private String principal;
+  private File keytab;
 
   /**
    * Creates a token using the provided principal and the currently logged-in user via {@link UserGroupInformation}.
@@ -56,6 +58,24 @@ public class KerberosToken implements AuthenticationToken {
   }
 
   /**
+   * Creates a token and logs in via {@link UserGroupInformation} using the provided principal and keytab. A key for the principal must exist in the keytab,
+   * otherwise login will fail.
+   *
+   * @param principal
+   *          The Kerberos principal
+   * @param keytab
+   *          A keytab file
+   */
+  public KerberosToken(String principal, File keytab) throws IOException {
+    Preconditions.checkNotNull(principal, "Principal was null");
+    Preconditions.checkNotNull(keytab, "Keytab was null");
+    Preconditions.checkArgument(keytab.exists() && keytab.isFile(), "Keytab was not a normal file");
+    UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keytab.getAbsolutePath());
+    this.principal = ugi.getUserName();
+    this.keytab = keytab;
+  }
+
+  /**
    * Creates a token using the login user as returned by {@link UserGroupInformation#getCurrentUser()}
    *
    * @throws IOException
@@ -96,6 +116,13 @@ public class KerberosToken implements AuthenticationToken {
     return principal;
   }
 
+  /**
+   * The keytab file used to perform Kerberos login. Optional, may be null.
+   */
+  public File getKeytab() {
+    return keytab;
+  }
+
   @Override
   public void write(DataOutput out) throws IOException {
     out.writeInt(VERSION);

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/minicluster/src/main/java/org/apache/accumulo/cluster/AccumuloCluster.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/cluster/AccumuloCluster.java b/minicluster/src/main/java/org/apache/accumulo/cluster/AccumuloCluster.java
index a3dbb09..767633b 100644
--- a/minicluster/src/main/java/org/apache/accumulo/cluster/AccumuloCluster.java
+++ b/minicluster/src/main/java/org/apache/accumulo/cluster/AccumuloCluster.java
@@ -24,6 +24,7 @@ import org.apache.accumulo.core.client.ClientConfiguration;
 import org.apache.accumulo.core.client.Connector;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
 
 /**
  * Defines a minimum required set of methods to run an Accumulo cluster.
@@ -76,4 +77,9 @@ public interface AccumuloCluster {
    * @return the {@link FileSystem} in use by this cluster
    */
   FileSystem getFileSystem() throws IOException;
+
+  /**
+   * @return A path on {@link FileSystem} this cluster is running on that can be used for temporary files
+   */
+  Path getTemporaryPath();
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUser.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUser.java b/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUser.java
new file mode 100644
index 0000000..8f45d7e
--- /dev/null
+++ b/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUser.java
@@ -0,0 +1,141 @@
+/*
+ * 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.accumulo.cluster;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.KerberosToken;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.hadoop.security.UserGroupInformation;
+
+/**
+ * Simple wrapper around a principal and its credentials: a password or a keytab.
+ */
+public class ClusterUser {
+  private String password;
+  private String principal;
+  private File keytab;
+
+  public ClusterUser(String principal, File keytab) {
+    checkNotNull(principal, "Principal was null");
+    checkNotNull(keytab, "Keytab was null");
+    checkArgument(keytab.exists() && keytab.isFile(), "Keytab should be a file");
+    this.principal = principal;
+    this.keytab = keytab;
+  }
+
+  public ClusterUser(String principal, String password) {
+    checkNotNull(principal, "Principal was null");
+    checkNotNull(password, "Password was null");
+    this.principal = principal;
+    this.password = password;
+  }
+
+  /**
+   * @return the principal
+   */
+  public String getPrincipal() {
+    return principal;
+  }
+
+  /**
+   * @return the keytab, or null if login is password-based
+   */
+  public File getKeytab() {
+    return keytab;
+  }
+
+  /**
+   * @return the password, or null if login is keytab-based
+   */
+  public String getPassword() {
+    return password;
+  }
+
+  /**
+   * Computes the appropriate {@link AuthenticationToken} for the user represented by this object. May not yet be created in Accumulo.
+   *
+   * @return the correct {@link AuthenticationToken} to use with Accumulo for this user
+   * @throws IOException
+   *           if performing necessary login failed
+   */
+  public AuthenticationToken getToken() throws IOException {
+    if (null != password) {
+      return new PasswordToken(password);
+    } else if (null != keytab) {
+      UserGroupInformation.loginUserFromKeytab(principal, keytab.getAbsolutePath());
+      return new KerberosToken(principal, keytab);
+    }
+
+    throw new IllegalStateException("One of password and keytab must be non-null");
+  }
+
+  @Override
+  public String toString() {
+    return "KerberosPrincipal [principal=" + principal + ", keytab=" + keytab + ", password=" + password + "]";
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + principal.hashCode();
+    result = prime * result + (keytab == null ? 0 : keytab.hashCode());
+    result = prime * result + (password == null ? 0 : password.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (obj == null) {
+      return false;
+    }
+
+    if (obj instanceof ClusterUser) {
+      ClusterUser other = (ClusterUser) obj;
+      if (null == keytab) {
+        if (null != other.keytab) {
+          return false;
+        }
+      } else if (!keytab.equals(other.keytab)) {
+        return false;
+      }
+
+      if (null == password) {
+        if (null != other.password) {
+          return false;
+        }
+      } else if (!password.equals(other.password)) {
+        return false;
+      }
+
+      return principal.equals(other.principal);
+    }
+
+    return false;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUsers.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUsers.java b/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUsers.java
new file mode 100644
index 0000000..13fef2d
--- /dev/null
+++ b/minicluster/src/main/java/org/apache/accumulo/cluster/ClusterUsers.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.accumulo.cluster;
+
+import org.apache.accumulo.core.security.SystemPermission;
+
+/**
+ * General interface to get user details for an {@link AccumuloCluster}.
+ */
+public interface ClusterUsers {
+
+  /**
+   * The "root" user. A user which has administrative permissions {@link SystemPermission#SYSTEM} and the like.
+   */
+  ClusterUser getAdminUser();
+
+  /**
+   * Get an unprivileged user. Multiple are created
+   *
+   * @param offset
+   *          the user to return
+   * @return the user denoted by the given offset
+   */
+  ClusterUser getUser(int offset);
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/minicluster/src/main/java/org/apache/accumulo/cluster/standalone/StandaloneAccumuloCluster.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/cluster/standalone/StandaloneAccumuloCluster.java b/minicluster/src/main/java/org/apache/accumulo/cluster/standalone/StandaloneAccumuloCluster.java
index 58536ed..4e0861e 100644
--- a/minicluster/src/main/java/org/apache/accumulo/cluster/standalone/StandaloneAccumuloCluster.java
+++ b/minicluster/src/main/java/org/apache/accumulo/cluster/standalone/StandaloneAccumuloCluster.java
@@ -16,10 +16,14 @@
  */
 package org.apache.accumulo.cluster.standalone;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.List;
 
 import org.apache.accumulo.cluster.AccumuloCluster;
+import org.apache.accumulo.cluster.ClusterUser;
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.ClientConfiguration;
@@ -44,14 +48,20 @@ public class StandaloneAccumuloCluster implements AccumuloCluster {
   private static final Logger log = LoggerFactory.getLogger(StandaloneAccumuloCluster.class);
 
   private Instance instance;
+  private ClientConfiguration clientConf;
   private String accumuloHome, accumuloConfDir, hadoopConfDir;
+  private Path tmp;
+  private List<ClusterUser> users;
 
-  public StandaloneAccumuloCluster(String instanceName, String zookeepers) {
-    this(new ZooKeeperInstance(instanceName, zookeepers));
+  public StandaloneAccumuloCluster(ClientConfiguration clientConf, Path tmp, List<ClusterUser> users) {
+    this(new ZooKeeperInstance(clientConf), clientConf, tmp, users);
   }
 
-  public StandaloneAccumuloCluster(Instance instance) {
+  public StandaloneAccumuloCluster(Instance instance, ClientConfiguration clientConf, Path tmp, List<ClusterUser> users) {
     this.instance = instance;
+    this.clientConf = clientConf;
+    this.tmp = tmp;
+    this.users = users;
   }
 
   public String getAccumuloHome() {
@@ -95,7 +105,7 @@ public class StandaloneAccumuloCluster implements AccumuloCluster {
 
   @Override
   public ClientConfiguration getClientConfig() {
-    return ClientConfiguration.loadDefault().withInstance(getInstanceName()).withZkHosts(getZooKeepers());
+    return clientConf;
   }
 
   @Override
@@ -128,8 +138,7 @@ public class StandaloneAccumuloCluster implements AccumuloCluster {
     }
   }
 
-  @Override
-  public FileSystem getFileSystem() throws IOException {
+  public Configuration getHadoopConfiguration() {
     String confDir = hadoopConfDir;
     if (null == confDir) {
       confDir = System.getenv("HADOOP_CONF_DIR");
@@ -142,6 +151,22 @@ public class StandaloneAccumuloCluster implements AccumuloCluster {
     conf.addResource(new Path(confDir, "core-site.xml"));
     // Need hdfs-site.xml for NN HA
     conf.addResource(new Path(confDir, "hdfs-site.xml"));
+    return conf;
+  }
+
+  @Override
+  public FileSystem getFileSystem() throws IOException {
+    Configuration conf = getHadoopConfiguration();
     return FileSystem.get(conf);
   }
+
+  @Override
+  public Path getTemporaryPath() {
+    return tmp;
+  }
+
+  public ClusterUser getUser(int offset) {
+    checkArgument(offset >= 0 && offset < users.size(), "Invalid offset, should be non-negative and less than " + users.size());
+    return users.get(offset);
+  }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/minicluster/src/main/java/org/apache/accumulo/minicluster/impl/MiniAccumuloClusterImpl.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/minicluster/impl/MiniAccumuloClusterImpl.java b/minicluster/src/main/java/org/apache/accumulo/minicluster/impl/MiniAccumuloClusterImpl.java
index 76f332b..608618c 100644
--- a/minicluster/src/main/java/org/apache/accumulo/minicluster/impl/MiniAccumuloClusterImpl.java
+++ b/minicluster/src/main/java/org/apache/accumulo/minicluster/impl/MiniAccumuloClusterImpl.java
@@ -795,4 +795,15 @@ public class MiniAccumuloClusterImpl implements AccumuloCluster {
   public MiniAccumuloClusterControl getClusterControl() {
     return clusterControl;
   }
+
+  @Override
+  public Path getTemporaryPath() {
+    if (config.useMiniDFS()) {
+      return new Path("/tmp/");
+    } else {
+      File tmp = new File(config.getDir(), "tmp");
+      tmp.mkdirs();
+      return new Path(tmp.toString());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/server/base/src/main/java/org/apache/accumulo/server/client/ClientServiceHandler.java
----------------------------------------------------------------------
diff --git a/server/base/src/main/java/org/apache/accumulo/server/client/ClientServiceHandler.java b/server/base/src/main/java/org/apache/accumulo/server/client/ClientServiceHandler.java
index e81dfbd..830178d 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/client/ClientServiceHandler.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/client/ClientServiceHandler.java
@@ -44,6 +44,8 @@ import org.apache.accumulo.core.client.impl.thrift.TableOperation;
 import org.apache.accumulo.core.client.impl.thrift.TableOperationExceptionType;
 import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException;
 import org.apache.accumulo.core.client.impl.thrift.ThriftTableOperationException;
+import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
 import org.apache.accumulo.core.conf.Property;
@@ -160,7 +162,17 @@ public class ClientServiceHandler implements ClientService.Iface {
 
   @Override
   public void createLocalUser(TInfo tinfo, TCredentials credentials, String principal, ByteBuffer password) throws ThriftSecurityException {
-    PasswordToken token = new PasswordToken(password);
+    AuthenticationToken token;
+    if (null != context.getSaslParams()) {
+      try {
+        token = new KerberosToken();
+      } catch (IOException e) {
+        log.warn("Failed to create KerberosToken");
+        throw new ThriftSecurityException(e.getMessage(), SecurityErrorCode.DEFAULT_SECURITY_ERROR);
+      }
+    } else {
+      token = new PasswordToken(password);
+    }
     Credentials newUser = new Credentials(principal, token);
     security.createUser(credentials, newUser, new Authorizations());
   }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java
----------------------------------------------------------------------
diff --git a/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java b/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java
index 0b0f212..c646bba 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/security/SecurityOperation.java
@@ -219,6 +219,8 @@ public class SecurityOperation {
         if (!authenticator.userExists(toCreds.getPrincipal())) {
           createUser(credentials, toCreds, Authorizations.EMPTY);
         }
+        // Likely that the KerberosAuthenticator will fail as we don't have the credentials for the other user,
+        // we only have our own Kerberos credentials.
       }
 
       return authenticator.authenticateUser(toCreds.getPrincipal(), toCreds.getToken());

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/shell/src/main/java/org/apache/accumulo/shell/commands/CreateUserCommand.java
----------------------------------------------------------------------
diff --git a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateUserCommand.java b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateUserCommand.java
index 161486c..f3d5f77 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateUserCommand.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateUserCommand.java
@@ -22,6 +22,8 @@ import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.TableExistsException;
 import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 import org.apache.accumulo.shell.Shell;
 import org.apache.accumulo.shell.Shell.Command;
@@ -34,21 +36,29 @@ public class CreateUserCommand extends Command {
       AccumuloSecurityException, TableExistsException, IOException {
     final String user = cl.getArgs()[0];
 
-    final String password = shellState.readMaskedLine("Enter new password for '" + user + "': ", '*');
-    if (password == null) {
-      shellState.getReader().println();
-      return 0;
-    } // user canceled
-    String passwordConfirm = shellState.readMaskedLine("Please confirm new password for '" + user + "': ", '*');
-    if (passwordConfirm == null) {
-      shellState.getReader().println();
-      return 0;
-    } // user canceled
+    AuthenticationToken userToken = shellState.getToken();
+    PasswordToken passwordToken;
+    if (userToken instanceof KerberosToken) {
+      passwordToken = new PasswordToken();
+    } else {
+      final String password = shellState.readMaskedLine("Enter new password for '" + user + "': ", '*');
+      if (password == null) {
+        shellState.getReader().println();
+        return 0;
+      } // user canceled
+      String passwordConfirm = shellState.readMaskedLine("Please confirm new password for '" + user + "': ", '*');
+      if (passwordConfirm == null) {
+        shellState.getReader().println();
+        return 0;
+      } // user canceled
 
-    if (!password.equals(passwordConfirm)) {
-      throw new IllegalArgumentException("Passwords do not match");
+      if (!password.equals(passwordConfirm)) {
+        throw new IllegalArgumentException("Passwords do not match");
+      }
+      passwordToken = new PasswordToken(password);
     }
-    shellState.getConnector().securityOperations().createLocalUser(user, new PasswordToken(password));
+
+    shellState.getConnector().securityOperations().createLocalUser(user, passwordToken);
     Shell.log.debug("Created user " + user);
     return 0;
   }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/main/java/org/apache/accumulo/test/TestIngest.java
----------------------------------------------------------------------
diff --git a/test/src/main/java/org/apache/accumulo/test/TestIngest.java b/test/src/main/java/org/apache/accumulo/test/TestIngest.java
index 8c67017..3f3c4cc 100644
--- a/test/src/main/java/org/apache/accumulo/test/TestIngest.java
+++ b/test/src/main/java/org/apache/accumulo/test/TestIngest.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.TreeSet;
 
 import org.apache.accumulo.core.cli.BatchWriterOpts;
+import org.apache.accumulo.core.cli.ClientOnDefaultTable;
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.BatchWriter;
@@ -48,7 +49,6 @@ import org.apache.accumulo.core.security.ColumnVisibility;
 import org.apache.accumulo.core.trace.DistributedTrace;
 import org.apache.accumulo.core.util.CachedConfiguration;
 import org.apache.accumulo.core.util.FastFormat;
-import org.apache.accumulo.server.cli.ClientOnDefaultTable;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.io.Text;
@@ -101,6 +101,9 @@ public class TestIngest {
     @Parameter(names = {"-cv", "--columnVisibility"}, description = "place columns in this column family", converter = VisibilityConverter.class)
     public ColumnVisibility columnVisibility = new ColumnVisibility();
 
+    public Configuration conf = null;
+    public FileSystem fs = null;
+
     public Opts() {
       super("test_ingest");
     }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/main/java/org/apache/accumulo/test/TestMultiTableIngest.java
----------------------------------------------------------------------
diff --git a/test/src/main/java/org/apache/accumulo/test/TestMultiTableIngest.java b/test/src/main/java/org/apache/accumulo/test/TestMultiTableIngest.java
index 9e8c9ad..9eb42ec 100644
--- a/test/src/main/java/org/apache/accumulo/test/TestMultiTableIngest.java
+++ b/test/src/main/java/org/apache/accumulo/test/TestMultiTableIngest.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map.Entry;
 
 import org.apache.accumulo.core.cli.BatchWriterOpts;
+import org.apache.accumulo.core.cli.ClientOpts;
 import org.apache.accumulo.core.cli.ScannerOpts;
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
@@ -34,7 +35,6 @@ import org.apache.accumulo.core.data.Key;
 import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.fate.util.UtilWaitThread;
-import org.apache.accumulo.server.cli.ClientOpts;
 import org.apache.hadoop.io.Text;
 
 import com.beust.jcommander.Parameter;

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/AccumuloClusterIT.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/AccumuloClusterIT.java b/test/src/test/java/org/apache/accumulo/harness/AccumuloClusterIT.java
index c1ad17b..05c17f1 100644
--- a/test/src/test/java/org/apache/accumulo/harness/AccumuloClusterIT.java
+++ b/test/src/test/java/org/apache/accumulo/harness/AccumuloClusterIT.java
@@ -16,20 +16,31 @@
  */
 package org.apache.accumulo.harness;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.fail;
 
-import java.io.File;
 import java.io.IOException;
 
 import org.apache.accumulo.cluster.AccumuloCluster;
 import org.apache.accumulo.cluster.ClusterControl;
+import org.apache.accumulo.cluster.ClusterUser;
+import org.apache.accumulo.cluster.ClusterUsers;
 import org.apache.accumulo.cluster.standalone.StandaloneAccumuloCluster;
+import org.apache.accumulo.core.client.ClientConfiguration;
+import org.apache.accumulo.core.client.ClientConfiguration.ClientProperty;
 import org.apache.accumulo.core.client.Connector;
 import org.apache.accumulo.core.client.admin.SecurityOperations;
 import org.apache.accumulo.core.client.admin.TableOperations;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.KerberosToken;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.security.TablePermission;
+import org.apache.accumulo.harness.conf.AccumuloClusterConfiguration;
 import org.apache.accumulo.harness.conf.AccumuloClusterPropertyConfiguration;
+import org.apache.accumulo.harness.conf.AccumuloMiniClusterConfiguration;
 import org.apache.accumulo.harness.conf.StandaloneAccumuloClusterConfiguration;
+import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
 import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
@@ -48,7 +59,7 @@ import com.google.common.base.Preconditions;
 /**
  * General Integration-Test base class that provides access to an Accumulo instance for testing. This instance could be MAC or a standalone instance.
  */
-public abstract class AccumuloClusterIT extends AccumuloIT implements MiniClusterConfigurationCallback {
+public abstract class AccumuloClusterIT extends AccumuloIT implements MiniClusterConfigurationCallback, ClusterUsers {
   private static final Logger log = LoggerFactory.getLogger(AccumuloClusterIT.class);
   private static final String TRUE = Boolean.toString(true);
 
@@ -75,6 +86,7 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
     if (ClusterType.MINI == type && TRUE.equals(System.getProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION))) {
       krb = new TestingKdc();
       krb.start();
+      log.info("MiniKdc started");
     }
 
     initialized = true;
@@ -88,43 +100,10 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
   }
 
   /**
-   * {@link TestingKdc#getAccumuloKeytab()}
+   * The {@link TestingKdc} used for this {@link AccumuloCluster}. Might be null.
    */
-  public static File getAccumuloKeytab() {
-    if (null == krb) {
-      throw new RuntimeException("KDC not enabled");
-    }
-    return krb.getAccumuloKeytab();
-  }
-
-  /**
-   * {@link TestingKdc#getAccumuloPrincipal()}
-   */
-  public static String getAccumuloPrincipal() {
-    if (null == krb) {
-      throw new RuntimeException("KDC not enabled");
-    }
-    return krb.getAccumuloPrincipal();
-  }
-
-  /**
-   * {@link TestingKdc#getClientKeytab()}
-   */
-  public static File getClientKeytab() {
-    if (null == krb) {
-      throw new RuntimeException("KDC not enabled");
-    }
-    return krb.getClientKeytab();
-  }
-
-  /**
-   * {@link TestingKdc#getClientPrincipal()}
-   */
-  public static String getClientPrincipal() {
-    if (null == krb) {
-      throw new RuntimeException("KDC not enabled");
-    }
-    return krb.getClientPrincipal();
+  public static TestingKdc getKdc() {
+    return krb;
   }
 
   @Before
@@ -136,15 +115,34 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
       case MINI:
         MiniClusterHarness miniClusterHarness = new MiniClusterHarness();
         // Intrinsically performs the callback to let tests alter MiniAccumuloConfig and core-site.xml
-        cluster = miniClusterHarness.create(this, getToken(), krb);
+        MiniAccumuloClusterImpl impl = miniClusterHarness.create(this, getAdminToken(), krb);
+        cluster = impl;
+        // MAC makes a ClientConf for us, just set it
+        ((AccumuloMiniClusterConfiguration) clusterConf).setClientConf(impl.getClientConfig());
+        // Login as the "root" user
+        if (null != krb) {
+          ClusterUser rootUser = krb.getRootUser();
+          // Log in the 'client' user
+          UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
+        }
         break;
       case STANDALONE:
         StandaloneAccumuloClusterConfiguration conf = (StandaloneAccumuloClusterConfiguration) clusterConf;
-        StandaloneAccumuloCluster standaloneCluster = new StandaloneAccumuloCluster(conf.getInstance());
+        ClientConfiguration clientConf = conf.getClientConf();
+        StandaloneAccumuloCluster standaloneCluster = new StandaloneAccumuloCluster(conf.getInstance(), clientConf, conf.getTmpDirectory(), conf.getUsers());
         // If these are provided in the configuration, pass them into the cluster
         standaloneCluster.setAccumuloHome(conf.getAccumuloHome());
         standaloneCluster.setAccumuloConfDir(conf.getAccumuloConfDir());
         standaloneCluster.setHadoopConfDir(conf.getHadoopConfDir());
+
+        // For SASL, we need to get the Hadoop configuration files as well otherwise UGI will log in as SIMPLE instead of KERBEROS
+        Configuration hadoopConfiguration = standaloneCluster.getHadoopConfiguration();
+        if (clientConf.getBoolean(ClientProperty.INSTANCE_RPC_SASL_ENABLED.getKey(), false)) {
+          UserGroupInformation.setConfiguration(hadoopConfiguration);
+          // Login as the admin user to start the tests
+          UserGroupInformation.loginUserFromKeytab(conf.getAdminPrincipal(), conf.getAdminKeytab().getAbsolutePath());
+        }
+
         // Set the implementation
         cluster = standaloneCluster;
         break;
@@ -154,16 +152,42 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
 
     if (type.isDynamic()) {
       cluster.start();
-      if (null != krb) {
-        // Log in the 'client' user
-        UserGroupInformation.loginUserFromKeytab(getClientPrincipal(), getClientKeytab().getAbsolutePath());
-      }
     } else {
       log.info("Removing tables which appear to be from a previous test run");
       cleanupTables();
       log.info("Removing users which appear to be from a previous test run");
       cleanupUsers();
     }
+
+    switch (type) {
+      case MINI:
+        if (null != krb) {
+          final String traceTable = Property.TRACE_TABLE.getDefaultValue();
+          final ClusterUser systemUser = krb.getAccumuloServerUser(), rootUser = krb.getRootUser();
+
+          // Login as the trace user
+          UserGroupInformation.loginUserFromKeytab(systemUser.getPrincipal(), systemUser.getKeytab().getAbsolutePath());
+
+          // Open a connector as the system user (ensures the user will exist for us to assign permissions to)
+          Connector conn = cluster.getConnector(systemUser.getPrincipal(), new KerberosToken(systemUser.getPrincipal(), systemUser.getKeytab()));
+
+          // Then, log back in as the "root" user and do the grant
+          UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
+          conn = getConnector();
+
+          // Create the trace table
+          conn.tableOperations().create(traceTable);
+
+          // Trace user (which is the same kerberos principal as the system user, but using a normal KerberosToken) needs
+          // to have the ability to read, write and alter the trace table
+          conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.READ);
+          conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.WRITE);
+          conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.ALTER_TABLE);
+        }
+        break;
+      default:
+        // do nothing
+    }
   }
 
   public void cleanupTables() throws Exception {
@@ -217,14 +241,50 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
     return type;
   }
 
-  public static String getPrincipal() {
+  public static String getAdminPrincipal() {
     Preconditions.checkState(initialized);
-    return clusterConf.getPrincipal();
+    return clusterConf.getAdminPrincipal();
   }
 
-  public static AuthenticationToken getToken() {
+  public static AuthenticationToken getAdminToken() {
     Preconditions.checkState(initialized);
-    return clusterConf.getToken();
+    return clusterConf.getAdminToken();
+  }
+
+  @Override
+  public ClusterUser getAdminUser() {
+    switch (type) {
+      case MINI:
+        if (null == krb) {
+          PasswordToken passwordToken = (PasswordToken) getAdminToken();
+          return new ClusterUser(getAdminPrincipal(), new String(passwordToken.getPassword(), UTF_8));
+        }
+        return krb.getRootUser();
+      case STANDALONE:
+        return new ClusterUser(getAdminPrincipal(), ((StandaloneAccumuloClusterConfiguration) clusterConf).getAdminKeytab());
+      default:
+        throw new RuntimeException("Unknown cluster type");
+    }
+  }
+
+  @Override
+  public ClusterUser getUser(int offset) {
+    switch (type) {
+      case MINI:
+        if (null != krb) {
+          // Defer to the TestingKdc when kerberos is on so we can get the keytab instead of a password
+          return krb.getClientPrincipal(offset);
+        } else {
+          // Come up with a mostly unique name
+          String principal = getClass().getSimpleName() + "_" + testName.getMethodName() + "_" + offset;
+          // Username and password are the same
+          return new ClusterUser(principal, principal);
+        }
+      case STANDALONE:
+        return ((StandaloneAccumuloCluster) cluster).getUser(offset);
+      default:
+        throw new RuntimeException("Unknown cluster type");
+    }
   }
 
   public static FileSystem getFileSystem() throws IOException {
@@ -232,9 +292,17 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
     return cluster.getFileSystem();
   }
 
+  public static AccumuloClusterConfiguration getClusterConfiguration() {
+    Preconditions.checkState(initialized);
+    return clusterConf;
+  }
+
   public Connector getConnector() {
     try {
-      return cluster.getConnector(getPrincipal(), getToken());
+      String princ = getAdminPrincipal();
+      AuthenticationToken token = getAdminToken();
+      log.debug("Creating connector as {} with {}", princ, token);
+      return cluster.getConnector(princ, token);
     } catch (Exception e) {
       log.error("Could not connect to Accumulo", e);
       fail("Could not connect to Accumulo");
@@ -262,17 +330,7 @@ public abstract class AccumuloClusterIT extends AccumuloIT implements MiniCluste
    *
    * @return A directory which can be expected to exist on the Cluster's FileSystem
    */
-  public String getUsableDir() throws IllegalArgumentException, IOException {
-    if (ClusterType.MINI == getClusterType()) {
-      File f = new File(System.getProperty("user.dir"), "target");
-      f.mkdirs();
-      return f.getAbsolutePath();
-    } else if (ClusterType.STANDALONE == getClusterType()) {
-      String path = "/tmp";
-      cluster.getFileSystem().mkdirs(new Path(path));
-      return path;
-    }
-
-    throw new IllegalArgumentException("Cannot determine a usable directory for cluster: " + getClusterType());
+  public Path getUsableDir() throws IllegalArgumentException, IOException {
+    return cluster.getTemporaryPath();
   }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/MiniClusterHarness.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/MiniClusterHarness.java b/test/src/test/java/org/apache/accumulo/harness/MiniClusterHarness.java
index 6245f96..7312486 100644
--- a/test/src/test/java/org/apache/accumulo/harness/MiniClusterHarness.java
+++ b/test/src/test/java/org/apache/accumulo/harness/MiniClusterHarness.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.accumulo.cluster.ClusterUser;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
@@ -55,7 +56,7 @@ public class MiniClusterHarness {
       USE_CRED_PROVIDER_FOR_IT_OPTION = "org.apache.accumulo.test.functional.useCredProviderForIT",
       USE_KERBEROS_FOR_IT_OPTION = "org.apache.accumulo.test.functional.useKrbForIT", TRUE = Boolean.toString(true);
 
-  // TODO These are defined in MiniKdc >= 2.6.0
+  // TODO These are defined in MiniKdc >= 2.6.0. Can be removed when minimum Hadoop dependency is increased to that.
   public static final String JAVA_SECURITY_KRB5_CONF = "java.security.krb5.conf", SUN_SECURITY_KRB5_DEBUG = "sun.security.krb5.debug";
 
   /**
@@ -214,13 +215,14 @@ public class MiniClusterHarness {
 
     // Turn on SASL and set the keytab/principal information
     cfg.setProperty(Property.INSTANCE_RPC_SASL_ENABLED, "true");
-    cfg.setProperty(Property.GENERAL_KERBEROS_KEYTAB, kdc.getAccumuloKeytab().getAbsolutePath());
-    cfg.setProperty(Property.GENERAL_KERBEROS_PRINCIPAL, kdc.getAccumuloPrincipal());
+    ClusterUser serverUser = kdc.getAccumuloServerUser();
+    cfg.setProperty(Property.GENERAL_KERBEROS_KEYTAB, serverUser.getKeytab().getAbsolutePath());
+    cfg.setProperty(Property.GENERAL_KERBEROS_PRINCIPAL, serverUser.getPrincipal());
     cfg.setProperty(Property.INSTANCE_SECURITY_AUTHENTICATOR, KerberosAuthenticator.class.getName());
     cfg.setProperty(Property.INSTANCE_SECURITY_AUTHORIZOR, KerberosAuthorizor.class.getName());
     cfg.setProperty(Property.INSTANCE_SECURITY_PERMISSION_HANDLER, KerberosPermissionHandler.class.getName());
     // Piggy-back on the "system user" credential, but use it as a normal KerberosToken, not the SystemToken.
-    cfg.setProperty(Property.TRACE_USER, kdc.getAccumuloPrincipal());
+    cfg.setProperty(Property.TRACE_USER, serverUser.getPrincipal());
     cfg.setProperty(Property.TRACE_TOKEN_TYPE, KerberosToken.CLASS_NAME);
 
     // Pass down some KRB5 debug properties
@@ -232,6 +234,6 @@ public class MiniClusterHarness {
     // Make sure UserGroupInformation will do the correct login
     coreSite.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
 
-    cfg.setRootUserName(kdc.getClientPrincipal());
+    cfg.setRootUserName(kdc.getRootUser().getPrincipal());
   }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/SharedMiniClusterIT.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/SharedMiniClusterIT.java b/test/src/test/java/org/apache/accumulo/harness/SharedMiniClusterIT.java
index c844388..189b535 100644
--- a/test/src/test/java/org/apache/accumulo/harness/SharedMiniClusterIT.java
+++ b/test/src/test/java/org/apache/accumulo/harness/SharedMiniClusterIT.java
@@ -17,12 +17,17 @@
 package org.apache.accumulo.harness;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Random;
 
+import org.apache.accumulo.cluster.ClusterUser;
+import org.apache.accumulo.cluster.ClusterUsers;
 import org.apache.accumulo.core.client.Connector;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.security.TablePermission;
 import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
@@ -39,7 +44,7 @@ import org.slf4j.LoggerFactory;
  * a static BeforeClass-annotated method. Because it is static and invoked before any other BeforeClass methods in the implementation, the actual test classes
  * can't expose any information to tell the base class that it is to perform the one-MAC-per-class semantics.
  */
-public abstract class SharedMiniClusterIT extends AccumuloIT {
+public abstract class SharedMiniClusterIT extends AccumuloIT implements ClusterUsers {
   private static final Logger log = LoggerFactory.getLogger(SharedMiniClusterIT.class);
   private static final String TRUE = Boolean.toString(true);
 
@@ -65,10 +70,11 @@ public abstract class SharedMiniClusterIT extends AccumuloIT {
       conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
       UserGroupInformation.setConfiguration(conf);
       // Login as the client
-      UserGroupInformation.loginUserFromKeytab(krb.getClientPrincipal(), krb.getClientKeytab().getAbsolutePath());
+      ClusterUser rootUser = krb.getRootUser();
+      UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
       // Get the krb token
-      principal = krb.getClientPrincipal();
-      token = new KerberosToken(principal);
+      principal = rootUser.getPrincipal();
+      token = new KerberosToken(principal, rootUser.getKeytab());
     } else {
       rootPassword = "rootPasswordShared1";
       token = new PasswordToken(rootPassword);
@@ -76,6 +82,29 @@ public abstract class SharedMiniClusterIT extends AccumuloIT {
 
     cluster = harness.create(SharedMiniClusterIT.class.getName(), System.currentTimeMillis() + "_" + new Random().nextInt(Short.MAX_VALUE), token, krb);
     cluster.start();
+
+    if (null != krb) {
+      final String traceTable = Property.TRACE_TABLE.getDefaultValue();
+      final ClusterUser systemUser = krb.getAccumuloServerUser(), rootUser = krb.getRootUser();
+      // Login as the trace user
+      UserGroupInformation.loginUserFromKeytab(systemUser.getPrincipal(), systemUser.getKeytab().getAbsolutePath());
+
+      // Open a connector as the system user (ensures the user will exist for us to assign permissions to)
+      Connector conn = cluster.getConnector(systemUser.getPrincipal(), new KerberosToken(systemUser.getPrincipal(), systemUser.getKeytab()));
+
+      // Then, log back in as the "root" user and do the grant
+      UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
+      conn = cluster.getConnector(principal, token);
+
+      // Create the trace table
+      conn.tableOperations().create(traceTable);
+
+      // Trace user (which is the same kerberos principal as the system user, but using a normal KerberosToken) needs
+      // to have the ability to read, write and alter the trace table
+      conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.READ);
+      conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.WRITE);
+      conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.ALTER_TABLE);
+    }
   }
 
   @AfterClass
@@ -101,9 +130,20 @@ public abstract class SharedMiniClusterIT extends AccumuloIT {
   }
 
   public static AuthenticationToken getToken() {
+    if (token instanceof KerberosToken) {
+      try {
+        UserGroupInformation.loginUserFromKeytab(getPrincipal(), krb.getRootUser().getKeytab().getAbsolutePath());
+      } catch (IOException e) {
+        throw new RuntimeException("Failed to login", e);
+      }
+    }
     return token;
   }
 
+  public static String getPrincipal() {
+    return principal;
+  }
+
   public static MiniAccumuloClusterImpl getCluster() {
     return cluster;
   }
@@ -119,4 +159,24 @@ public abstract class SharedMiniClusterIT extends AccumuloIT {
       throw new RuntimeException(e);
     }
   }
+
+  @Override
+  public ClusterUser getAdminUser() {
+    if (null == krb) {
+      return new ClusterUser(getPrincipal(), getRootPassword());
+    } else {
+      return krb.getRootUser();
+    }
+  }
+
+  @Override
+  public ClusterUser getUser(int offset) {
+    if (null == krb) {
+      String user = SharedMiniClusterIT.class.getName() + "_" + testName.getMethodName() + "_" + offset;
+      // Password is the username
+      return new ClusterUser(user, user);
+    } else {
+      return krb.getClientPrincipal(offset);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/TestingKdc.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/TestingKdc.java b/test/src/test/java/org/apache/accumulo/harness/TestingKdc.java
index 2abdc62..acc2a03 100644
--- a/test/src/test/java/org/apache/accumulo/harness/TestingKdc.java
+++ b/test/src/test/java/org/apache/accumulo/harness/TestingKdc.java
@@ -16,26 +16,32 @@
  */
 package org.apache.accumulo.harness;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.io.File;
 import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
+import org.apache.accumulo.cluster.ClusterUser;
 import org.apache.hadoop.minikdc.MiniKdc;
 import org.junit.Assert;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Preconditions;
-
 /**
  * Creates a {@link MiniKdc} for tests to use to exercise secure Accumulo
  */
 public class TestingKdc {
   private static final Logger log = LoggerFactory.getLogger(TestingKdc.class);
 
+  public static final int NUM_USERS = 10;
+
   protected MiniKdc kdc = null;
-  protected File accumuloKeytab = null, clientKeytab = null;
-  protected String accumuloPrincipal = null, clientPrincipal = null;
+  protected ClusterUser accumuloServerUser = null, accumuloAdmin = null;
+  protected List<ClusterUser> clientPrincipals = null;
 
   public final String ORG_NAME = "EXAMPLE", ORG_DOMAIN = "COM";
 
@@ -44,21 +50,46 @@ public class TestingKdc {
   private boolean started = false;
 
   public TestingKdc() throws Exception {
+    this(computeKdcDir(), computeKeytabDir());
+  }
+
+  private static File computeKdcDir() {
     File targetDir = new File(System.getProperty("user.dir"), "target");
     Assert.assertTrue("Could not find Maven target directory: " + targetDir, targetDir.exists() && targetDir.isDirectory());
 
-    // Create the directories: target/kerberos/{keytabs,minikdc}
-    File krbDir = new File(targetDir, "kerberos"), kdcDir = new File(krbDir, "minikdc");
-    keytabDir = new File(krbDir, "keytabs");
+    // Create the directories: target/kerberos/minikdc
+    File kdcDir = new File(new File(targetDir, "kerberos"), "minikdc");
 
-    keytabDir.mkdirs();
     kdcDir.mkdirs();
 
-    hostname = InetAddress.getLocalHost().getCanonicalHostName();
+    return kdcDir;
+  }
+
+  private static File computeKeytabDir() {
+    File targetDir = new File(System.getProperty("user.dir"), "target");
+    Assert.assertTrue("Could not find Maven target directory: " + targetDir, targetDir.exists() && targetDir.isDirectory());
+
+    // Create the directories: target/kerberos/keytabs
+    File keytabDir = new File(new File(targetDir, "kerberos"), "keytabs");
+
+    keytabDir.mkdirs();
+
+    return keytabDir;
+  }
+
+  public TestingKdc(File kdcDir, File keytabDir) throws Exception {
+    checkNotNull(kdcDir, "KDC directory was null");
+    checkNotNull(keytabDir, "Keytab directory was null");
+
+    this.keytabDir = keytabDir;
+    this.hostname = InetAddress.getLocalHost().getCanonicalHostName();
+
+    log.debug("Starting MiniKdc in {} with keytabs in {}", kdcDir, keytabDir);
 
     Properties kdcConf = MiniKdc.createConf();
     kdcConf.setProperty(MiniKdc.ORG_NAME, ORG_NAME);
     kdcConf.setProperty(MiniKdc.ORG_DOMAIN, ORG_DOMAIN);
+    // kdcConf.setProperty(MiniKdc.DEBUG, "true");
     kdc = new MiniKdc(kdcConf, kdcDir);
   }
 
@@ -66,28 +97,45 @@ public class TestingKdc {
    * Starts the KDC and creates the principals and their keytabs
    */
   public synchronized void start() throws Exception {
-    Preconditions.checkArgument(!started, "KDC was already started");
+    checkArgument(!started, "KDC was already started");
     kdc.start();
+    Thread.sleep(1000);
 
-    accumuloKeytab = new File(keytabDir, "accumulo.keytab");
-    clientKeytab = new File(keytabDir, "client.keytab");
-
-    accumuloPrincipal = String.format("accumulo/%s", hostname);
-    clientPrincipal = "client";
+    // Create the identity for accumulo servers
+    File accumuloKeytab = new File(keytabDir, "accumulo.keytab");
+    String accumuloPrincipal = String.format("accumulo/%s", hostname);
 
     log.info("Creating Kerberos principal {} with keytab {}", accumuloPrincipal, accumuloKeytab);
     kdc.createPrincipal(accumuloKeytab, accumuloPrincipal);
-    log.info("Creating Kerberos principal {} with keytab {}", clientPrincipal, clientKeytab);
-    kdc.createPrincipal(clientKeytab, clientPrincipal);
 
-    accumuloPrincipal = qualifyUser(accumuloPrincipal);
-    clientPrincipal = qualifyUser(clientPrincipal);
+    accumuloServerUser = new ClusterUser(qualifyUser(accumuloPrincipal), accumuloKeytab);
+
+    // Create the identity for the "root" user
+    String rootPrincipal = "root";
+    File rootKeytab = new File(keytabDir, rootPrincipal + ".keytab");
+
+    log.info("Creating Kerberos principal {} with keytab {}", rootPrincipal, rootKeytab);
+    kdc.createPrincipal(rootKeytab, rootPrincipal);
+
+    accumuloAdmin = new ClusterUser(qualifyUser(rootPrincipal), rootKeytab);
+
+    clientPrincipals = new ArrayList<>(NUM_USERS);
+    // Create a number of unprivileged users for tests to use
+    for (int i = 1; i <= NUM_USERS; i++) {
+      String clientPrincipal = "client" + i;
+      File clientKeytab = new File(keytabDir, clientPrincipal + ".keytab");
+
+      log.info("Creating Kerberos principal {} with keytab {}", clientPrincipal, clientKeytab);
+      kdc.createPrincipal(clientKeytab, clientPrincipal);
+
+      clientPrincipals.add(new ClusterUser(qualifyUser(clientPrincipal), clientKeytab));
+    }
 
     started = true;
   }
 
   public synchronized void stop() throws Exception {
-    Preconditions.checkArgument(started, "KDC is not started");
+    checkArgument(started, "KDC is not started");
     kdc.stop();
     started = false;
   }
@@ -100,42 +148,38 @@ public class TestingKdc {
   }
 
   /**
-   * A Kerberos keytab for the Accumulo server processes
+   * A {@link ClusterUser} for Accumulo server processes to use
    */
-  public File getAccumuloKeytab() {
-    Preconditions.checkArgument(started, "Accumulo keytab is not initialized, is the KDC started?");
-    return accumuloKeytab;
+  public ClusterUser getAccumuloServerUser() {
+    checkArgument(started, "The KDC is not started");
+    return accumuloServerUser;
   }
 
   /**
-   * The corresponding principal for the Accumulo service keytab
+   * A {@link ClusterUser} which is the Accumulo "root" user
    */
-  public String getAccumuloPrincipal() {
-    Preconditions.checkArgument(started, "Accumulo principal is not initialized, is the KDC started?");
-    return accumuloPrincipal;
+  public ClusterUser getRootUser() {
+    checkArgument(started, "The KDC is not started");
+    return accumuloAdmin;
   }
 
   /**
-   * A Kerberos keytab for client use
-   */
-  public File getClientKeytab() {
-    Preconditions.checkArgument(started, "Client keytab is not initialized, is the KDC started?");
-    return clientKeytab;
-  }
-
-  /**
-   * The corresponding principal for the client keytab
+   * The {@link ClusterUser} corresponding to the given offset. Represents an unprivileged user.
+   *
+   * @param offset
+   *          The offset to fetch credentials for, valid through {@link #NUM_USERS}
    */
-  public String getClientPrincipal() {
-    Preconditions.checkArgument(started, "Client principal is not initialized, is the KDC started?");
-    return clientPrincipal;
+  public ClusterUser getClientPrincipal(int offset) {
+    checkArgument(started, "Client principal is not initialized, is the KDC started?");
+    checkArgument(offset >= 0 && offset < NUM_USERS, "Offset is invalid, must be non-negative and less than " + NUM_USERS);
+    return clientPrincipals.get(offset);
   }
 
   /**
    * @see MiniKdc#createPrincipal(File, String...)
    */
   public void createPrincipal(File keytabFile, String... principals) throws Exception {
-    Preconditions.checkArgument(started, "KDC is not started");
+    checkArgument(started, "KDC is not started");
     kdc.createPrincipal(keytabFile, principals);
   }
 

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterConfiguration.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterConfiguration.java b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterConfiguration.java
index 4752541..3ce83b8 100644
--- a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterConfiguration.java
+++ b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterConfiguration.java
@@ -16,6 +16,7 @@
  */
 package org.apache.accumulo.harness.conf;
 
+import org.apache.accumulo.core.client.ClientConfiguration;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 import org.apache.accumulo.harness.AccumuloClusterIT.ClusterType;
 
@@ -26,8 +27,9 @@ public interface AccumuloClusterConfiguration {
 
   ClusterType getClusterType();
 
-  String getPrincipal();
+  String getAdminPrincipal();
 
-  AuthenticationToken getToken();
+  AuthenticationToken getAdminToken();
 
+  ClientConfiguration getClientConf();
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterPropertyConfiguration.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterPropertyConfiguration.java b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterPropertyConfiguration.java
index 75c0e70..8661d39 100644
--- a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterPropertyConfiguration.java
+++ b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloClusterPropertyConfiguration.java
@@ -44,12 +44,14 @@ public abstract class AccumuloClusterPropertyConfiguration implements AccumuloCl
   public static final String ACCUMULO_MINI_PREFIX = "accumulo.it.cluster.mini.";
   public static final String ACCUMULO_STANDALONE_PREFIX = "accumulo.it.cluster.standalone.";
 
+  public static final String ACCUMULO_CLUSTER_CLIENT_CONF_KEY = "accumulo.it.cluster.clientconf";
+
   protected ClusterType clusterType;
 
   public static AccumuloClusterPropertyConfiguration get() {
     Properties systemProperties = System.getProperties();
 
-    String clusterTypeValue = null;
+    String clusterTypeValue = null, clientConf = null;
     String propertyFile = systemProperties.getProperty(ACCUMULO_IT_PROPERTIES_FILE);
 
     if (null != propertyFile) {
@@ -78,6 +80,7 @@ public abstract class AccumuloClusterPropertyConfiguration implements AccumuloCl
           }
 
           clusterTypeValue = fileProperties.getProperty(ACCUMULO_CLUSTER_TYPE_KEY);
+          clientConf = fileProperties.getProperty(ACCUMULO_CLUSTER_CLIENT_CONF_KEY);
         }
       } else {
         log.debug("Property file ({}) is not a readable file", propertyFile);
@@ -90,6 +93,10 @@ public abstract class AccumuloClusterPropertyConfiguration implements AccumuloCl
       clusterTypeValue = systemProperties.getProperty(ACCUMULO_CLUSTER_TYPE_KEY);
     }
 
+    if (null == clientConf) {
+      clientConf = systemProperties.getProperty(ACCUMULO_CLUSTER_CLIENT_CONF_KEY);
+    }
+
     ClusterType type;
     if (null == clusterTypeValue) {
       type = ClusterType.MINI;
@@ -101,9 +108,17 @@ public abstract class AccumuloClusterPropertyConfiguration implements AccumuloCl
 
     switch (type) {
       case MINI:
+        // we'll let no client conf pass through and expect that the caller will set it after MAC is started
         return new AccumuloMiniClusterConfiguration();
       case STANDALONE:
-        return new StandaloneAccumuloClusterConfiguration();
+        if (null == clientConf) {
+          throw new RuntimeException("Expected client configuration to be provided: " + ACCUMULO_CLUSTER_CLIENT_CONF_KEY);
+        }
+        File clientConfFile = new File(clientConf);
+        if (!clientConfFile.exists() || !clientConfFile.isFile()) {
+          throw new RuntimeException("Client configuration should be a normal file: " + clientConfFile);
+        }
+        return new StandaloneAccumuloClusterConfiguration(clientConfFile);
       default:
         throw new RuntimeException("Clusters other than MiniAccumuloCluster are not yet implemented");
     }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloMiniClusterConfiguration.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloMiniClusterConfiguration.java b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloMiniClusterConfiguration.java
index 0efba9e..a1295dc 100644
--- a/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloMiniClusterConfiguration.java
+++ b/test/src/test/java/org/apache/accumulo/harness/conf/AccumuloMiniClusterConfiguration.java
@@ -16,10 +16,11 @@
  */
 package org.apache.accumulo.harness.conf;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.Map;
 
+import org.apache.accumulo.cluster.ClusterUser;
+import org.apache.accumulo.core.client.ClientConfiguration;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
@@ -29,10 +30,11 @@ import org.apache.accumulo.harness.MiniClusterHarness;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
 import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.hadoop.security.authentication.util.KerberosName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Extract configuration properties for a MiniAccumuloCluster from Java properties
  */
@@ -47,6 +49,7 @@ public class AccumuloMiniClusterConfiguration extends AccumuloClusterPropertyCon
 
   private final Map<String,String> conf;
   private final boolean saslEnabled;
+  private ClientConfiguration clientConf;
 
   public AccumuloMiniClusterConfiguration() {
     ClusterType type = getClusterType();
@@ -60,13 +63,9 @@ public class AccumuloMiniClusterConfiguration extends AccumuloClusterPropertyCon
   }
 
   @Override
-  public String getPrincipal() {
+  public String getAdminPrincipal() {
     if (saslEnabled) {
-      try {
-        return new KerberosName(AccumuloClusterIT.getClientPrincipal()).getShortName();
-      } catch (IOException e) {
-        throw new RuntimeException("Could not parse client principal", e);
-      }
+      return AccumuloClusterIT.getKdc().getRootUser().getPrincipal();
     } else {
       String principal = conf.get(ACCUMULO_MINI_PRINCIPAL_KEY);
       if (null == principal) {
@@ -78,18 +77,16 @@ public class AccumuloMiniClusterConfiguration extends AccumuloClusterPropertyCon
   }
 
   @Override
-  public AuthenticationToken getToken() {
+  public AuthenticationToken getAdminToken() {
     if (saslEnabled) {
       // Turn on Kerberos authentication so UGI acts properly
       final Configuration conf = new Configuration(false);
       conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
       UserGroupInformation.setConfiguration(conf);
 
-      File clientKeytab = AccumuloClusterIT.getClientKeytab();
-      String clientPrincipal = AccumuloClusterIT.getClientPrincipal();
+      ClusterUser rootUser = AccumuloClusterIT.getKdc().getRootUser();
       try {
-        UserGroupInformation.loginUserFromKeytab(clientPrincipal, clientKeytab.getAbsolutePath());
-        return new KerberosToken(clientPrincipal);
+        return new KerberosToken(rootUser.getPrincipal(), rootUser.getKeytab());
       } catch (IOException e) {
         throw new RuntimeException(e);
       }
@@ -108,4 +105,13 @@ public class AccumuloMiniClusterConfiguration extends AccumuloClusterPropertyCon
     return ClusterType.MINI;
   }
 
+  @Override
+  public ClientConfiguration getClientConf() {
+    return clientConf;
+  }
+
+  public void setClientConf(ClientConfiguration conf) {
+    Preconditions.checkNotNull(conf, "Client configuration was null");
+    this.clientConf = conf;
+  }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/1c5bef32/test/src/test/java/org/apache/accumulo/harness/conf/StandaloneAccumuloClusterConfiguration.java
----------------------------------------------------------------------
diff --git a/test/src/test/java/org/apache/accumulo/harness/conf/StandaloneAccumuloClusterConfiguration.java b/test/src/test/java/org/apache/accumulo/harness/conf/StandaloneAccumuloClusterConfiguration.java
index 5fb7203..12e9619 100644
--- a/test/src/test/java/org/apache/accumulo/harness/conf/StandaloneAccumuloClusterConfiguration.java
+++ b/test/src/test/java/org/apache/accumulo/harness/conf/StandaloneAccumuloClusterConfiguration.java
@@ -16,48 +16,103 @@
  */
 package org.apache.accumulo.harness.conf;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
+import org.apache.accumulo.cluster.ClusterUser;
+import org.apache.accumulo.core.client.ClientConfiguration;
+import org.apache.accumulo.core.client.ClientConfiguration.ClientProperty;
 import org.apache.accumulo.core.client.Instance;
 import org.apache.accumulo.core.client.ZooKeeperInstance;
 import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
+import org.apache.accumulo.core.client.security.tokens.KerberosToken;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 import org.apache.accumulo.harness.AccumuloClusterIT.ClusterType;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.hadoop.fs.Path;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Extract connection information to a standalone Accumulo instance from Java properties
  */
 public class StandaloneAccumuloClusterConfiguration extends AccumuloClusterPropertyConfiguration {
+  private static final Logger log = LoggerFactory.getLogger(StandaloneAccumuloClusterConfiguration.class);
 
-  public static final String ACCUMULO_STANDALONE_PRINCIPAL_KEY = ACCUMULO_STANDALONE_PREFIX + "principal";
-  public static final String ACCUMULO_STANDALONE_PRINCIPAL_DEFAULT = "root";
-  public static final String ACCUMULO_STANDALONE_PASSWORD_KEY = ACCUMULO_STANDALONE_PREFIX + "password";
+  public static final String ACCUMULO_STANDALONE_ADMIN_PRINCIPAL_KEY = ACCUMULO_STANDALONE_PREFIX + "admin.principal";
+  public static final String ACCUMULO_STANDALONE_ADMIN_PRINCIPAL_DEFAULT = "root";
+  public static final String ACCUMULO_STANDALONE_PASSWORD_KEY = ACCUMULO_STANDALONE_PREFIX + "admin.password";
   public static final String ACCUMULO_STANDALONE_PASSWORD_DEFAULT = "rootPassword1";
+  public static final String ACCUMULO_STANDALONE_ADMIN_KEYTAB_KEY = ACCUMULO_STANDALONE_PREFIX + "admin.keytab";
   public static final String ACCUMULO_STANDALONE_ZOOKEEPERS_KEY = ACCUMULO_STANDALONE_PREFIX + "zookeepers";
   public static final String ACCUMULO_STANDALONE_ZOOKEEPERS_DEFAULT = "localhost";
   public static final String ACCUMULO_STANDALONE_INSTANCE_NAME_KEY = ACCUMULO_STANDALONE_PREFIX + "instance.name";
   public static final String ACCUMULO_STANDALONE_INSTANCE_NAME_DEFAULT = "accumulo";
+  public static final String ACCUMULO_STANDALONE_TMP_DIR_KEY = ACCUMULO_STANDALONE_PREFIX + "tmpdir";
+  public static final String ACCUMULO_STANDALONE_TMP_DIR_DEFAULT = "/tmp";
+
+  // A set of users we can use to connect to this instances
+  public static final String ACCUMULO_STANDALONE_USER_KEY = ACCUMULO_STANDALONE_PREFIX + "users.";
+  // Keytabs for the users
+  public static final String ACCUMULO_STANDALONE_USER_KEYTABS_KEY = ACCUMULO_STANDALONE_PREFIX + "keytabs.";
 
   public static final String ACCUMULO_STANDALONE_HOME = ACCUMULO_STANDALONE_PREFIX + "home";
   public static final String ACCUMULO_STANDALONE_CONF = ACCUMULO_STANDALONE_PREFIX + "conf";
   public static final String ACCUMULO_STANDALONE_HADOOP_CONF = ACCUMULO_STANDALONE_PREFIX + "hadoop.conf";
 
   private Map<String,String> conf;
+  private File clientConfFile;
+  private ClientConfiguration clientConf;
+  private List<ClusterUser> clusterUsers;
 
-  public StandaloneAccumuloClusterConfiguration() {
+  public StandaloneAccumuloClusterConfiguration(File clientConfFile) {
     ClusterType type = getClusterType();
     if (ClusterType.STANDALONE != type) {
       throw new IllegalStateException("Expected only to see standalone cluster state");
     }
 
     this.conf = getConfiguration(type);
+    this.clientConfFile = clientConfFile;
+    this.clientConf = ClientConfiguration.loadDefault();
+    try {
+      clientConf.addConfiguration(new PropertiesConfiguration(clientConfFile));
+    } catch (ConfigurationException e) {
+      throw new RuntimeException("Failed to load client configuration from " + clientConfFile);
+    }
+    // Update instance name if not already set
+    if (!clientConf.containsKey(ClientProperty.INSTANCE_NAME.getKey())) {
+      clientConf.withInstance(getInstanceName());
+    }
+    // Update zookeeper hosts if not already set
+    if (!clientConf.containsKey(ClientProperty.INSTANCE_ZK_HOST.getKey())) {
+      clientConf.withZkHosts(getZooKeepers());
+    }
+
+    clusterUsers = new ArrayList<>();
+    for (Entry<String,String> entry : conf.entrySet()) {
+      String key = entry.getKey();
+      if (key.startsWith(ACCUMULO_STANDALONE_USER_KEY)) {
+        String suffix = key.substring(ACCUMULO_STANDALONE_USER_KEY.length());
+        String keytab = conf.get(ACCUMULO_STANDALONE_USER_KEYTABS_KEY + suffix);
+        if (null != keytab) {
+          clusterUsers.add(new ClusterUser(entry.getValue(), keytab));
+        }
+      }
+    }
+    log.info("Initialized Accumulo users with Kerberos keytabs: {}", clusterUsers);
   }
 
   @Override
-  public String getPrincipal() {
-    String principal = conf.get(ACCUMULO_STANDALONE_PRINCIPAL_KEY);
+  public String getAdminPrincipal() {
+    String principal = conf.get(ACCUMULO_STANDALONE_ADMIN_PRINCIPAL_KEY);
     if (null == principal) {
-      principal = ACCUMULO_STANDALONE_PRINCIPAL_DEFAULT;
+      principal = ACCUMULO_STANDALONE_ADMIN_PRINCIPAL_DEFAULT;
     }
     return principal;
   }
@@ -70,12 +125,38 @@ public class StandaloneAccumuloClusterConfiguration extends AccumuloClusterPrope
     return password;
   }
 
+  public File getAdminKeytab() {
+    String keytabPath = conf.get(ACCUMULO_STANDALONE_ADMIN_KEYTAB_KEY);
+    if (null == keytabPath) {
+      throw new RuntimeException("SASL is enabled, but " + ACCUMULO_STANDALONE_ADMIN_KEYTAB_KEY + " was not provided");
+    }
+    File keytab = new File(keytabPath);
+    if (!keytab.exists() || !keytab.isFile()) {
+      throw new RuntimeException(keytabPath + " should be a regular file");
+    }
+    return keytab;
+  }
+
   @Override
-  public AuthenticationToken getToken() {
-    return new PasswordToken(getPassword());
+  public AuthenticationToken getAdminToken() {
+    if (clientConf.getBoolean(ClientProperty.INSTANCE_RPC_SASL_ENABLED.getKey(), false)) {
+      File keytab = getAdminKeytab();
+      try {
+        return new KerberosToken(getAdminPrincipal(), keytab);
+      } catch (IOException e) {
+        // The user isn't logged in
+        throw new RuntimeException("Failed to create KerberosToken", e);
+      }
+    } else {
+      return new PasswordToken(getPassword());
+    }
   }
 
   public String getZooKeepers() {
+    if (clientConf.containsKey(ClientProperty.INSTANCE_ZK_HOST.getKey())) {
+      return clientConf.get(ClientProperty.INSTANCE_ZK_HOST);
+    }
+
     String zookeepers = conf.get(ACCUMULO_STANDALONE_ZOOKEEPERS_KEY);
     if (null == zookeepers) {
       zookeepers = ACCUMULO_STANDALONE_ZOOKEEPERS_DEFAULT;
@@ -84,6 +165,10 @@ public class StandaloneAccumuloClusterConfiguration extends AccumuloClusterPrope
   }
 
   public String getInstanceName() {
+    if (clientConf.containsKey(ClientProperty.INSTANCE_NAME.getKey())) {
+      return clientConf.get(ClientProperty.INSTANCE_NAME);
+    }
+
     String instanceName = conf.get(ACCUMULO_STANDALONE_INSTANCE_NAME_KEY);
     if (null == instanceName) {
       instanceName = ACCUMULO_STANDALONE_INSTANCE_NAME_DEFAULT;
@@ -92,7 +177,8 @@ public class StandaloneAccumuloClusterConfiguration extends AccumuloClusterPrope
   }
 
   public Instance getInstance() {
-    return new ZooKeeperInstance(getInstanceName(), getZooKeepers());
+    // Make sure the ZKI is created with the ClientConf so it gets things like SASL passed through to the connector
+    return new ZooKeeperInstance(clientConf);
   }
 
   @Override
@@ -111,4 +197,25 @@ public class StandaloneAccumuloClusterConfiguration extends AccumuloClusterPrope
   public String getAccumuloConfDir() {
     return conf.get(ACCUMULO_STANDALONE_CONF);
   }
+
+  @Override
+  public ClientConfiguration getClientConf() {
+    return clientConf;
+  }
+
+  public File getClientConfFile() {
+    return clientConfFile;
+  }
+
+  public Path getTmpDirectory() {
+    String tmpDir = conf.get(ACCUMULO_STANDALONE_TMP_DIR_KEY);
+    if (null == tmpDir) {
+      tmpDir = ACCUMULO_STANDALONE_TMP_DIR_DEFAULT;
+    }
+    return new Path(tmpDir);
+  }
+
+  public List<ClusterUser> getUsers() {
+    return Collections.unmodifiableList(clusterUsers);
+  }
 }


Mime
View raw message