accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ctubb...@apache.org
Subject [17/28] accumulo git commit: Merge branch '1.7' into 1.8
Date Thu, 01 Sep 2016 19:04:37 GMT
http://git-wip-us.apache.org/repos/asf/accumulo/blob/a4afd1bf/test/src/main/java/org/apache/accumulo/test/functional/KerberosProxyIT.java
----------------------------------------------------------------------
diff --cc test/src/main/java/org/apache/accumulo/test/functional/KerberosProxyIT.java
index 7264a42,0000000..59a6762
mode 100644,000000..100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/KerberosProxyIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/KerberosProxyIT.java
@@@ -1,485 -1,0 +1,485 @@@
 +/*
 + * 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.test.functional;
 +
 +import static java.nio.charset.StandardCharsets.UTF_8;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertTrue;
 +
 +import java.io.File;
 +import java.io.FileWriter;
 +import java.io.IOException;
 +import java.net.ConnectException;
 +import java.net.InetAddress;
 +import java.nio.ByteBuffer;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Properties;
 +
 +import org.apache.accumulo.cluster.ClusterUser;
 +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.rpc.UGIAssumingTransport;
 +import org.apache.accumulo.harness.AccumuloITBase;
 +import org.apache.accumulo.harness.MiniClusterConfigurationCallback;
 +import org.apache.accumulo.harness.MiniClusterHarness;
 +import org.apache.accumulo.harness.TestingKdc;
 +import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
 +import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl;
 +import org.apache.accumulo.proxy.Proxy;
 +import org.apache.accumulo.proxy.ProxyServer;
 +import org.apache.accumulo.proxy.thrift.AccumuloProxy;
 +import org.apache.accumulo.proxy.thrift.AccumuloProxy.Client;
 +import org.apache.accumulo.proxy.thrift.AccumuloSecurityException;
 +import org.apache.accumulo.proxy.thrift.ColumnUpdate;
 +import org.apache.accumulo.proxy.thrift.Key;
 +import org.apache.accumulo.proxy.thrift.KeyValue;
 +import org.apache.accumulo.proxy.thrift.ScanOptions;
 +import org.apache.accumulo.proxy.thrift.ScanResult;
 +import org.apache.accumulo.proxy.thrift.TimeType;
 +import org.apache.accumulo.proxy.thrift.WriterOptions;
 +import org.apache.accumulo.server.util.PortUtils;
- import org.apache.accumulo.test.categories.MiniClusterOnlyTest;
++import org.apache.accumulo.test.categories.MiniClusterOnlyTests;
 +import org.apache.hadoop.conf.Configuration;
 +import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
 +import org.apache.hadoop.security.UserGroupInformation;
 +import org.apache.thrift.protocol.TCompactProtocol;
 +import org.apache.thrift.transport.TSaslClientTransport;
 +import org.apache.thrift.transport.TSocket;
 +import org.apache.thrift.transport.TTransportException;
 +import org.hamcrest.Description;
 +import org.hamcrest.TypeSafeMatcher;
 +import org.junit.After;
 +import org.junit.AfterClass;
 +import org.junit.Before;
 +import org.junit.BeforeClass;
 +import org.junit.Rule;
 +import org.junit.Test;
 +import org.junit.experimental.categories.Category;
 +import org.junit.rules.ExpectedException;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +/**
 + * Tests impersonation of clients by the proxy over SASL
 + */
- @Category(MiniClusterOnlyTest.class)
++@Category(MiniClusterOnlyTests.class)
 +public class KerberosProxyIT extends AccumuloITBase {
 +  private static final Logger log = LoggerFactory.getLogger(KerberosProxyIT.class);
 +
 +  @Rule
 +  public ExpectedException thrown = ExpectedException.none();
 +
 +  private static TestingKdc kdc;
 +  private static String krbEnabledForITs = null;
 +  private static File proxyKeytab;
 +  private static String hostname, proxyPrimary, proxyPrincipal;
 +
 +  @Override
 +  protected int defaultTimeoutSeconds() {
 +    return 60 * 5;
 +  }
 +
 +  @BeforeClass
 +  public static void startKdc() throws Exception {
 +    kdc = new TestingKdc();
 +    kdc.start();
 +    krbEnabledForITs = System.getProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION);
 +    if (null == krbEnabledForITs || !Boolean.parseBoolean(krbEnabledForITs)) {
 +      System.setProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION, "true");
 +    }
 +
 +    // Create a principal+keytab for the proxy
 +    proxyKeytab = new File(kdc.getKeytabDir(), "proxy.keytab");
 +    hostname = InetAddress.getLocalHost().getCanonicalHostName();
 +    // Set the primary because the client needs to know it
 +    proxyPrimary = "proxy";
 +    // Qualify with an instance
 +    proxyPrincipal = proxyPrimary + "/" + hostname;
 +    kdc.createPrincipal(proxyKeytab, proxyPrincipal);
 +    // Tack on the realm too
 +    proxyPrincipal = kdc.qualifyUser(proxyPrincipal);
 +  }
 +
 +  @AfterClass
 +  public static void stopKdc() throws Exception {
 +    if (null != kdc) {
 +      kdc.stop();
 +    }
 +    if (null != krbEnabledForITs) {
 +      System.setProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION, krbEnabledForITs);
 +    }
 +    UserGroupInformation.setConfiguration(new Configuration(false));
 +  }
 +
 +  private MiniAccumuloClusterImpl mac;
 +  private Process proxyProcess;
 +  private int proxyPort;
 +
 +  @Before
 +  public void startMac() throws Exception {
 +    MiniClusterHarness harness = new MiniClusterHarness();
 +    mac = harness.create(getClass().getName(), testName.getMethodName(), new PasswordToken("unused"), new MiniClusterConfigurationCallback() {
 +
 +      @Override
 +      public void configureMiniCluster(MiniAccumuloConfigImpl cfg, Configuration coreSite) {
 +        cfg.setNumTservers(1);
 +        Map<String,String> siteCfg = cfg.getSiteConfig();
 +        // Allow the proxy to impersonate the client user, but no one else
 +        siteCfg.put(Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION.getKey(), proxyPrincipal + ":" + kdc.getRootUser().getPrincipal());
 +        siteCfg.put(Property.INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION.getKey(), "*");
 +        cfg.setSiteConfig(siteCfg);
 +      }
 +
 +    }, kdc);
 +
 +    mac.start();
 +    MiniAccumuloConfigImpl cfg = mac.getConfig();
 +
 +    // Generate Proxy configuration and start the proxy
 +    proxyProcess = startProxy(cfg);
 +
 +    // Enabled kerberos auth
 +    Configuration conf = new Configuration(false);
 +    conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
 +    UserGroupInformation.setConfiguration(conf);
 +
 +    boolean success = false;
 +    ClusterUser rootUser = kdc.getRootUser();
 +    // Rely on the junit timeout rule
 +    while (!success) {
 +      UserGroupInformation ugi;
 +      try {
 +        ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
 +      } catch (IOException ex) {
 +        log.info("Login as root is failing", ex);
 +        Thread.sleep(3000);
 +        continue;
 +      }
 +
 +      TSocket socket = new TSocket(hostname, proxyPort);
 +      log.info("Connecting to proxy with server primary '" + proxyPrimary + "' running on " + hostname);
 +      TSaslClientTransport transport = new TSaslClientTransport("GSSAPI", null, proxyPrimary, hostname, Collections.singletonMap("javax.security.sasl.qop",
 +          "auth"), null, socket);
 +
 +      final UGIAssumingTransport ugiTransport = new UGIAssumingTransport(transport, ugi);
 +
 +      try {
 +        // UGI transport will perform the doAs for us
 +        ugiTransport.open();
 +        success = true;
 +      } catch (TTransportException e) {
 +        Throwable cause = e.getCause();
 +        if (null != cause && cause instanceof ConnectException) {
 +          log.info("Proxy not yet up, waiting");
 +          Thread.sleep(3000);
 +          proxyProcess = checkProxyAndRestart(proxyProcess, cfg);
 +          continue;
 +        }
 +      } finally {
 +        if (null != ugiTransport) {
 +          ugiTransport.close();
 +        }
 +      }
 +    }
 +
 +    assertTrue("Failed to connect to the proxy repeatedly", success);
 +  }
 +
 +  /**
 +   * Starts the thrift proxy using the given MAConfig.
 +   *
 +   * @param cfg
 +   *          configuration for MAC
 +   * @return Process for the thrift proxy
 +   */
 +  private Process startProxy(MiniAccumuloConfigImpl cfg) throws IOException {
 +    File proxyPropertiesFile = generateNewProxyConfiguration(cfg);
 +    return mac.exec(Proxy.class, "-p", proxyPropertiesFile.getCanonicalPath());
 +  }
 +
 +  /**
 +   * Generates a proxy configuration file for the MAC instance. Implicitly updates {@link #proxyPort} when choosing the port the proxy will listen on.
 +   *
 +   * @param cfg
 +   *          The MAC configuration
 +   * @return The proxy's configuration file
 +   */
 +  private File generateNewProxyConfiguration(MiniAccumuloConfigImpl cfg) throws IOException {
 +    // Chooses a new port for the proxy as side-effect
 +    proxyPort = PortUtils.getRandomFreePort();
 +
 +    // Proxy configuration
 +    File proxyPropertiesFile = new File(cfg.getConfDir(), "proxy.properties");
 +    if (proxyPropertiesFile.exists()) {
 +      assertTrue("Failed to delete proxy.properties file", proxyPropertiesFile.delete());
 +    }
 +    Properties proxyProperties = new Properties();
 +    proxyProperties.setProperty("useMockInstance", "false");
 +    proxyProperties.setProperty("useMiniAccumulo", "false");
 +    proxyProperties.setProperty("protocolFactory", TCompactProtocol.Factory.class.getName());
 +    proxyProperties.setProperty("tokenClass", KerberosToken.class.getName());
 +    proxyProperties.setProperty("port", Integer.toString(proxyPort));
 +    proxyProperties.setProperty("maxFrameSize", "16M");
 +    proxyProperties.setProperty("instance", mac.getInstanceName());
 +    proxyProperties.setProperty("zookeepers", mac.getZooKeepers());
 +    proxyProperties.setProperty("thriftServerType", "sasl");
 +    proxyProperties.setProperty("kerberosPrincipal", proxyPrincipal);
 +    proxyProperties.setProperty("kerberosKeytab", proxyKeytab.getCanonicalPath());
 +
 +    // Write out the proxy.properties file
 +    FileWriter writer = new FileWriter(proxyPropertiesFile);
 +    proxyProperties.store(writer, "Configuration for Accumulo proxy");
 +    writer.close();
 +
 +    log.info("Created configuration for proxy listening on {}", proxyPort);
 +
 +    return proxyPropertiesFile;
 +  }
 +
 +  /**
 +   * Restarts the thrift proxy if the previous instance is no longer running. If the proxy is still running, this method does nothing.
 +   *
 +   * @param proxy
 +   *          The thrift proxy process
 +   * @param cfg
 +   *          The MAC configuration
 +   * @return The process for the Proxy, either the previous instance or a new instance.
 +   */
 +  private Process checkProxyAndRestart(Process proxy, MiniAccumuloConfigImpl cfg) throws IOException {
 +    try {
 +      // Get the return code
 +      proxy.exitValue();
 +    } catch (IllegalThreadStateException e) {
 +      log.info("Proxy is still running");
 +      // OK, process is still running, don't restart
 +      return proxy;
 +    }
 +
 +    log.info("Restarting proxy because it is no longer alive");
 +
 +    // We got a return code which means the proxy exited. We'll assume this is because it failed
 +    // to bind the port due to the known race condition between choosing a port and having the
 +    // proxy bind it.
 +    return startProxy(cfg);
 +  }
 +
 +  @After
 +  public void stopMac() throws Exception {
 +    if (null != proxyProcess) {
 +      log.info("Destroying proxy process");
 +      proxyProcess.destroy();
 +      log.info("Waiting for proxy termination");
 +      proxyProcess.waitFor();
 +      log.info("Proxy terminated");
 +    }
 +    if (null != mac) {
 +      mac.stop();
 +    }
 +  }
 +
 +  @Test
 +  public void testProxyClient() throws Exception {
 +    ClusterUser rootUser = kdc.getRootUser();
 +    UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
 +
 +    TSocket socket = new TSocket(hostname, proxyPort);
 +    log.info("Connecting to proxy with server primary '" + proxyPrimary + "' running on " + hostname);
 +    TSaslClientTransport transport = new TSaslClientTransport("GSSAPI", null, proxyPrimary, hostname, Collections.singletonMap("javax.security.sasl.qop",
 +        "auth"), null, socket);
 +
 +    final UGIAssumingTransport ugiTransport = new UGIAssumingTransport(transport, ugi);
 +
 +    // UGI transport will perform the doAs for us
 +    ugiTransport.open();
 +
 +    AccumuloProxy.Client.Factory factory = new AccumuloProxy.Client.Factory();
 +    Client client = factory.getClient(new TCompactProtocol(ugiTransport), new TCompactProtocol(ugiTransport));
 +
 +    // Will fail if the proxy can impersonate the client
 +    ByteBuffer login = client.login(rootUser.getPrincipal(), Collections.<String,String> emptyMap());
 +
 +    // For all of the below actions, the proxy user doesn't have permission to do any of them, but the client user does.
 +    // The fact that any of them actually run tells us that impersonation is working.
 +
 +    // Create a table
 +    String table = "table";
 +    if (!client.tableExists(login, table)) {
 +      client.createTable(login, table, true, TimeType.MILLIS);
 +    }
 +
 +    // Write two records to the table
 +    String writer = client.createWriter(login, table, new WriterOptions());
 +    Map<ByteBuffer,List<ColumnUpdate>> updates = new HashMap<>();
 +    ColumnUpdate update = new ColumnUpdate(ByteBuffer.wrap("cf1".getBytes(UTF_8)), ByteBuffer.wrap("cq1".getBytes(UTF_8)));
 +    update.setValue(ByteBuffer.wrap("value1".getBytes(UTF_8)));
 +    updates.put(ByteBuffer.wrap("row1".getBytes(UTF_8)), Collections.<ColumnUpdate> singletonList(update));
 +    update = new ColumnUpdate(ByteBuffer.wrap("cf2".getBytes(UTF_8)), ByteBuffer.wrap("cq2".getBytes(UTF_8)));
 +    update.setValue(ByteBuffer.wrap("value2".getBytes(UTF_8)));
 +    updates.put(ByteBuffer.wrap("row2".getBytes(UTF_8)), Collections.<ColumnUpdate> singletonList(update));
 +    client.update(writer, updates);
 +
 +    // Flush and close the writer
 +    client.flush(writer);
 +    client.closeWriter(writer);
 +
 +    // Open a scanner to the table
 +    String scanner = client.createScanner(login, table, new ScanOptions());
 +    ScanResult results = client.nextK(scanner, 10);
 +    assertEquals(2, results.getResults().size());
 +
 +    // Check the first key-value
 +    KeyValue kv = results.getResults().get(0);
 +    Key k = kv.key;
 +    ByteBuffer v = kv.value;
 +    assertEquals(ByteBuffer.wrap("row1".getBytes(UTF_8)), k.row);
 +    assertEquals(ByteBuffer.wrap("cf1".getBytes(UTF_8)), k.colFamily);
 +    assertEquals(ByteBuffer.wrap("cq1".getBytes(UTF_8)), k.colQualifier);
 +    assertEquals(ByteBuffer.wrap(new byte[0]), k.colVisibility);
 +    assertEquals(ByteBuffer.wrap("value1".getBytes(UTF_8)), v);
 +
 +    // And then the second
 +    kv = results.getResults().get(1);
 +    k = kv.key;
 +    v = kv.value;
 +    assertEquals(ByteBuffer.wrap("row2".getBytes(UTF_8)), k.row);
 +    assertEquals(ByteBuffer.wrap("cf2".getBytes(UTF_8)), k.colFamily);
 +    assertEquals(ByteBuffer.wrap("cq2".getBytes(UTF_8)), k.colQualifier);
 +    assertEquals(ByteBuffer.wrap(new byte[0]), k.colVisibility);
 +    assertEquals(ByteBuffer.wrap("value2".getBytes(UTF_8)), v);
 +
 +    // Close the scanner
 +    client.closeScanner(scanner);
 +
 +    ugiTransport.close();
 +  }
 +
 +  @Test
 +  public void testDisallowedClientForImpersonation() throws Exception {
 +    String user = testName.getMethodName();
 +    File keytab = new File(kdc.getKeytabDir(), user + ".keytab");
 +    kdc.createPrincipal(keytab, user);
 +
 +    // Login as the new user
 +    UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user, keytab.getAbsolutePath());
 +
 +    log.info("Logged in as " + ugi);
 +
 +    // Expect an AccumuloSecurityException
 +    thrown.expect(AccumuloSecurityException.class);
 +    // Error msg would look like:
 +    //
 +    // org.apache.accumulo.core.client.AccumuloSecurityException: Error BAD_CREDENTIALS for user Principal in credentials object should match kerberos
 +    // principal.
 +    // Expected 'proxy/hw10447.local@EXAMPLE.COM' but was 'testDisallowedClientForImpersonation@EXAMPLE.COM' - Username or Password is Invalid)
 +    thrown.expect(new ThriftExceptionMatchesPattern(".*Error BAD_CREDENTIALS.*"));
 +    thrown.expect(new ThriftExceptionMatchesPattern(".*Expected '" + proxyPrincipal + "' but was '" + kdc.qualifyUser(user) + "'.*"));
 +
 +    TSocket socket = new TSocket(hostname, proxyPort);
 +    log.info("Connecting to proxy with server primary '" + proxyPrimary + "' running on " + hostname);
 +
 +    // Should fail to open the tran
 +    TSaslClientTransport transport = new TSaslClientTransport("GSSAPI", null, proxyPrimary, hostname, Collections.singletonMap("javax.security.sasl.qop",
 +        "auth"), null, socket);
 +
 +    final UGIAssumingTransport ugiTransport = new UGIAssumingTransport(transport, ugi);
 +
 +    // UGI transport will perform the doAs for us
 +    ugiTransport.open();
 +
 +    AccumuloProxy.Client.Factory factory = new AccumuloProxy.Client.Factory();
 +    Client client = factory.getClient(new TCompactProtocol(ugiTransport), new TCompactProtocol(ugiTransport));
 +
 +    // Will fail because the proxy can't impersonate this user (per the site configuration)
 +    try {
 +      client.login(kdc.qualifyUser(user), Collections.<String,String> emptyMap());
 +    } finally {
 +      if (null != ugiTransport) {
 +        ugiTransport.close();
 +      }
 +    }
 +  }
 +
 +  @Test
 +  public void testMismatchPrincipals() throws Exception {
 +    ClusterUser rootUser = kdc.getRootUser();
 +    // Should get an AccumuloSecurityException and the given message
 +    thrown.expect(AccumuloSecurityException.class);
 +    thrown.expect(new ThriftExceptionMatchesPattern(ProxyServer.RPC_ACCUMULO_PRINCIPAL_MISMATCH_MSG));
 +
 +    // Make a new user
 +    String user = testName.getMethodName();
 +    File keytab = new File(kdc.getKeytabDir(), user + ".keytab");
 +    kdc.createPrincipal(keytab, user);
 +
 +    // Login as the new user
 +    UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user, keytab.getAbsolutePath());
 +
 +    log.info("Logged in as " + ugi);
 +
 +    TSocket socket = new TSocket(hostname, proxyPort);
 +    log.info("Connecting to proxy with server primary '" + proxyPrimary + "' running on " + hostname);
 +
 +    // Should fail to open the tran
 +    TSaslClientTransport transport = new TSaslClientTransport("GSSAPI", null, proxyPrimary, hostname, Collections.singletonMap("javax.security.sasl.qop",
 +        "auth"), null, socket);
 +
 +    final UGIAssumingTransport ugiTransport = new UGIAssumingTransport(transport, ugi);
 +
 +    // UGI transport will perform the doAs for us
 +    ugiTransport.open();
 +
 +    AccumuloProxy.Client.Factory factory = new AccumuloProxy.Client.Factory();
 +    Client client = factory.getClient(new TCompactProtocol(ugiTransport), new TCompactProtocol(ugiTransport));
 +
 +    // The proxy needs to recognize that the requested principal isn't the same as the SASL principal and fail
 +    // Accumulo should let this through -- we need to rely on the proxy to dump me before talking to accumulo
 +    try {
 +      client.login(rootUser.getPrincipal(), Collections.<String,String> emptyMap());
 +    } finally {
 +      if (null != ugiTransport) {
 +        ugiTransport.close();
 +      }
 +    }
 +  }
 +
 +  private static class ThriftExceptionMatchesPattern extends TypeSafeMatcher<AccumuloSecurityException> {
 +    private String pattern;
 +
 +    public ThriftExceptionMatchesPattern(String pattern) {
 +      this.pattern = pattern;
 +    }
 +
 +    @Override
 +    protected boolean matchesSafely(AccumuloSecurityException item) {
 +      return item.isSetMsg() && item.msg.matches(pattern);
 +    }
 +
 +    @Override
 +    public void describeTo(Description description) {
 +      description.appendText("matches pattern ").appendValue(pattern);
 +    }
 +
 +    @Override
 +    protected void describeMismatchSafely(AccumuloSecurityException item, Description mismatchDescription) {
 +      mismatchDescription.appendText("does not match");
 +    }
 +  }
 +}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/a4afd1bf/test/src/main/java/org/apache/accumulo/test/functional/KerberosRenewalIT.java
----------------------------------------------------------------------
diff --cc test/src/main/java/org/apache/accumulo/test/functional/KerberosRenewalIT.java
index 0e60501,0000000..4774f69
mode 100644,000000..100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/KerberosRenewalIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/KerberosRenewalIT.java
@@@ -1,191 -1,0 +1,191 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one or more
 + * contributor license agreements.  See the NOTICE file distributed with
 + * this work for additional information regarding copyright ownership.
 + * The ASF licenses this file to You under the Apache License, Version 2.0
 + * (the "License"); you may not use this file except in compliance with
 + * the License.  You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +package org.apache.accumulo.test.functional;
 +
 +import static org.junit.Assert.assertEquals;
 +
 +import java.util.Map;
 +import java.util.Map.Entry;
 +
 +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.BatchWriter;
 +import org.apache.accumulo.core.client.BatchWriterConfig;
 +import org.apache.accumulo.core.client.Connector;
 +import org.apache.accumulo.core.client.Scanner;
 +import org.apache.accumulo.core.client.TableExistsException;
 +import org.apache.accumulo.core.client.TableNotFoundException;
 +import org.apache.accumulo.core.client.admin.CompactionConfig;
 +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.data.Key;
 +import org.apache.accumulo.core.data.Mutation;
 +import org.apache.accumulo.core.data.PartialKey;
 +import org.apache.accumulo.core.data.Value;
 +import org.apache.accumulo.core.security.Authorizations;
 +import org.apache.accumulo.harness.AccumuloITBase;
 +import org.apache.accumulo.harness.MiniClusterConfigurationCallback;
 +import org.apache.accumulo.harness.MiniClusterHarness;
 +import org.apache.accumulo.harness.TestingKdc;
 +import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
 +import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl;
- import org.apache.accumulo.test.categories.MiniClusterOnlyTest;
++import org.apache.accumulo.test.categories.MiniClusterOnlyTests;
 +import org.apache.hadoop.conf.Configuration;
 +import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
 +import org.apache.hadoop.minikdc.MiniKdc;
 +import org.apache.hadoop.security.UserGroupInformation;
 +import org.junit.After;
 +import org.junit.AfterClass;
 +import org.junit.Before;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +import org.junit.experimental.categories.Category;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +import com.google.common.collect.Iterables;
 +
 +/**
 + * MAC test which uses {@link MiniKdc} to simulate ta secure environment. Can be used as a sanity check for Kerberos/SASL testing.
 + */
- @Category(MiniClusterOnlyTest.class)
++@Category(MiniClusterOnlyTests.class)
 +public class KerberosRenewalIT extends AccumuloITBase {
 +  private static final Logger log = LoggerFactory.getLogger(KerberosRenewalIT.class);
 +
 +  private static TestingKdc kdc;
 +  private static String krbEnabledForITs = null;
 +  private static ClusterUser rootUser;
 +
 +  private static final long TICKET_LIFETIME = 6 * 60 * 1000; // Anything less seems to fail when generating the ticket
 +  private static final long TICKET_TEST_LIFETIME = 8 * 60 * 1000; // Run a test for 8 mins
 +  private static final long TEST_DURATION = 9 * 60 * 1000; // The test should finish within 9 mins
 +
 +  @BeforeClass
 +  public static void startKdc() throws Exception {
 +    // 30s renewal time window
 +    kdc = new TestingKdc(TestingKdc.computeKdcDir(), TestingKdc.computeKeytabDir(), TICKET_LIFETIME);
 +    kdc.start();
 +    krbEnabledForITs = System.getProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION);
 +    if (null == krbEnabledForITs || !Boolean.parseBoolean(krbEnabledForITs)) {
 +      System.setProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION, "true");
 +    }
 +    rootUser = kdc.getRootUser();
 +  }
 +
 +  @AfterClass
 +  public static void stopKdc() throws Exception {
 +    if (null != kdc) {
 +      kdc.stop();
 +    }
 +    if (null != krbEnabledForITs) {
 +      System.setProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION, krbEnabledForITs);
 +    }
 +  }
 +
 +  @Override
 +  public int defaultTimeoutSeconds() {
 +    return (int) TEST_DURATION / 1000;
 +  }
 +
 +  private MiniAccumuloClusterImpl mac;
 +
 +  @Before
 +  public void startMac() throws Exception {
 +    MiniClusterHarness harness = new MiniClusterHarness();
 +    mac = harness.create(this, new PasswordToken("unused"), kdc, new MiniClusterConfigurationCallback() {
 +
 +      @Override
 +      public void configureMiniCluster(MiniAccumuloConfigImpl cfg, Configuration coreSite) {
 +        Map<String,String> site = cfg.getSiteConfig();
 +        site.put(Property.INSTANCE_ZK_TIMEOUT.getKey(), "15s");
 +        // Reduce the period just to make sure we trigger renewal fast
 +        site.put(Property.GENERAL_KERBEROS_RENEWAL_PERIOD.getKey(), "5s");
 +        cfg.setSiteConfig(site);
 +      }
 +
 +    });
 +
 +    mac.getConfig().setNumTservers(1);
 +    mac.start();
 +    // Enabled kerberos auth
 +    Configuration conf = new Configuration(false);
 +    conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
 +    UserGroupInformation.setConfiguration(conf);
 +  }
 +
 +  @After
 +  public void stopMac() throws Exception {
 +    if (null != mac) {
 +      mac.stop();
 +    }
 +  }
 +
 +  // Intentially setting the Test annotation timeout. We do not want to scale the timeout.
 +  @Test(timeout = TEST_DURATION)
 +  public void testReadAndWriteThroughTicketLifetime() throws Exception {
 +    // Attempt to use Accumulo for a duration of time that exceeds the Kerberos ticket lifetime.
 +    // This is a functional test to verify that Accumulo services renew their ticket.
 +    // If the test doesn't finish on its own, this signifies that Accumulo services failed
 +    // and the test should fail. If Accumulo services renew their ticket, the test case
 +    // should exit gracefully on its own.
 +
 +    // Login as the "root" user
 +    UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
 +    log.info("Logged in as {}", rootUser.getPrincipal());
 +
 +    Connector conn = mac.getConnector(rootUser.getPrincipal(), new KerberosToken());
 +    log.info("Created connector as {}", rootUser.getPrincipal());
 +    assertEquals(rootUser.getPrincipal(), conn.whoami());
 +
 +    long duration = 0;
 +    long last = System.currentTimeMillis();
 +    // Make sure we have a couple renewals happen
 +    while (duration < TICKET_TEST_LIFETIME) {
 +      // Create a table, write a record, compact, read the record, drop the table.
 +      createReadWriteDrop(conn);
 +      // Wait a bit after
 +      Thread.sleep(5000);
 +
 +      // Update the duration
 +      long now = System.currentTimeMillis();
 +      duration += now - last;
 +      last = now;
 +    }
 +  }
 +
 +  /**
 +   * Creates a table, adds a record to it, and then compacts the table. A simple way to make sure that the system user exists (since the master does an RPC to
 +   * the tserver which will create the system user if it doesn't already exist).
 +   */
 +  private void createReadWriteDrop(Connector conn) throws TableNotFoundException, AccumuloSecurityException, AccumuloException, TableExistsException {
 +    final String table = testName.getMethodName() + "_table";
 +    conn.tableOperations().create(table);
 +    BatchWriter bw = conn.createBatchWriter(table, new BatchWriterConfig());
 +    Mutation m = new Mutation("a");
 +    m.put("b", "c", "d");
 +    bw.addMutation(m);
 +    bw.close();
 +    conn.tableOperations().compact(table, new CompactionConfig().setFlush(true).setWait(true));
 +    Scanner s = conn.createScanner(table, Authorizations.EMPTY);
 +    Entry<Key,Value> entry = Iterables.getOnlyElement(s);
 +    assertEquals("Did not find the expected key", 0, new Key("a", "b", "c").compareTo(entry.getKey(), PartialKey.ROW_COLFAM_COLQUAL));
 +    assertEquals("d", entry.getValue().toString());
 +    conn.tableOperations().delete(table);
 +  }
 +}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/a4afd1bf/test/src/main/java/org/apache/accumulo/test/functional/NativeMapIT.java
----------------------------------------------------------------------
diff --cc test/src/main/java/org/apache/accumulo/test/functional/NativeMapIT.java
index 2d594f5,0000000..ea3ba66
mode 100644,000000..100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/NativeMapIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/NativeMapIT.java
@@@ -1,613 -1,0 +1,616 @@@
 +/*
 + * 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.test.functional;
 +
 +import static java.nio.charset.StandardCharsets.UTF_8;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertNull;
 +import static org.junit.Assert.assertTrue;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.Iterator;
 +import java.util.Map.Entry;
 +import java.util.NoSuchElementException;
 +import java.util.Random;
 +import java.util.TreeMap;
 +
 +import org.apache.accumulo.core.Constants;
 +import org.apache.accumulo.core.data.Key;
 +import org.apache.accumulo.core.data.Value;
 +import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
 +import org.apache.accumulo.core.util.Pair;
++import org.apache.accumulo.test.categories.SunnyDayTests;
 +import org.apache.accumulo.tserver.NativeMap;
 +import org.apache.hadoop.io.Text;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
++import org.junit.experimental.categories.Category;
 +
++@Category(SunnyDayTests.class)
 +public class NativeMapIT {
 +
 +  private Key nk(int r) {
 +    return new Key(new Text(String.format("r%09d", r)));
 +  }
 +
 +  private Key nk(int r, int cf, int cq, int cv, int ts, boolean deleted) {
 +    Key k = new Key(new Text(String.format("r%09d", r)), new Text(String.format("cf%09d", cf)), new Text(String.format("cq%09d", cq)), new Text(String.format(
 +        "cv%09d", cv)), ts);
 +
 +    k.setDeleted(deleted);
 +
 +    return k;
 +  }
 +
 +  private Value nv(int v) {
 +    return new Value(String.format("r%09d", v).getBytes(UTF_8));
 +  }
 +
 +  public static File nativeMapLocation() {
 +    File projectDir = new File(System.getProperty("user.dir")).getParentFile();
 +    File nativeMapDir = new File(projectDir, "server/native/target/accumulo-native-" + Constants.VERSION + "/accumulo-native-" + Constants.VERSION);
 +    return nativeMapDir;
 +  }
 +
 +  @BeforeClass
 +  public static void setUp() {
 +    NativeMap.loadNativeLib(Collections.singletonList(nativeMapLocation()));
 +  }
 +
 +  private void verifyIterator(int start, int end, int valueOffset, Iterator<Entry<Key,Value>> iter) {
 +    for (int i = start; i <= end; i++) {
 +      assertTrue(iter.hasNext());
 +      Entry<Key,Value> entry = iter.next();
 +      assertEquals(nk(i), entry.getKey());
 +      assertEquals(nv(i + valueOffset), entry.getValue());
 +    }
 +
 +    assertFalse(iter.hasNext());
 +  }
 +
 +  private void insertAndVerify(NativeMap nm, int start, int end, int valueOffset) {
 +    for (int i = start; i <= end; i++) {
 +      nm.put(nk(i), nv(i + valueOffset));
 +    }
 +
 +    for (int i = start; i <= end; i++) {
 +      Value v = nm.get(nk(i));
 +      assertNotNull(v);
 +      assertEquals(nv(i + valueOffset), v);
 +
 +      Iterator<Entry<Key,Value>> iter2 = nm.iterator(nk(i));
 +      assertTrue(iter2.hasNext());
 +      Entry<Key,Value> entry = iter2.next();
 +      assertEquals(nk(i), entry.getKey());
 +      assertEquals(nv(i + valueOffset), entry.getValue());
 +    }
 +
 +    assertNull(nm.get(nk(start - 1)));
 +
 +    assertNull(nm.get(nk(end + 1)));
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +    verifyIterator(start, end, valueOffset, iter);
 +
 +    for (int i = start; i <= end; i++) {
 +      iter = nm.iterator(nk(i));
 +      verifyIterator(i, end, valueOffset, iter);
 +
 +      // lookup nonexistant key that falls after existing key
 +      iter = nm.iterator(nk(i, 1, 1, 1, 1, false));
 +      verifyIterator(i + 1, end, valueOffset, iter);
 +    }
 +
 +    assertEquals(end - start + 1, nm.size());
 +  }
 +
 +  private void insertAndVerifyExhaustive(NativeMap nm, int num, int run) {
 +    for (int i = 0; i < num; i++) {
 +      for (int j = 0; j < num; j++) {
 +        for (int k = 0; k < num; k++) {
 +          for (int l = 0; l < num; l++) {
 +            for (int ts = 0; ts < num; ts++) {
 +              Key key = nk(i, j, k, l, ts, true);
 +              Value value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + true + "_" + run).getBytes(UTF_8));
 +
 +              nm.put(key, value);
 +
 +              key = nk(i, j, k, l, ts, false);
 +              value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + false + "_" + run).getBytes(UTF_8));
 +
 +              nm.put(key, value);
 +            }
 +          }
 +        }
 +      }
 +    }
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +
 +    for (int i = 0; i < num; i++) {
 +      for (int j = 0; j < num; j++) {
 +        for (int k = 0; k < num; k++) {
 +          for (int l = 0; l < num; l++) {
 +            for (int ts = num - 1; ts >= 0; ts--) {
 +              Key key = nk(i, j, k, l, ts, true);
 +              Value value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + true + "_" + run).getBytes(UTF_8));
 +
 +              assertTrue(iter.hasNext());
 +              Entry<Key,Value> entry = iter.next();
 +              assertEquals(key, entry.getKey());
 +              assertEquals(value, entry.getValue());
 +
 +              key = nk(i, j, k, l, ts, false);
 +              value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + false + "_" + run).getBytes(UTF_8));
 +
 +              assertTrue(iter.hasNext());
 +              entry = iter.next();
 +              assertEquals(key, entry.getKey());
 +              assertEquals(value, entry.getValue());
 +            }
 +          }
 +        }
 +      }
 +    }
 +
 +    assertFalse(iter.hasNext());
 +
 +    for (int i = 0; i < num; i++) {
 +      for (int j = 0; j < num; j++) {
 +        for (int k = 0; k < num; k++) {
 +          for (int l = 0; l < num; l++) {
 +            for (int ts = 0; ts < num; ts++) {
 +              Key key = nk(i, j, k, l, ts, true);
 +              Value value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + true + "_" + run).getBytes(UTF_8));
 +
 +              assertEquals(value, nm.get(key));
 +
 +              Iterator<Entry<Key,Value>> iter2 = nm.iterator(key);
 +              assertTrue(iter2.hasNext());
 +              Entry<Key,Value> entry = iter2.next();
 +              assertEquals(key, entry.getKey());
 +              assertEquals(value, entry.getValue());
 +
 +              key = nk(i, j, k, l, ts, false);
 +              value = new Value((i + "_" + j + "_" + k + "_" + l + "_" + ts + "_" + false + "_" + run).getBytes(UTF_8));
 +
 +              assertEquals(value, nm.get(key));
 +
 +              Iterator<Entry<Key,Value>> iter3 = nm.iterator(key);
 +              assertTrue(iter3.hasNext());
 +              Entry<Key,Value> entry2 = iter3.next();
 +              assertEquals(key, entry2.getKey());
 +              assertEquals(value, entry2.getValue());
 +            }
 +          }
 +        }
 +      }
 +    }
 +
 +    assertEquals(num * num * num * num * num * 2, nm.size());
 +  }
 +
 +  @Test
 +  public void test1() {
 +    NativeMap nm = new NativeMap();
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +    assertFalse(iter.hasNext());
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void test2() {
 +    NativeMap nm = new NativeMap();
 +
 +    insertAndVerify(nm, 1, 10, 0);
 +    insertAndVerify(nm, 1, 10, 1);
 +    insertAndVerify(nm, 1, 10, 2);
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void test4() {
 +    NativeMap nm = new NativeMap();
 +
 +    insertAndVerifyExhaustive(nm, 3, 0);
 +    insertAndVerifyExhaustive(nm, 3, 1);
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void test5() {
 +    NativeMap nm = new NativeMap();
 +
 +    insertAndVerify(nm, 1, 10, 0);
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +    iter.next();
 +
 +    nm.delete();
 +
 +    try {
 +      nm.put(nk(1), nv(1));
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +    try {
 +      nm.get(nk(1));
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +    try {
 +      nm.iterator();
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +    try {
 +      nm.iterator(nk(1));
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +    try {
 +      nm.size();
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +    try {
 +      iter.next();
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +
 +  }
 +
 +  @Test
 +  public void test7() {
 +    NativeMap nm = new NativeMap();
 +
 +    insertAndVerify(nm, 1, 10, 0);
 +
 +    nm.delete();
 +
 +    try {
 +      nm.delete();
 +      assertTrue(false);
 +    } catch (IllegalStateException e) {
 +
 +    }
 +  }
 +
 +  @Test
 +  public void test8() {
 +    // test verifies that native map sorts keys sharing some common prefix properly
 +
 +    NativeMap nm = new NativeMap();
 +
 +    TreeMap<Key,Value> tm = new TreeMap<>();
 +
 +    tm.put(new Key(new Text("fo")), new Value(new byte[] {'0'}));
 +    tm.put(new Key(new Text("foo")), new Value(new byte[] {'1'}));
 +    tm.put(new Key(new Text("foo1")), new Value(new byte[] {'2'}));
 +    tm.put(new Key(new Text("foo2")), new Value(new byte[] {'3'}));
 +
 +    for (Entry<Key,Value> entry : tm.entrySet()) {
 +      nm.put(entry.getKey(), entry.getValue());
 +    }
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +
 +    for (Entry<Key,Value> entry : tm.entrySet()) {
 +      assertTrue(iter.hasNext());
 +      Entry<Key,Value> entry2 = iter.next();
 +
 +      assertEquals(entry.getKey(), entry2.getKey());
 +      assertEquals(entry.getValue(), entry2.getValue());
 +    }
 +
 +    assertFalse(iter.hasNext());
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void test9() {
 +    NativeMap nm = new NativeMap();
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +
 +    try {
 +      iter.next();
 +      assertTrue(false);
 +    } catch (NoSuchElementException e) {
 +
 +    }
 +
 +    insertAndVerify(nm, 1, 1, 0);
 +
 +    iter = nm.iterator();
 +    iter.next();
 +
 +    try {
 +      iter.next();
 +      assertTrue(false);
 +    } catch (NoSuchElementException e) {
 +
 +    }
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void test10() {
 +    int start = 1;
 +    int end = 10000;
 +
 +    NativeMap nm = new NativeMap();
 +    for (int i = start; i <= end; i++) {
 +      nm.put(nk(i), nv(i));
 +    }
 +
 +    long mem1 = nm.getMemoryUsed();
 +
 +    for (int i = start; i <= end; i++) {
 +      nm.put(nk(i), nv(i));
 +    }
 +
 +    long mem2 = nm.getMemoryUsed();
 +
 +    if (mem1 != mem2) {
 +      throw new RuntimeException("Memory changed after inserting duplicate data " + mem1 + " " + mem2);
 +    }
 +
 +    for (int i = start; i <= end; i++) {
 +      nm.put(nk(i), nv(i));
 +    }
 +
 +    long mem3 = nm.getMemoryUsed();
 +
 +    if (mem1 != mem3) {
 +      throw new RuntimeException("Memory changed after inserting duplicate data " + mem1 + " " + mem3);
 +    }
 +
 +    byte bigrow[] = new byte[1000000];
 +    byte bigvalue[] = new byte[bigrow.length];
 +
 +    for (int i = 0; i < bigrow.length; i++) {
 +      bigrow[i] = (byte) (0xff & (i % 256));
 +      bigvalue[i] = bigrow[i];
 +    }
 +
 +    nm.put(new Key(new Text(bigrow)), new Value(bigvalue));
 +
 +    long mem4 = nm.getMemoryUsed();
 +
 +    Value val = nm.get(new Key(new Text(bigrow)));
 +    if (val == null || !val.equals(new Value(bigvalue))) {
 +      throw new RuntimeException("Did not get expected big value");
 +    }
 +
 +    nm.put(new Key(new Text(bigrow)), new Value(bigvalue));
 +
 +    long mem5 = nm.getMemoryUsed();
 +
 +    if (mem4 != mem5) {
 +      throw new RuntimeException("Memory changed after inserting duplicate data " + mem4 + " " + mem5);
 +    }
 +
 +    val = nm.get(new Key(new Text(bigrow)));
 +    if (val == null || !val.equals(new Value(bigvalue))) {
 +      throw new RuntimeException("Did not get expected big value");
 +    }
 +
 +    nm.delete();
 +  }
 +
 +  // random length random field
 +  private static byte[] rlrf(Random r, int maxLen) {
 +    int len = r.nextInt(maxLen);
 +
 +    byte f[] = new byte[len];
 +    r.nextBytes(f);
 +
 +    return f;
 +  }
 +
 +  @Test
 +  public void test11() {
 +    NativeMap nm = new NativeMap();
 +
 +    // insert things with varying field sizes and value sizes
 +
 +    // generate random data
 +    Random r = new Random(75);
 +
 +    ArrayList<Pair<Key,Value>> testData = new ArrayList<>();
 +
 +    for (int i = 0; i < 100000; i++) {
 +
 +      Key k = new Key(rlrf(r, 97), rlrf(r, 13), rlrf(r, 31), rlrf(r, 11), (r.nextLong() & 0x7fffffffffffffffl), false, false);
 +      Value v = new Value(rlrf(r, 511));
 +
 +      testData.add(new Pair<>(k, v));
 +    }
 +
 +    // insert unsorted data
 +    for (Pair<Key,Value> pair : testData) {
 +      nm.put(pair.getFirst(), pair.getSecond());
 +    }
 +
 +    for (int i = 0; i < 2; i++) {
 +
 +      // sort data
 +      Collections.sort(testData, new Comparator<Pair<Key,Value>>() {
 +        @Override
 +        public int compare(Pair<Key,Value> o1, Pair<Key,Value> o2) {
 +          return o1.getFirst().compareTo(o2.getFirst());
 +        }
 +      });
 +
 +      // verify
 +      Iterator<Entry<Key,Value>> iter1 = nm.iterator();
 +      Iterator<Pair<Key,Value>> iter2 = testData.iterator();
 +
 +      while (iter1.hasNext() && iter2.hasNext()) {
 +        Entry<Key,Value> e = iter1.next();
 +        Pair<Key,Value> p = iter2.next();
 +
 +        if (!e.getKey().equals(p.getFirst()))
 +          throw new RuntimeException("Keys not equal");
 +
 +        if (!e.getValue().equals(p.getSecond()))
 +          throw new RuntimeException("Values not equal");
 +      }
 +
 +      if (iter1.hasNext())
 +        throw new RuntimeException("Not all of native map consumed");
 +
 +      if (iter2.hasNext())
 +        throw new RuntimeException("Not all of test data consumed");
 +
 +      System.out.println("test 11 nm mem " + nm.getMemoryUsed());
 +
 +      // insert data again w/ different value
 +      Collections.shuffle(testData, r);
 +      // insert unsorted data
 +      for (Pair<Key,Value> pair : testData) {
 +        pair.getSecond().set(rlrf(r, 511));
 +        nm.put(pair.getFirst(), pair.getSecond());
 +      }
 +    }
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void testBinary() {
 +    NativeMap nm = new NativeMap();
 +
 +    byte emptyBytes[] = new byte[0];
 +
 +    for (int i = 0; i < 256; i++) {
 +      for (int j = 0; j < 256; j++) {
 +        byte row[] = new byte[] {'r', (byte) (0xff & i), (byte) (0xff & j)};
 +        byte data[] = new byte[] {'v', (byte) (0xff & i), (byte) (0xff & j)};
 +
 +        Key k = new Key(row, emptyBytes, emptyBytes, emptyBytes, 1);
 +        Value v = new Value(data);
 +
 +        nm.put(k, v);
 +      }
 +    }
 +
 +    Iterator<Entry<Key,Value>> iter = nm.iterator();
 +    for (int i = 0; i < 256; i++) {
 +      for (int j = 0; j < 256; j++) {
 +        byte row[] = new byte[] {'r', (byte) (0xff & i), (byte) (0xff & j)};
 +        byte data[] = new byte[] {'v', (byte) (0xff & i), (byte) (0xff & j)};
 +
 +        Key k = new Key(row, emptyBytes, emptyBytes, emptyBytes, 1);
 +        Value v = new Value(data);
 +
 +        assertTrue(iter.hasNext());
 +        Entry<Key,Value> entry = iter.next();
 +
 +        assertEquals(k, entry.getKey());
 +        assertEquals(v, entry.getValue());
 +
 +      }
 +    }
 +
 +    assertFalse(iter.hasNext());
 +
 +    for (int i = 0; i < 256; i++) {
 +      for (int j = 0; j < 256; j++) {
 +        byte row[] = new byte[] {'r', (byte) (0xff & i), (byte) (0xff & j)};
 +        byte data[] = new byte[] {'v', (byte) (0xff & i), (byte) (0xff & j)};
 +
 +        Key k = new Key(row, emptyBytes, emptyBytes, emptyBytes, 1);
 +        Value v = new Value(data);
 +
 +        Value v2 = nm.get(k);
 +
 +        assertEquals(v, v2);
 +      }
 +    }
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void testEmpty() {
 +    NativeMap nm = new NativeMap();
 +
 +    assertTrue(nm.size() == 0);
 +    assertTrue(nm.getMemoryUsed() == 0);
 +
 +    nm.delete();
 +  }
 +
 +  @Test
 +  public void testConcurrentIter() throws IOException {
 +    NativeMap nm = new NativeMap();
 +
 +    nm.put(nk(0), nv(0));
 +    nm.put(nk(1), nv(1));
 +    nm.put(nk(3), nv(3));
 +
 +    SortedKeyValueIterator<Key,Value> iter = nm.skvIterator();
 +
 +    // modify map after iter created
 +    nm.put(nk(2), nv(2));
 +
 +    assertTrue(iter.hasTop());
 +    assertEquals(iter.getTopKey(), nk(0));
 +    iter.next();
 +
 +    assertTrue(iter.hasTop());
 +    assertEquals(iter.getTopKey(), nk(1));
 +    iter.next();
 +
 +    assertTrue(iter.hasTop());
 +    assertEquals(iter.getTopKey(), nk(2));
 +    iter.next();
 +
 +    assertTrue(iter.hasTop());
 +    assertEquals(iter.getTopKey(), nk(3));
 +    iter.next();
 +
 +    assertFalse(iter.hasTop());
 +
 +    nm.delete();
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/a4afd1bf/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java
----------------------------------------------------------------------
diff --cc test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java
index c7fc709,0000000..7bf52ee
mode 100644,000000..100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java
@@@ -1,710 -1,0 +1,710 @@@
 +/*
 + * 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.test.functional;
 +
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertTrue;
 +
 +import java.io.IOException;
 +import java.util.Arrays;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +
 +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.BatchWriter;
 +import org.apache.accumulo.core.client.BatchWriterConfig;
 +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.MutationsRejectedException;
 +import org.apache.accumulo.core.client.Scanner;
 +import org.apache.accumulo.core.client.TableExistsException;
 +import org.apache.accumulo.core.client.TableNotFoundException;
 +import org.apache.accumulo.core.client.security.SecurityErrorCode;
 +import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
 +import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 +import org.apache.accumulo.core.conf.Property;
 +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.core.metadata.MetadataTable;
 +import org.apache.accumulo.core.security.Authorizations;
 +import org.apache.accumulo.core.security.SystemPermission;
 +import org.apache.accumulo.core.security.TablePermission;
 +import org.apache.accumulo.harness.AccumuloClusterHarness;
- import org.apache.accumulo.test.categories.MiniClusterOnlyTest;
++import org.apache.accumulo.test.categories.MiniClusterOnlyTests;
 +import org.apache.hadoop.io.Text;
 +import org.junit.Assume;
 +import org.junit.Before;
 +import org.junit.Test;
 +import org.junit.experimental.categories.Category;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +
 +// This test verifies the default permissions so a clean instance must be used. A shared instance might
 +// not be representative of a fresh installation.
- @Category(MiniClusterOnlyTest.class)
++@Category(MiniClusterOnlyTests.class)
 +public class PermissionsIT extends AccumuloClusterHarness {
 +  private static final Logger log = LoggerFactory.getLogger(PermissionsIT.class);
 +
 +  @Override
 +  public int defaultTimeoutSeconds() {
 +    return 60;
 +  }
 +
 +  @Before
 +  public void limitToMini() throws Exception {
 +    Assume.assumeTrue(ClusterType.MINI == getClusterType());
 +    Connector c = getConnector();
 +    Set<String> users = c.securityOperations().listLocalUsers();
 +    ClusterUser user = getUser(0);
 +    if (users.contains(user.getPrincipal())) {
 +      c.securityOperations().dropLocalUser(user.getPrincipal());
 +    }
 +  }
 +
 +  private void loginAs(ClusterUser user) throws IOException {
 +    // Force a re-login as the provided user
 +    user.getToken();
 +  }
 +
 +  @Test
 +  public void systemPermissionsTest() throws Exception {
 +    ClusterUser testUser = getUser(0), rootUser = getAdminUser();
 +
 +    // verify that the test is being run by root
 +    Connector c = getConnector();
 +    verifyHasOnlyTheseSystemPermissions(c, c.whoami(), SystemPermission.values());
 +
 +    // create the test user
 +    String principal = testUser.getPrincipal();
 +    AuthenticationToken token = testUser.getToken();
 +    PasswordToken passwordToken = null;
 +    if (token instanceof PasswordToken) {
 +      passwordToken = (PasswordToken) token;
 +    }
 +    loginAs(rootUser);
 +    c.securityOperations().createLocalUser(principal, passwordToken);
 +    loginAs(testUser);
 +    Connector test_user_conn = c.getInstance().getConnector(principal, token);
 +    loginAs(rootUser);
 +    verifyHasNoSystemPermissions(c, principal, SystemPermission.values());
 +
 +    // test each permission
 +    for (SystemPermission perm : SystemPermission.values()) {
 +      log.debug("Verifying the " + perm + " permission");
 +
 +      // test permission before and after granting it
 +      String tableNamePrefix = getUniqueNames(1)[0];
 +      testMissingSystemPermission(tableNamePrefix, c, rootUser, test_user_conn, testUser, perm);
 +      loginAs(rootUser);
 +      c.securityOperations().grantSystemPermission(principal, perm);
 +      verifyHasOnlyTheseSystemPermissions(c, principal, perm);
 +      testGrantedSystemPermission(tableNamePrefix, c, rootUser, test_user_conn, testUser, perm);
 +      loginAs(rootUser);
 +      c.securityOperations().revokeSystemPermission(principal, perm);
 +      verifyHasNoSystemPermissions(c, principal, perm);
 +    }
 +  }
 +
 +  static Map<String,String> map(Iterable<Entry<String,String>> i) {
 +    Map<String,String> result = new HashMap<>();
 +    for (Entry<String,String> e : i) {
 +      result.put(e.getKey(), e.getValue());
 +    }
 +    return result;
 +  }
 +
 +  private void testMissingSystemPermission(String tableNamePrefix, Connector root_conn, ClusterUser rootUser, Connector test_user_conn, ClusterUser testUser,
 +      SystemPermission perm) throws Exception {
 +    String tableName, user, password = "password", namespace;
 +    boolean passwordBased = testUser.getPassword() != null;
 +    log.debug("Confirming that the lack of the " + perm + " permission properly restricts the user");
 +
 +    // test permission prior to granting it
 +    switch (perm) {
 +      case CREATE_TABLE:
 +        tableName = tableNamePrefix + "__CREATE_TABLE_WITHOUT_PERM_TEST__";
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.tableOperations().create(tableName);
 +          throw new IllegalStateException("Should NOT be able to create a table");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || root_conn.tableOperations().list().contains(tableName))
 +            throw e;
 +        }
 +        break;
 +      case DROP_TABLE:
 +        tableName = tableNamePrefix + "__DROP_TABLE_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.tableOperations().create(tableName);
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.tableOperations().delete(tableName);
 +          throw new IllegalStateException("Should NOT be able to delete a table");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.tableOperations().list().contains(tableName))
 +            throw e;
 +        }
 +        break;
 +      case ALTER_TABLE:
 +        tableName = tableNamePrefix + "__ALTER_TABLE_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.tableOperations().create(tableName);
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.tableOperations().setProperty(tableName, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +          throw new IllegalStateException("Should NOT be able to set a table property");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED
 +              || map(root_conn.tableOperations().getProperties(tableName)).get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +            throw e;
 +        }
 +        loginAs(rootUser);
 +        root_conn.tableOperations().setProperty(tableName, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.tableOperations().removeProperty(tableName, Property.TABLE_BLOOM_ERRORRATE.getKey());
 +          throw new IllegalStateException("Should NOT be able to remove a table property");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED
 +              || !map(root_conn.tableOperations().getProperties(tableName)).get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +            throw e;
 +        }
 +        String table2 = tableName + "2";
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.tableOperations().rename(tableName, table2);
 +          throw new IllegalStateException("Should NOT be able to rename a table");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.tableOperations().list().contains(tableName)
 +              || root_conn.tableOperations().list().contains(table2))
 +            throw e;
 +        }
 +        break;
 +      case CREATE_USER:
 +        user = "__CREATE_USER_WITHOUT_PERM_TEST__";
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +          throw new IllegalStateException("Should NOT be able to create a user");
 +        } catch (AccumuloSecurityException e) {
 +          AuthenticationToken userToken = testUser.getToken();
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED
 +              || (userToken instanceof PasswordToken && root_conn.securityOperations().authenticateUser(user, userToken)))
 +            throw e;
 +        }
 +        break;
 +      case DROP_USER:
 +        user = "__DROP_USER_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.securityOperations().dropLocalUser(user);
 +          throw new IllegalStateException("Should NOT be able to delete a user");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.securityOperations().listLocalUsers().contains(user)) {
 +            log.info("Failed to authenticate as " + user);
 +            throw e;
 +          }
 +        }
 +        break;
 +      case ALTER_USER:
 +        user = "__ALTER_USER_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.securityOperations().changeUserAuthorizations(user, new Authorizations("A", "B"));
 +          throw new IllegalStateException("Should NOT be able to alter a user");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.securityOperations().getUserAuthorizations(user).isEmpty())
 +            throw e;
 +        }
 +        break;
 +      case SYSTEM:
 +        // test for system permission would go here
 +        break;
 +      case CREATE_NAMESPACE:
 +        namespace = "__CREATE_NAMESPACE_WITHOUT_PERM_TEST__";
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.namespaceOperations().create(namespace);
 +          throw new IllegalStateException("Should NOT be able to create a namespace");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || root_conn.namespaceOperations().list().contains(namespace))
 +            throw e;
 +        }
 +        break;
 +      case DROP_NAMESPACE:
 +        namespace = "__DROP_NAMESPACE_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.namespaceOperations().create(namespace);
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.namespaceOperations().delete(namespace);
 +          throw new IllegalStateException("Should NOT be able to delete a namespace");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.namespaceOperations().list().contains(namespace))
 +            throw e;
 +        }
 +        break;
 +      case ALTER_NAMESPACE:
 +        namespace = "__ALTER_NAMESPACE_WITHOUT_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.namespaceOperations().create(namespace);
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.namespaceOperations().setProperty(namespace, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +          throw new IllegalStateException("Should NOT be able to set a namespace property");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED
 +              || map(root_conn.namespaceOperations().getProperties(namespace)).get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +            throw e;
 +        }
 +        loginAs(rootUser);
 +        root_conn.namespaceOperations().setProperty(namespace, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.namespaceOperations().removeProperty(namespace, Property.TABLE_BLOOM_ERRORRATE.getKey());
 +          throw new IllegalStateException("Should NOT be able to remove a namespace property");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED
 +              || !map(root_conn.namespaceOperations().getProperties(namespace)).get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +            throw e;
 +        }
 +        String namespace2 = namespace + "2";
 +        try {
 +          loginAs(testUser);
 +          test_user_conn.namespaceOperations().rename(namespace, namespace2);
 +          throw new IllegalStateException("Should NOT be able to rename a namespace");
 +        } catch (AccumuloSecurityException e) {
 +          loginAs(rootUser);
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED || !root_conn.namespaceOperations().list().contains(namespace)
 +              || root_conn.namespaceOperations().list().contains(namespace2))
 +            throw e;
 +        }
 +        break;
 +      case OBTAIN_DELEGATION_TOKEN:
 +        ClientConfiguration clientConf = cluster.getClientConfig();
 +        if (clientConf.getBoolean(ClientProperty.INSTANCE_RPC_SASL_ENABLED.getKey(), false)) {
 +          // TODO Try to obtain a delegation token without the permission
 +        }
 +        break;
 +      case GRANT:
 +        loginAs(testUser);
 +        try {
 +          test_user_conn.securityOperations().grantSystemPermission(testUser.getPrincipal(), SystemPermission.GRANT);
 +          throw new IllegalStateException("Should NOT be able to grant System.GRANT to yourself");
 +        } catch (AccumuloSecurityException e) {
 +          // Expected
 +          loginAs(rootUser);
 +          assertFalse(root_conn.securityOperations().hasSystemPermission(testUser.getPrincipal(), SystemPermission.GRANT));
 +        }
 +        break;
 +      default:
 +        throw new IllegalArgumentException("Unrecognized System Permission: " + perm);
 +    }
 +  }
 +
 +  private void testGrantedSystemPermission(String tableNamePrefix, Connector root_conn, ClusterUser rootUser, Connector test_user_conn, ClusterUser testUser,
 +      SystemPermission perm) throws Exception {
 +    String tableName, user, password = "password", namespace;
 +    boolean passwordBased = testUser.getPassword() != null;
 +    log.debug("Confirming that the presence of the " + perm + " permission properly permits the user");
 +
 +    // test permission after granting it
 +    switch (perm) {
 +      case CREATE_TABLE:
 +        tableName = tableNamePrefix + "__CREATE_TABLE_WITH_PERM_TEST__";
 +        loginAs(testUser);
 +        test_user_conn.tableOperations().create(tableName);
 +        loginAs(rootUser);
 +        if (!root_conn.tableOperations().list().contains(tableName))
 +          throw new IllegalStateException("Should be able to create a table");
 +        break;
 +      case DROP_TABLE:
 +        tableName = tableNamePrefix + "__DROP_TABLE_WITH_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.tableOperations().create(tableName);
 +        loginAs(testUser);
 +        test_user_conn.tableOperations().delete(tableName);
 +        loginAs(rootUser);
 +        if (root_conn.tableOperations().list().contains(tableName))
 +          throw new IllegalStateException("Should be able to delete a table");
 +        break;
 +      case ALTER_TABLE:
 +        tableName = tableNamePrefix + "__ALTER_TABLE_WITH_PERM_TEST__";
 +        String table2 = tableName + "2";
 +        loginAs(rootUser);
 +        root_conn.tableOperations().create(tableName);
 +        loginAs(testUser);
 +        test_user_conn.tableOperations().setProperty(tableName, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +        loginAs(rootUser);
 +        Map<String,String> properties = map(root_conn.tableOperations().getProperties(tableName));
 +        if (!properties.get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +          throw new IllegalStateException("Should be able to set a table property");
 +        loginAs(testUser);
 +        test_user_conn.tableOperations().removeProperty(tableName, Property.TABLE_BLOOM_ERRORRATE.getKey());
 +        loginAs(rootUser);
 +        properties = map(root_conn.tableOperations().getProperties(tableName));
 +        if (properties.get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +          throw new IllegalStateException("Should be able to remove a table property");
 +        loginAs(testUser);
 +        test_user_conn.tableOperations().rename(tableName, table2);
 +        loginAs(rootUser);
 +        if (root_conn.tableOperations().list().contains(tableName) || !root_conn.tableOperations().list().contains(table2))
 +          throw new IllegalStateException("Should be able to rename a table");
 +        break;
 +      case CREATE_USER:
 +        user = "__CREATE_USER_WITH_PERM_TEST__";
 +        loginAs(testUser);
 +        test_user_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +        loginAs(rootUser);
 +        if (passwordBased && !root_conn.securityOperations().authenticateUser(user, new PasswordToken(password)))
 +          throw new IllegalStateException("Should be able to create a user");
 +        break;
 +      case DROP_USER:
 +        user = "__DROP_USER_WITH_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +        loginAs(testUser);
 +        test_user_conn.securityOperations().dropLocalUser(user);
 +        loginAs(rootUser);
 +        if (passwordBased && root_conn.securityOperations().authenticateUser(user, new PasswordToken(password)))
 +          throw new IllegalStateException("Should be able to delete a user");
 +        break;
 +      case ALTER_USER:
 +        user = "__ALTER_USER_WITH_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.securityOperations().createLocalUser(user, (passwordBased ? new PasswordToken(password) : null));
 +        loginAs(testUser);
 +        test_user_conn.securityOperations().changeUserAuthorizations(user, new Authorizations("A", "B"));
 +        loginAs(rootUser);
 +        if (root_conn.securityOperations().getUserAuthorizations(user).isEmpty())
 +          throw new IllegalStateException("Should be able to alter a user");
 +        break;
 +      case SYSTEM:
 +        // test for system permission would go here
 +        break;
 +      case CREATE_NAMESPACE:
 +        namespace = "__CREATE_NAMESPACE_WITH_PERM_TEST__";
 +        loginAs(testUser);
 +        test_user_conn.namespaceOperations().create(namespace);
 +        loginAs(rootUser);
 +        if (!root_conn.namespaceOperations().list().contains(namespace))
 +          throw new IllegalStateException("Should be able to create a namespace");
 +        break;
 +      case DROP_NAMESPACE:
 +        namespace = "__DROP_NAMESPACE_WITH_PERM_TEST__";
 +        loginAs(rootUser);
 +        root_conn.namespaceOperations().create(namespace);
 +        loginAs(testUser);
 +        test_user_conn.namespaceOperations().delete(namespace);
 +        loginAs(rootUser);
 +        if (root_conn.namespaceOperations().list().contains(namespace))
 +          throw new IllegalStateException("Should be able to delete a namespace");
 +        break;
 +      case ALTER_NAMESPACE:
 +        namespace = "__ALTER_NAMESPACE_WITH_PERM_TEST__";
 +        String namespace2 = namespace + "2";
 +        loginAs(rootUser);
 +        root_conn.namespaceOperations().create(namespace);
 +        loginAs(testUser);
 +        test_user_conn.namespaceOperations().setProperty(namespace, Property.TABLE_BLOOM_ERRORRATE.getKey(), "003.14159%");
 +        loginAs(rootUser);
 +        Map<String,String> propies = map(root_conn.namespaceOperations().getProperties(namespace));
 +        if (!propies.get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +          throw new IllegalStateException("Should be able to set a table property");
 +        loginAs(testUser);
 +        test_user_conn.namespaceOperations().removeProperty(namespace, Property.TABLE_BLOOM_ERRORRATE.getKey());
 +        loginAs(rootUser);
 +        propies = map(root_conn.namespaceOperations().getProperties(namespace));
 +        if (propies.get(Property.TABLE_BLOOM_ERRORRATE.getKey()).equals("003.14159%"))
 +          throw new IllegalStateException("Should be able to remove a table property");
 +        loginAs(testUser);
 +        test_user_conn.namespaceOperations().rename(namespace, namespace2);
 +        loginAs(rootUser);
 +        if (root_conn.namespaceOperations().list().contains(namespace) || !root_conn.namespaceOperations().list().contains(namespace2))
 +          throw new IllegalStateException("Should be able to rename a table");
 +        break;
 +      case OBTAIN_DELEGATION_TOKEN:
 +        ClientConfiguration clientConf = cluster.getClientConfig();
 +        if (clientConf.getBoolean(ClientProperty.INSTANCE_RPC_SASL_ENABLED.getKey(), false)) {
 +          // TODO Try to obtain a delegation token with the permission
 +        }
 +        break;
 +      case GRANT:
 +        loginAs(rootUser);
 +        root_conn.securityOperations().grantSystemPermission(testUser.getPrincipal(), SystemPermission.GRANT);
 +        loginAs(testUser);
 +        test_user_conn.securityOperations().grantSystemPermission(testUser.getPrincipal(), SystemPermission.CREATE_TABLE);
 +        loginAs(rootUser);
 +        assertTrue("Test user should have CREATE_TABLE",
 +            root_conn.securityOperations().hasSystemPermission(testUser.getPrincipal(), SystemPermission.CREATE_TABLE));
 +        assertTrue("Test user should have GRANT", root_conn.securityOperations().hasSystemPermission(testUser.getPrincipal(), SystemPermission.GRANT));
 +        root_conn.securityOperations().revokeSystemPermission(testUser.getPrincipal(), SystemPermission.CREATE_TABLE);
 +        break;
 +      default:
 +        throw new IllegalArgumentException("Unrecognized System Permission: " + perm);
 +    }
 +  }
 +
 +  private void verifyHasOnlyTheseSystemPermissions(Connector root_conn, String user, SystemPermission... perms) throws AccumuloException,
 +      AccumuloSecurityException {
 +    List<SystemPermission> permList = Arrays.asList(perms);
 +    for (SystemPermission p : SystemPermission.values()) {
 +      if (permList.contains(p)) {
 +        // should have these
 +        if (!root_conn.securityOperations().hasSystemPermission(user, p))
 +          throw new IllegalStateException(user + " SHOULD have system permission " + p);
 +      } else {
 +        // should not have these
 +        if (root_conn.securityOperations().hasSystemPermission(user, p))
 +          throw new IllegalStateException(user + " SHOULD NOT have system permission " + p);
 +      }
 +    }
 +  }
 +
 +  private void verifyHasNoSystemPermissions(Connector root_conn, String user, SystemPermission... perms) throws AccumuloException, AccumuloSecurityException {
 +    for (SystemPermission p : perms)
 +      if (root_conn.securityOperations().hasSystemPermission(user, p))
 +        throw new IllegalStateException(user + " SHOULD NOT have system permission " + p);
 +  }
 +
 +  @Test
 +  public void tablePermissionTest() throws Exception {
 +    // create the test user
 +    ClusterUser testUser = getUser(0), rootUser = getAdminUser();
 +
 +    String principal = testUser.getPrincipal();
 +    AuthenticationToken token = testUser.getToken();
 +    PasswordToken passwordToken = null;
 +    if (token instanceof PasswordToken) {
 +      passwordToken = (PasswordToken) token;
 +    }
 +    loginAs(rootUser);
 +    Connector c = getConnector();
 +    c.securityOperations().createLocalUser(principal, passwordToken);
 +    loginAs(testUser);
 +    Connector test_user_conn = c.getInstance().getConnector(principal, token);
 +
 +    // check for read-only access to metadata table
 +    loginAs(rootUser);
 +    verifyHasOnlyTheseTablePermissions(c, c.whoami(), MetadataTable.NAME, TablePermission.READ, TablePermission.ALTER_TABLE);
 +    verifyHasOnlyTheseTablePermissions(c, principal, MetadataTable.NAME, TablePermission.READ);
 +    String tableName = getUniqueNames(1)[0] + "__TABLE_PERMISSION_TEST__";
 +
 +    // test each permission
 +    for (TablePermission perm : TablePermission.values()) {
 +      log.debug("Verifying the " + perm + " permission");
 +
 +      // test permission before and after granting it
 +      createTestTable(c, principal, tableName);
 +      loginAs(testUser);
 +      testMissingTablePermission(test_user_conn, testUser, perm, tableName);
 +      loginAs(rootUser);
 +      c.securityOperations().grantTablePermission(principal, tableName, perm);
 +      verifyHasOnlyTheseTablePermissions(c, principal, tableName, perm);
 +      loginAs(testUser);
 +      testGrantedTablePermission(test_user_conn, testUser, perm, tableName);
 +
 +      loginAs(rootUser);
 +      createTestTable(c, principal, tableName);
 +      c.securityOperations().revokeTablePermission(principal, tableName, perm);
 +      verifyHasNoTablePermissions(c, principal, tableName, perm);
 +    }
 +  }
 +
 +  private void createTestTable(Connector c, String testUser, String tableName) throws Exception, MutationsRejectedException {
 +    if (!c.tableOperations().exists(tableName)) {
 +      // create the test table
 +      c.tableOperations().create(tableName);
 +      // put in some initial data
 +      BatchWriter writer = c.createBatchWriter(tableName, new BatchWriterConfig());
 +      Mutation m = new Mutation(new Text("row"));
 +      m.put(new Text("cf"), new Text("cq"), new Value("val".getBytes()));
 +      writer.addMutation(m);
 +      writer.close();
 +
 +      // verify proper permissions for creator and test user
 +      verifyHasOnlyTheseTablePermissions(c, c.whoami(), tableName, TablePermission.values());
 +      verifyHasNoTablePermissions(c, testUser, tableName, TablePermission.values());
 +
 +    }
 +  }
 +
 +  private void testMissingTablePermission(Connector test_user_conn, ClusterUser testUser, TablePermission perm, String tableName) throws Exception {
 +    Scanner scanner;
 +    BatchWriter writer;
 +    Mutation m;
 +    log.debug("Confirming that the lack of the " + perm + " permission properly restricts the user");
 +
 +    // test permission prior to granting it
 +    switch (perm) {
 +      case READ:
 +        try {
 +          scanner = test_user_conn.createScanner(tableName, Authorizations.EMPTY);
 +          int i = 0;
 +          for (Entry<Key,Value> entry : scanner)
 +            i += 1 + entry.getKey().getRowData().length();
 +          if (i != 0)
 +            throw new IllegalStateException("Should NOT be able to read from the table");
 +        } catch (RuntimeException e) {
 +          AccumuloSecurityException se = (AccumuloSecurityException) e.getCause();
 +          if (se.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED)
 +            throw se;
 +        }
 +        break;
 +      case WRITE:
 +        try {
 +          writer = test_user_conn.createBatchWriter(tableName, new BatchWriterConfig());
 +          m = new Mutation(new Text("row"));
 +          m.put(new Text("a"), new Text("b"), new Value("c".getBytes()));
 +          writer.addMutation(m);
 +          try {
 +            writer.close();
 +          } catch (MutationsRejectedException e1) {
 +            if (e1.getSecurityErrorCodes().size() > 0)
 +              throw new AccumuloSecurityException(test_user_conn.whoami(), org.apache.accumulo.core.client.impl.thrift.SecurityErrorCode.PERMISSION_DENIED, e1);
 +          }
 +          throw new IllegalStateException("Should NOT be able to write to a table");
 +        } catch (AccumuloSecurityException e) {
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED)
 +            throw e;
 +        }
 +        break;
 +      case BULK_IMPORT:
 +        // test for bulk import permission would go here
 +        break;
 +      case ALTER_TABLE:
 +        Map<String,Set<Text>> groups = new HashMap<>();
 +        groups.put("tgroup", new HashSet<>(Arrays.asList(new Text("t1"), new Text("t2"))));
 +        try {
 +          test_user_conn.tableOperations().setLocalityGroups(tableName, groups);
 +          throw new IllegalStateException("User should not be able to set locality groups");
 +        } catch (AccumuloSecurityException e) {
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED)
 +            throw e;
 +        }
 +        break;
 +      case DROP_TABLE:
 +        try {
 +          test_user_conn.tableOperations().delete(tableName);
 +          throw new IllegalStateException("User should not be able delete the table");
 +        } catch (AccumuloSecurityException e) {
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED)
 +            throw e;
 +        }
 +        break;
 +      case GRANT:
 +        try {
 +          test_user_conn.securityOperations().grantTablePermission(getAdminPrincipal(), tableName, TablePermission.GRANT);
 +          throw new IllegalStateException("User should not be able grant permissions");
 +        } catch (AccumuloSecurityException e) {
 +          if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED)
 +            throw e;
 +        }
 +        break;
 +      default:
 +        throw new IllegalArgumentException("Unrecognized table Permission: " + perm);
 +    }
 +  }
 +
 +  private void testGrantedTablePermission(Connector test_user_conn, ClusterUser normalUser, TablePermission perm, String tableName) throws AccumuloException,
 +      TableExistsException, AccumuloSecurityException, TableNotFoundException, MutationsRejectedException {
 +    Scanner scanner;
 +    BatchWriter writer;
 +    Mutation m;
 +    log.debug("Confirming that the presence of the " + perm + " permission properly permits the user");
 +
 +    // test permission after granting it
 +    switch (perm) {
 +      case READ:
 +        scanner = test_user_conn.createScanner(tableName, Authorizations.EMPTY);
 +        Iterator<Entry<Key,Value>> iter = scanner.iterator();
 +        while (iter.hasNext())
 +          iter.next();
 +        break;
 +      case WRITE:
 +        writer = test_user_conn.createBatchWriter(tableName, new BatchWriterConfig());
 +        m = new Mutation(new Text("row"));
 +        m.put(new Text("a"), new Text("b"), new Value("c".getBytes()));
 +        writer.addMutation(m);
 +        writer.close();
 +        break;
 +      case BULK_IMPORT:
 +        // test for bulk import permission would go here
 +        break;
 +      case ALTER_TABLE:
 +        Map<String,Set<Text>> groups = new HashMap<>();
 +        groups.put("tgroup", new HashSet<>(Arrays.asList(new Text("t1"), new Text("t2"))));
 +        break;
 +      case DROP_TABLE:
 +        test_user_conn.tableOperations().delete(tableName);
 +        break;
 +      case GRANT:
 +        test_user_conn.securityOperations().grantTablePermission(getAdminPrincipal(), tableName, TablePermission.GRANT);
 +        break;
 +      default:
 +        throw new IllegalArgumentException("Unrecognized table Permission: " + perm);
 +    }
 +  }
 +
 +  private void verifyHasOnlyTheseTablePermissions(Connector root_conn, String user, String table, TablePermission... perms) throws AccumuloException,
 +      AccumuloSecurityException {
 +    List<TablePermission> permList = Arrays.asList(perms);
 +    for (TablePermission p : TablePermission.values()) {
 +      if (permList.contains(p)) {
 +        // should have these
 +        if (!root_conn.securityOperations().hasTablePermission(user, table, p))
 +          throw new IllegalStateException(user + " SHOULD have table permission " + p + " for table " + table);
 +      } else {
 +        // should not have these
 +        if (root_conn.securityOperations().hasTablePermission(user, table, p))
 +          throw new IllegalStateException(user + " SHOULD NOT have table permission " + p + " for table " + table);
 +      }
 +    }
 +  }
 +
 +  private void verifyHasNoTablePermissions(Connector root_conn, String user, String table, TablePermission... perms) throws AccumuloException,
 +      AccumuloSecurityException {
 +    for (TablePermission p : perms)
 +      if (root_conn.securityOperations().hasTablePermission(user, table, p))
 +        throw new IllegalStateException(user + " SHOULD NOT have table permission " + p + " for table " + table);
 +  }
 +}


Mime
View raw message