You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by ct...@apache.org on 2013/10/26 02:36:35 UTC

[5/6] ACCUMULO-1599 Use target directory more

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e95dee9e/core/src/test/resources/crypto-on-accumulo-site.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/crypto-on-accumulo-site.xml b/core/src/test/resources/crypto-on-accumulo-site.xml
deleted file mode 100644
index 9dc4aac..0000000
--- a/core/src/test/resources/crypto-on-accumulo-site.xml
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  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.
--->
-<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
-
-<configuration>
-	<!--
-  Put your site-specific accumulo configurations here.
-
-  The available configuration values along with their defaults
-  are documented in docs/config.html
-
-  Unless you are simply testing at your workstation, you will most 
-  definitely need to change the three entries below.
-	-->
-
-    <property>
-      <name>instance.zookeeper.host</name>
-      <value>localhost:2181</value>
-      <description>comma separated list of zookeeper servers</description>
-    </property>
-
-    <property>
-      <name>logger.dir.walog</name>
-      <value>walogs</value>
-      <description>The directory used to store write-ahead logs on the local filesystem. It is possible to specify a comma-separated list of directories.</description>
-    </property>
-    
-    <property>
-      <name>instance.secret</name>
-      <value>DEFAULT</value>
-      <description>A secret unique to a given instance that all servers must know in order to communicate with one another. 
-                   Change it before initialization. To change it later use ./bin/accumulo org.apache.accumulo.server.util.ChangeSecret [oldpasswd] [newpasswd], 
-                   and then update this file.
-      </description>
-    </property>
-
-    <property>
-      <name>tserver.memory.maps.max</name>
-      <value>80M</value>
-    </property>
-    
-    <property>
-      <name>tserver.cache.data.size</name>
-      <value>7M</value>
-    </property>
-    
-    <property>
-      <name>tserver.cache.index.size</name>
-      <value>20M</value>
-    </property>
-    
-    <property>
-      <name>trace.password</name>
-      <!-- 
-        change this to the root user's password, and/or change the user below 
-       -->
-      <value>password</value>
-    </property>
-    
-    <property>
-      <name>trace.user</name>
-      <value>root</value>
-    </property>
-    
-    <property>
-      <name>tserver.sort.buffer.size</name>
-      <value>50M</value>
-    </property>
-    
-    <property>
-      <name>tserver.walog.max.size</name>
-      <value>100M</value>
-    </property>
-
-    <property>
-      <name>general.classpaths</name>
-      <value>
-    $ACCUMULO_HOME/server/target/classes/,
-    $ACCUMULO_HOME/core/target/classes/,
-    $ACCUMULO_HOME/start/target/classes/,
-    $ACCUMULO_HOME/fate/target/classes/,
-    $ACCUMULO_HOME/proxy/target/classes/,
-    $ACCUMULO_HOME/examples/target/classes/,
-	$ACCUMULO_HOME/lib/[^.].$ACCUMULO_VERSION.jar,
-	$ACCUMULO_HOME/lib/[^.].*.jar,
-	$ZOOKEEPER_HOME/zookeeper[^.].*.jar,
-	$HADOOP_CONF_DIR,
-	$HADOOP_PREFIX/[^.].*.jar,
-	$HADOOP_PREFIX/lib/[^.].*.jar,
-      </value>
-      <description>Classpaths that accumulo checks for updates and class files.
-      When using the Security Manager, please remove the ".../target/classes/" values.
-      </description>
-    </property>
-
-    <property>
-      <name>crypto.module.class</name>
-      <value>org.apache.accumulo.core.security.crypto.DefaultCryptoModule</value>
-    </property>
-    <property>
-      <name>crypto.cipher.suite</name>
-      <value>AES/CFB/PKCS5Padding</value>
-    </property>
-    <property>
-      <name>crypto.cipher.algorithm.name</name>
-      <value>AES</value>
-    </property>
-    <property>
-      <name>crypto.cipher.key.length</name>
-      <value>128</value>
-    </property>
-    <property>
-      <name>crypto.secure.rng</name>
-      <value>SHA1PRNG</value>
-    </property>
-    <property>
-      <name>crypto.secure.rng.provider</name>
-      <value>SUN</value>
-    </property>
-    <property>
-      <name>crypto.secret.key.encryption.strategy.class</name>
-      <value>org.apache.accumulo.core.security.crypto.DefaultSecretKeyEncryptionStrategy</value>
-    </property>
-    <property>
-      <name>instance.dfs.dir</name>
-      <value>/tmp</value>
-    </property>
-    <property>
-      <name>instance.dfs.uri</name>
-      <value>file:///</value>
-    </property>
-    
-    <property>
-      <name>crypto.default.key.strategy.hdfs.uri</name>
-      <value>file:///</value>
-    </property>
-    <property>
-      <name>crypto.default.key.strategy.key.location</name>
-      <value>/tmp/test.secret.key</value>
-    </property>
-    
-    <property>
-    	<name>crypto.default.key.strategy.cipher.suite</name>
-    	<value>AES/ECB/NoPadding</value>
-    </property>
-
-
-
-</configuration>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e95dee9e/core/src/test/resources/crypto-on-no-key-encryption-accumulo-site.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/crypto-on-no-key-encryption-accumulo-site.xml b/core/src/test/resources/crypto-on-no-key-encryption-accumulo-site.xml
deleted file mode 100644
index 640abac..0000000
--- a/core/src/test/resources/crypto-on-no-key-encryption-accumulo-site.xml
+++ /dev/null
@@ -1,144 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  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.
--->
-<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
-
-<configuration>
-	<!--
-  Put your site-specific accumulo configurations here.
-
-  The available configuration values along with their defaults
-  are documented in docs/config.html
-
-  Unless you are simply testing at your workstation, you will most
-  definitely need to change the three entries below.
-	-->
-
-    <property>
-      <name>instance.zookeeper.host</name>
-      <value>localhost:2181</value>
-      <description>comma separated list of zookeeper servers</description>
-    </property>
-
-    <property>
-      <name>logger.dir.walog</name>
-      <value>walogs</value>
-      <description>The directory used to store write-ahead logs on the local filesystem. It is possible to specify a comma-separated list of directories.</description>
-    </property>
-
-    <property>
-      <name>instance.secret</name>
-      <value>DEFAULT</value>
-      <description>A secret unique to a given instance that all servers must know in order to communicate with one another.
-                   Change it before initialization. To change it later use ./bin/accumulo org.apache.accumulo.server.util.ChangeSecret [oldpasswd] [newpasswd],
-                   and then update this file.
-      </description>
-    </property>
-
-    <property>
-      <name>tserver.memory.maps.max</name>
-      <value>80M</value>
-    </property>
-
-    <property>
-      <name>tserver.cache.data.size</name>
-      <value>7M</value>
-    </property>
-
-    <property>
-      <name>tserver.cache.index.size</name>
-      <value>20M</value>
-    </property>
-
-    <property>
-      <name>trace.password</name>
-      <!--
-        change this to the root user's password, and/or change the user below
-       -->
-      <value>password</value>
-    </property>
-
-    <property>
-      <name>trace.user</name>
-      <value>root</value>
-    </property>
-
-    <property>
-      <name>tserver.sort.buffer.size</name>
-      <value>50M</value>
-    </property>
-
-    <property>
-      <name>tserver.walog.max.size</name>
-      <value>100M</value>
-    </property>
-
-    <property>
-      <name>general.classpaths</name>
-      <value>
-    $ACCUMULO_HOME/server/target/classes/,
-    $ACCUMULO_HOME/core/target/classes/,
-    $ACCUMULO_HOME/start/target/classes/,
-    $ACCUMULO_HOME/fate/target/classes/,
-    $ACCUMULO_HOME/proxy/target/classes/,
-    $ACCUMULO_HOME/examples/target/classes/,
-	$ACCUMULO_HOME/lib/[^.].$ACCUMULO_VERSION.jar,
-	$ACCUMULO_HOME/lib/[^.].*.jar,
-	$ZOOKEEPER_HOME/zookeeper[^.].*.jar,
-	$HADOOP_CONF_DIR,
-	$HADOOP_PREFIX/[^.].*.jar,
-	$HADOOP_PREFIX/lib/[^.].*.jar,
-      </value>
-      <description>Classpaths that accumulo checks for updates and class files.
-      When using the Security Manager, please remove the ".../target/classes/" values.
-      </description>
-    </property>
-
-    <property>
-      <name>crypto.module.class</name>
-      <value>org.apache.accumulo.core.security.crypto.DefaultCryptoModule</value>
-    </property>
-    <property>
-      <name>crypto.cipher.suite</name>
-      <value>AES/CFB/PKCS5Padding</value>
-    </property>
-    <property>
-      <name>crypto.cipher.algorithm.name</name>
-      <value>AES</value>
-    </property>
-    <property>
-      <name>crypto.cipher.key.length</name>
-      <value>128</value>
-    </property>
-    <property>
-      <name>crypto.secure.rng</name>
-      <value>SHA1PRNG</value>
-    </property>
-    <property>
-      <name>crypto.secure.rng.provider</name>
-      <value>SUN</value>
-    </property>
-    <property>
-      <name>instance.dfs.dir</name>
-      <value>/tmp</value>
-    </property>
-    <property>
-      <name>instance.dfs.uri</name>
-      <value>file:///</value>
-    </property>
-
-</configuration>

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e95dee9e/minicluster/src/main/java/org/apache/accumulo/minicluster/MiniAccumuloConfig.java
----------------------------------------------------------------------
diff --git a/minicluster/src/main/java/org/apache/accumulo/minicluster/MiniAccumuloConfig.java b/minicluster/src/main/java/org/apache/accumulo/minicluster/MiniAccumuloConfig.java
index 39cd6d4..bfa7922 100644
--- a/minicluster/src/main/java/org/apache/accumulo/minicluster/MiniAccumuloConfig.java
+++ b/minicluster/src/main/java/org/apache/accumulo/minicluster/MiniAccumuloConfig.java
@@ -113,7 +113,7 @@ public class MiniAccumuloConfig {
       // since there is a small amount of memory, check more frequently for majc... setting may not be needed in 1.5
       mergeProp(Property.TSERV_MAJC_DELAY.getKey(), "3");
       mergeProp(Property.GENERAL_CLASSPATHS.getKey(), classpath);
-      mergeProp(Property.GENERAL_DYNAMIC_CLASSPATHS.getKey(), libDir.getAbsolutePath());
+      mergeProp(Property.GENERAL_DYNAMIC_CLASSPATHS.getKey(), libDir.getAbsolutePath() + "/[^.].*[.]jar");
       mergeProp(Property.GC_CYCLE_DELAY.getKey(), "30s");
       mergePropWithRandomPort(Property.MASTER_CLIENTPORT.getKey());
       mergePropWithRandomPort(Property.TRACE_PORT.getKey());
@@ -395,4 +395,13 @@ public class MiniAccumuloConfig {
   public void setClasspathItems(String... classpathItems) {
     this.classpathItems = classpathItems;
   }
+
+  /**
+   * Sets arbitrary configuration properties.
+   * 
+   * @since 1.6.0
+   */
+  public void setProperty(Property p, String value) {
+    this.siteConfig.put(p.getKey(), value);
+  }
 }

http://git-wip-us.apache.org/repos/asf/accumulo/blob/e95dee9e/proxy/src/test/java/org/apache/accumulo/proxy/SimpleProxyIT.java
----------------------------------------------------------------------
diff --git a/proxy/src/test/java/org/apache/accumulo/proxy/SimpleProxyIT.java b/proxy/src/test/java/org/apache/accumulo/proxy/SimpleProxyIT.java
new file mode 100644
index 0000000..dd4f7a8
--- /dev/null
+++ b/proxy/src/test/java/org/apache/accumulo/proxy/SimpleProxyIT.java
@@ -0,0 +1,1525 @@
+/*
+ * 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.proxy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.conf.DefaultConfiguration;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.file.FileOperations;
+import org.apache.accumulo.core.file.FileSKVWriter;
+import org.apache.accumulo.core.iterators.DevNull;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iterators.user.SummingCombiner;
+import org.apache.accumulo.core.iterators.user.VersioningIterator;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.util.ByteBufferUtil;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.accumulo.examples.simple.constraints.NumericValueConstraint;
+import org.apache.accumulo.minicluster.MiniAccumuloCluster;
+import org.apache.accumulo.minicluster.MiniAccumuloConfig;
+import org.apache.accumulo.proxy.thrift.AccumuloProxy.Client;
+import org.apache.accumulo.proxy.thrift.AccumuloSecurityException;
+import org.apache.accumulo.proxy.thrift.ActiveCompaction;
+import org.apache.accumulo.proxy.thrift.ActiveScan;
+import org.apache.accumulo.proxy.thrift.BatchScanOptions;
+import org.apache.accumulo.proxy.thrift.Column;
+import org.apache.accumulo.proxy.thrift.ColumnUpdate;
+import org.apache.accumulo.proxy.thrift.CompactionReason;
+import org.apache.accumulo.proxy.thrift.CompactionType;
+import org.apache.accumulo.proxy.thrift.Condition;
+import org.apache.accumulo.proxy.thrift.ConditionalStatus;
+import org.apache.accumulo.proxy.thrift.ConditionalUpdates;
+import org.apache.accumulo.proxy.thrift.ConditionalWriterOptions;
+import org.apache.accumulo.proxy.thrift.DiskUsage;
+import org.apache.accumulo.proxy.thrift.IteratorScope;
+import org.apache.accumulo.proxy.thrift.IteratorSetting;
+import org.apache.accumulo.proxy.thrift.Key;
+import org.apache.accumulo.proxy.thrift.KeyValue;
+import org.apache.accumulo.proxy.thrift.MutationsRejectedException;
+import org.apache.accumulo.proxy.thrift.PartialKey;
+import org.apache.accumulo.proxy.thrift.Range;
+import org.apache.accumulo.proxy.thrift.ScanColumn;
+import org.apache.accumulo.proxy.thrift.ScanOptions;
+import org.apache.accumulo.proxy.thrift.ScanResult;
+import org.apache.accumulo.proxy.thrift.ScanState;
+import org.apache.accumulo.proxy.thrift.ScanType;
+import org.apache.accumulo.proxy.thrift.SystemPermission;
+import org.apache.accumulo.proxy.thrift.TableExistsException;
+import org.apache.accumulo.proxy.thrift.TableNotFoundException;
+import org.apache.accumulo.proxy.thrift.TablePermission;
+import org.apache.accumulo.proxy.thrift.TimeType;
+import org.apache.accumulo.proxy.thrift.UnknownScanner;
+import org.apache.accumulo.proxy.thrift.UnknownWriter;
+import org.apache.accumulo.proxy.thrift.WriterOptions;
+import org.apache.accumulo.server.util.PortUtils;
+import org.apache.accumulo.test.functional.SlowIterator;
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.Text;
+import org.apache.thrift.TException;
+import org.apache.thrift.protocol.TProtocolFactory;
+import org.apache.thrift.server.TServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
+
+/**
+ * Call every method on the proxy and try to verify that it works.
+ */
+public class SimpleProxyIT {
+
+  public static File macTestFolder = new File(System.getProperty("user.dir") + "/target/" + SimpleProxyIT.class.getName());
+
+  private static MiniAccumuloCluster accumulo;
+  private static String secret = "superSecret";
+  private static Random random = new Random();
+  private static TServer proxyServer;
+  private static Thread thread;
+  private static int proxyPort;
+  private static org.apache.accumulo.proxy.thrift.AccumuloProxy.Client client;
+  private static String principal = "root";
+
+  private static Map<String,String> properties = new TreeMap<String,String>() {
+    private static final long serialVersionUID = 1L;
+
+    {
+      put("password", secret);
+    }
+  };
+  private static ByteBuffer creds = null;
+
+  private static Class<? extends TProtocolFactory> protocolClass;
+
+  static Class<? extends TProtocolFactory> getRandomProtocol() {
+    List<Class<? extends TProtocolFactory>> protocolFactories = new ArrayList<Class<? extends TProtocolFactory>>();
+    protocolFactories.add(org.apache.thrift.protocol.TJSONProtocol.Factory.class);
+    protocolFactories.add(org.apache.thrift.protocol.TBinaryProtocol.Factory.class);
+    protocolFactories.add(org.apache.thrift.protocol.TTupleProtocol.Factory.class);
+    protocolFactories.add(org.apache.thrift.protocol.TCompactProtocol.Factory.class);
+
+    return protocolFactories.get(random.nextInt(protocolFactories.size()));
+  }
+
+  private static final AtomicInteger tableCounter = new AtomicInteger(0);
+
+  private static String makeTableName() {
+    return "test" + tableCounter.getAndIncrement();
+  }
+
+  @Rule
+  public TemporaryFolder tempFolder = new TemporaryFolder(macTestFolder);
+
+  @Rule
+  public TestName testName = new TestName();
+
+  @BeforeClass
+  public static void setupMiniCluster() throws Exception {
+    FileUtils.deleteQuietly(macTestFolder);
+    macTestFolder.mkdirs();
+    MiniAccumuloConfig config = new MiniAccumuloConfig(macTestFolder, secret).setNumTservers(1);
+    accumulo = new MiniAccumuloCluster(config);
+    accumulo.start();
+
+    Properties props = new Properties();
+    props.put("instance", accumulo.getConfig().getInstanceName());
+    props.put("zookeepers", accumulo.getConfig().getZooKeepers());
+    props.put("tokenClass", PasswordToken.class.getName());
+
+    protocolClass = getRandomProtocol();
+
+    proxyPort = PortUtils.getRandomFreePort();
+    proxyServer = Proxy.createProxyServer(org.apache.accumulo.proxy.thrift.AccumuloProxy.class, org.apache.accumulo.proxy.ProxyServer.class, proxyPort,
+        protocolClass, props);
+    thread = new Thread() {
+      @Override
+      public void run() {
+        proxyServer.serve();
+      }
+    };
+    thread.start();
+    while (!proxyServer.isServing())
+      UtilWaitThread.sleep(100);
+    client = new TestProxyClient("localhost", proxyPort, protocolClass.newInstance()).proxy();
+    creds = client.login(principal, properties);
+  }
+
+  @Test(timeout = 10000)
+  public void security() throws Exception {
+    client.createLocalUser(creds, "user", s2bb(secret));
+    ByteBuffer badLogin = client.login("user", properties);
+    client.dropLocalUser(creds, "user");
+    final String table = makeTableName();
+    client.createTable(creds, table, false, TimeType.MILLIS);
+
+    final IteratorSetting setting = new IteratorSetting(100, "slow", SlowIterator.class.getName(), Collections.singletonMap("sleepTime", "200"));
+
+    try {
+      client.addConstraint(badLogin, table, NumericValueConstraint.class.getName());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.addSplits(badLogin, table, Collections.singleton(s2bb("1")));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.clearLocatorCache(badLogin, table);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.compactTable(badLogin, table, null, null, null, true, false);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.cancelCompaction(badLogin, table);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createTable(badLogin, table, false, TimeType.MILLIS);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.deleteTable(badLogin, table);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.deleteRows(badLogin, table, null, null);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.tableExists(badLogin, table);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.flushTable(badLogin, table, null, null, false);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getLocalityGroups(badLogin, table);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getMaxRow(badLogin, table, Collections.<ByteBuffer> emptySet(), null, false, null, false);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getTableProperties(badLogin, table);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.listSplits(badLogin, table, 10000);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.listTables(badLogin);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.listConstraints(badLogin, table);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.mergeTablets(badLogin, table, null, null);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.offlineTable(badLogin, table, false);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.onlineTable(badLogin, table, false);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.removeConstraint(badLogin, table, 0);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.removeTableProperty(badLogin, table, Property.TABLE_FILE_MAX.getKey());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.renameTable(badLogin, table, "someTableName");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      Map<String,Set<String>> groups = new HashMap<String,Set<String>>();
+      groups.put("group1", Collections.singleton("cf1"));
+      groups.put("group2", Collections.singleton("cf2"));
+      client.setLocalityGroups(badLogin, table, groups);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.setTableProperty(badLogin, table, Property.TABLE_FILE_MAX.getKey(), "0");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.tableIdMap(badLogin);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.getSiteConfiguration(badLogin);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getSystemConfiguration(badLogin);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getTabletServers(badLogin);
+      fail("exception not thrown");
+    } catch (TException ex) {}
+    try {
+      client.getActiveScans(badLogin, "fake");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getActiveCompactions(badLogin, "fakse");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.removeProperty(badLogin, "table.split.threshold");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.setProperty(badLogin, "table.split.threshold", "500M");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.testClassLoad(badLogin, DevNull.class.getName(), SortedKeyValueIterator.class.getName());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.authenticateUser(badLogin, "root", s2pp(secret));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      HashSet<ByteBuffer> auths = new HashSet<ByteBuffer>(Arrays.asList(s2bb("A"), s2bb("B")));
+      client.changeUserAuthorizations(badLogin, "stooge", auths);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.changeLocalUserPassword(badLogin, "stooge", s2bb(""));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createLocalUser(badLogin, "stooge", s2bb("password"));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.dropLocalUser(badLogin, "stooge");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getUserAuthorizations(badLogin, "stooge");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.grantSystemPermission(badLogin, "stooge", SystemPermission.CREATE_TABLE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.grantTablePermission(badLogin, "root", table, TablePermission.WRITE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.hasSystemPermission(badLogin, "stooge", SystemPermission.CREATE_TABLE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.hasTablePermission(badLogin, "root", table, TablePermission.WRITE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.listLocalUsers(badLogin);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.revokeSystemPermission(badLogin, "stooge", SystemPermission.CREATE_TABLE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.revokeTablePermission(badLogin, "root", table, TablePermission.ALTER_TABLE);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createScanner(badLogin, table, new ScanOptions());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createBatchScanner(badLogin, table, new BatchScanOptions());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.updateAndFlush(badLogin, table, new HashMap<ByteBuffer,List<ColumnUpdate>>());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createWriter(badLogin, table, new WriterOptions());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.attachIterator(badLogin, "slow", setting, EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.checkIteratorConflicts(badLogin, table, setting, EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      final String TABLE_TEST = makeTableName();
+      client.cloneTable(badLogin, table, TABLE_TEST, false, null, null);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.exportTable(badLogin, table, "/tmp");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.importTable(badLogin, "testify", "/tmp");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.getIteratorSetting(badLogin, table, "foo", IteratorScope.SCAN);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.listIterators(badLogin, table);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.removeIterator(badLogin, table, "name", EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.splitRangeByTablets(badLogin, table, client.getRowRange(ByteBuffer.wrap("row".getBytes())), 10);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      File importDir = tempFolder.newFolder("importDir");
+      File failuresDir = tempFolder.newFolder("failuresDir");
+      client.importDirectory(badLogin, table, importDir.getAbsolutePath(), failuresDir.getAbsolutePath(), true);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.pingTabletServer(badLogin, "fake");
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.login("badUser", properties);
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.testTableClassLoad(badLogin, table, VersioningIterator.class.getName(), SortedKeyValueIterator.class.getName());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+    try {
+      client.createConditionalWriter(badLogin, table, new ConditionalWriterOptions());
+      fail("exception not thrown");
+    } catch (AccumuloSecurityException ex) {}
+  }
+
+  @Test(timeout = 10000)
+  public void tableNotFound() throws Exception {
+    final String doesNotExist = "doesNotExists";
+    try {
+      client.addConstraint(creds, doesNotExist, NumericValueConstraint.class.getName());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.addSplits(creds, doesNotExist, Collections.<ByteBuffer> emptySet());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    final IteratorSetting setting = new IteratorSetting(100, "slow", SlowIterator.class.getName(), Collections.singletonMap("sleepTime", "200"));
+    try {
+      client.attachIterator(creds, doesNotExist, setting, EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.cancelCompaction(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.checkIteratorConflicts(creds, doesNotExist, setting, EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.clearLocatorCache(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      final String TABLE_TEST = makeTableName();
+      client.cloneTable(creds, doesNotExist, TABLE_TEST, false, null, null);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.compactTable(creds, doesNotExist, null, null, null, true, false);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.createBatchScanner(creds, doesNotExist, new BatchScanOptions());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.createScanner(creds, doesNotExist, new ScanOptions());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.createWriter(creds, doesNotExist, new WriterOptions());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.deleteRows(creds, doesNotExist, null, null);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.deleteTable(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.exportTable(creds, doesNotExist, "/tmp");
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.flushTable(creds, doesNotExist, null, null, false);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.getIteratorSetting(creds, doesNotExist, "foo", IteratorScope.SCAN);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.getLocalityGroups(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.getMaxRow(creds, doesNotExist, Collections.<ByteBuffer> emptySet(), null, false, null, false);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.getTableProperties(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.grantTablePermission(creds, "root", doesNotExist, TablePermission.WRITE);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.hasTablePermission(creds, "root", doesNotExist, TablePermission.WRITE);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      File importDir = tempFolder.newFolder("importDir");
+      File failuresDir = tempFolder.newFolder("failuresDir");
+      client.importDirectory(creds, doesNotExist, importDir.getAbsolutePath(), failuresDir.getAbsolutePath(), true);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.listConstraints(creds, doesNotExist);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.listSplits(creds, doesNotExist, 10000);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.mergeTablets(creds, doesNotExist, null, null);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.offlineTable(creds, doesNotExist, false);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.onlineTable(creds, doesNotExist, false);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.removeConstraint(creds, doesNotExist, 0);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.removeIterator(creds, doesNotExist, "name", EnumSet.allOf(IteratorScope.class));
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.removeTableProperty(creds, doesNotExist, Property.TABLE_FILE_MAX.getKey());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.renameTable(creds, doesNotExist, "someTableName");
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.revokeTablePermission(creds, "root", doesNotExist, TablePermission.ALTER_TABLE);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.setTableProperty(creds, doesNotExist, Property.TABLE_FILE_MAX.getKey(), "0");
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.splitRangeByTablets(creds, doesNotExist, client.getRowRange(ByteBuffer.wrap("row".getBytes())), 10);
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.updateAndFlush(creds, doesNotExist, new HashMap<ByteBuffer,List<ColumnUpdate>>());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.getDiskUsage(creds, Collections.singleton(doesNotExist));
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.testTableClassLoad(creds, doesNotExist, VersioningIterator.class.getName(), SortedKeyValueIterator.class.getName());
+      fail("exception not thrown");
+    } catch (TableNotFoundException ex) {}
+    try {
+      client.createConditionalWriter(creds, doesNotExist, new ConditionalWriterOptions());
+    } catch (TableNotFoundException ex) {}
+  }
+
+  @Test(timeout = 10000)
+  public void testExists() throws Exception {
+    client.createTable(creds, "ett1", false, TimeType.MILLIS);
+    client.createTable(creds, "ett2", false, TimeType.MILLIS);
+    try {
+      client.createTable(creds, "ett1", false, TimeType.MILLIS);
+      fail("exception not thrown");
+    } catch (TableExistsException tee) {}
+    try {
+      client.renameTable(creds, "ett1", "ett2");
+      fail("exception not thrown");
+    } catch (TableExistsException tee) {}
+    try {
+      client.cloneTable(creds, "ett1", "ett2", false, new HashMap<String,String>(), new HashSet<String>());
+      fail("exception not thrown");
+    } catch (TableExistsException tee) {}
+  }
+
+  @Test(timeout = 10000)
+  public void testUnknownScanner() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+
+    String scanner = client.createScanner(creds, TABLE_TEST, null);
+    assertFalse(client.hasNext(scanner));
+    client.closeScanner(scanner);
+
+    try {
+      client.hasNext(scanner);
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+
+    try {
+      client.closeScanner(scanner);
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+
+    try {
+      client.nextEntry("99999999");
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+    try {
+      client.nextK("99999999", 6);
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+    try {
+      client.hasNext("99999999");
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+    try {
+      client.hasNext(UUID.randomUUID().toString());
+      fail("exception not thrown");
+    } catch (UnknownScanner us) {}
+  }
+
+  @Test(timeout = 10000)
+  public void testUnknownWriter() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+
+    String writer = client.createWriter(creds, TABLE_TEST, null);
+    client.update(writer, mutation("row0", "cf", "cq", "value"));
+    client.flush(writer);
+    client.update(writer, mutation("row2", "cf", "cq", "value2"));
+    client.closeWriter(writer);
+
+    // this is a oneway call, so it does not throw exceptions
+    client.update(writer, mutation("row2", "cf", "cq", "value2"));
+
+    try {
+      client.flush(writer);
+      fail("exception not thrown");
+    } catch (UnknownWriter uw) {}
+    try {
+      client.flush("99999");
+      fail("exception not thrown");
+    } catch (UnknownWriter uw) {}
+    try {
+      client.flush(UUID.randomUUID().toString());
+      fail("exception not thrown");
+    } catch (UnknownWriter uw) {}
+    try {
+      client.closeWriter("99999");
+      fail("exception not thrown");
+    } catch (UnknownWriter uw) {}
+  }
+
+  @Test(timeout = 10000)
+  public void testDelete() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+    client.updateAndFlush(creds, TABLE_TEST, mutation("row0", "cf", "cq", "value"));
+    String scanner = client.createScanner(creds, TABLE_TEST, null);
+    ScanResult entries = client.nextK(scanner, 10);
+    client.closeScanner(scanner);
+    assertFalse(entries.more);
+    assertEquals(1, entries.results.size());
+
+    ColumnUpdate upd = new ColumnUpdate(s2bb("cf"), s2bb("cq"));
+    upd.setDeleteCell(true);
+    Map<ByteBuffer,List<ColumnUpdate>> delete = Collections.singletonMap(s2bb("row0"), Collections.singletonList(upd));
+
+    client.updateAndFlush(creds, TABLE_TEST, delete);
+
+    scanner = client.createScanner(creds, TABLE_TEST, null);
+    entries = client.nextK(scanner, 10);
+    client.closeScanner(scanner);
+    assertEquals(0, entries.results.size());
+  }
+
+  @Test(timeout = 10000)
+  public void testInstanceOperations() throws Exception {
+    int tservers = 0;
+    for (String tserver : client.getTabletServers(creds)) {
+      client.pingTabletServer(creds, tserver);
+      tservers++;
+    }
+    assertTrue(tservers > 0);
+
+    // get something we know is in the site config
+    Map<String,String> cfg = client.getSiteConfiguration(creds);
+    assertTrue(cfg.get("instance.dfs.dir").startsWith(macTestFolder.getPath()));
+
+    // set a property in zookeeper
+    client.setProperty(creds, "table.split.threshold", "500M");
+
+    // check that we can read it
+    for (int i = 0; i < 5; i++) {
+      cfg = client.getSystemConfiguration(creds);
+      if ("500M".equals(cfg.get("table.split.threshold")))
+        break;
+      UtilWaitThread.sleep(200);
+    }
+    assertEquals("500M", cfg.get("table.split.threshold"));
+
+    // unset the setting, check that it's not what it was
+    client.removeProperty(creds, "table.split.threshold");
+    for (int i = 0; i < 5; i++) {
+      cfg = client.getSystemConfiguration(creds);
+      if (!"500M".equals(cfg.get("table.split.threshold")))
+        break;
+      UtilWaitThread.sleep(200);
+    }
+    assertNotEquals("500M", cfg.get("table.split.threshold"));
+
+    // try to load some classes via the proxy
+    assertTrue(client.testClassLoad(creds, DevNull.class.getName(), SortedKeyValueIterator.class.getName()));
+    assertFalse(client.testClassLoad(creds, "foo.bar", SortedKeyValueIterator.class.getName()));
+
+    // create a table that's very slow, so we can look for scans/compactions
+    client.createTable(creds, "slow", true, TimeType.MILLIS);
+    IteratorSetting setting = new IteratorSetting(100, "slow", SlowIterator.class.getName(), Collections.singletonMap("sleepTime", "200"));
+    client.attachIterator(creds, "slow", setting, EnumSet.allOf(IteratorScope.class));
+    client.updateAndFlush(creds, "slow", mutation("row", "cf", "cq", "value"));
+    client.updateAndFlush(creds, "slow", mutation("row2", "cf", "cq", "value"));
+    client.updateAndFlush(creds, "slow", mutation("row3", "cf", "cq", "value"));
+    client.updateAndFlush(creds, "slow", mutation("row4", "cf", "cq", "value"));
+
+    // scan
+    Thread t = new Thread() {
+      @Override
+      public void run() {
+        String scanner;
+        try {
+          Client client2 = new TestProxyClient("localhost", proxyPort, protocolClass.newInstance()).proxy();
+          scanner = client2.createScanner(creds, "slow", null);
+          client2.nextK(scanner, 10);
+          client2.closeScanner(scanner);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+    t.start();
+    // look for the scan
+    List<ActiveScan> scans = Collections.emptyList();
+    loop: for (int i = 0; i < 100; i++) {
+      for (String tserver : client.getTabletServers(creds)) {
+        scans = client.getActiveScans(creds, tserver);
+        if (!scans.isEmpty())
+          break loop;
+        UtilWaitThread.sleep(10);
+      }
+    }
+    t.join();
+    assertFalse(scans.isEmpty());
+    ActiveScan scan = scans.get(0);
+    assertEquals("root", scan.getUser());
+    assertTrue(ScanState.RUNNING.equals(scan.getState()) || ScanState.QUEUED.equals(scan.getState()));
+    assertEquals(ScanType.SINGLE, scan.getType());
+    assertEquals("slow", scan.getTable());
+    Map<String,String> map = client.tableIdMap(creds);
+    assertEquals(map.get("slow"), scan.getExtent().tableId);
+    assertTrue(scan.getExtent().endRow == null);
+    assertTrue(scan.getExtent().prevEndRow == null);
+
+    // start a compaction
+    t = new Thread() {
+      @Override
+      public void run() {
+        try {
+          Client client2 = new TestProxyClient("localhost", proxyPort, protocolClass.newInstance()).proxy();
+          client2.compactTable(creds, "slow", null, null, null, true, true);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+    t.start();
+
+    // try to catch it in the act
+    List<ActiveCompaction> compactions = Collections.emptyList();
+    loop2: for (int i = 0; i < 100; i++) {
+      for (String tserver : client.getTabletServers(creds)) {
+        compactions = client.getActiveCompactions(creds, tserver);
+        if (!compactions.isEmpty())
+          break loop2;
+      }
+      UtilWaitThread.sleep(10);
+    }
+    t.join();
+    // verify the compaction information
+    assertFalse(compactions.isEmpty());
+    ActiveCompaction c = compactions.get(0);
+    assertEquals(map.get("slow"), c.getExtent().tableId);
+    assertTrue(c.inputFiles.isEmpty());
+    assertEquals(CompactionType.MINOR, c.getType());
+    assertEquals(CompactionReason.USER, c.getReason());
+    assertEquals("", c.localityGroup);
+    assertTrue(c.outputFile.contains("default_tablet"));
+  }
+
+  @Test
+  public void testSecurityOperations() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    // check password
+    assertTrue(client.authenticateUser(creds, "root", s2pp(secret)));
+    assertFalse(client.authenticateUser(creds, "root", s2pp("")));
+
+    // create a user
+    client.createLocalUser(creds, "stooge", s2bb("password"));
+    // change auths
+    Set<String> users = client.listLocalUsers(creds);
+    assertEquals(new HashSet<String>(Arrays.asList("root", "stooge")), users);
+    HashSet<ByteBuffer> auths = new HashSet<ByteBuffer>(Arrays.asList(s2bb("A"), s2bb("B")));
+    client.changeUserAuthorizations(creds, "stooge", auths);
+    List<ByteBuffer> update = client.getUserAuthorizations(creds, "stooge");
+    assertEquals(auths, new HashSet<ByteBuffer>(update));
+
+    // change password
+    client.changeLocalUserPassword(creds, "stooge", s2bb(""));
+    assertTrue(client.authenticateUser(creds, "stooge", s2pp("")));
+
+    // check permission failure
+    @SuppressWarnings("serial")
+    ByteBuffer stooge = client.login("stooge", new TreeMap<String,String>() {
+      {
+        put("password", "");
+      }
+    });
+
+    try {
+      client.createTable(stooge, "fail", true, TimeType.MILLIS);
+      fail("should not create the table");
+    } catch (AccumuloSecurityException ex) {
+      assertFalse(client.listTables(creds).contains("fail"));
+    }
+    // grant permissions and test
+    assertFalse(client.hasSystemPermission(creds, "stooge", SystemPermission.CREATE_TABLE));
+    client.grantSystemPermission(creds, "stooge", SystemPermission.CREATE_TABLE);
+    assertTrue(client.hasSystemPermission(creds, "stooge", SystemPermission.CREATE_TABLE));
+    client.createTable(stooge, "success", true, TimeType.MILLIS);
+    client.listTables(creds).contains("succcess");
+
+    // revoke permissions
+    client.revokeSystemPermission(creds, "stooge", SystemPermission.CREATE_TABLE);
+    assertFalse(client.hasSystemPermission(creds, "stooge", SystemPermission.CREATE_TABLE));
+    try {
+      client.createTable(stooge, "fail", true, TimeType.MILLIS);
+      fail("should not create the table");
+    } catch (AccumuloSecurityException ex) {
+      assertFalse(client.listTables(creds).contains("fail"));
+    }
+    // create a table to test table permissions
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+    // denied!
+    try {
+      String scanner = client.createScanner(stooge, TABLE_TEST, null);
+      client.nextK(scanner, 100);
+      fail("stooge should not read table test");
+    } catch (AccumuloSecurityException ex) {}
+    // grant
+    assertFalse(client.hasTablePermission(creds, "stooge", TABLE_TEST, TablePermission.READ));
+    client.grantTablePermission(creds, "stooge", TABLE_TEST, TablePermission.READ);
+    assertTrue(client.hasTablePermission(creds, "stooge", TABLE_TEST, TablePermission.READ));
+    String scanner = client.createScanner(stooge, TABLE_TEST, null);
+    client.nextK(scanner, 10);
+    client.closeScanner(scanner);
+    // revoke
+    client.revokeTablePermission(creds, "stooge", TABLE_TEST, TablePermission.READ);
+    assertFalse(client.hasTablePermission(creds, "stooge", TABLE_TEST, TablePermission.READ));
+    try {
+      scanner = client.createScanner(stooge, TABLE_TEST, null);
+      client.nextK(scanner, 100);
+      fail("stooge should not read table test");
+    } catch (AccumuloSecurityException ex) {}
+
+    // delete user
+    client.dropLocalUser(creds, "stooge");
+    users = client.listLocalUsers(creds);
+    assertEquals(1, users.size());
+
+  }
+
+  @Test
+  public void testBatchWriter() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+    client.addConstraint(creds, TABLE_TEST, NumericValueConstraint.class.getName());
+
+    WriterOptions writerOptions = new WriterOptions();
+    writerOptions.setLatencyMs(10000);
+    writerOptions.setMaxMemory(2);
+    writerOptions.setThreads(1);
+    writerOptions.setTimeoutMs(100000);
+
+    String batchWriter = client.createWriter(creds, TABLE_TEST, writerOptions);
+    client.update(batchWriter, mutation("row1", "cf", "cq", "x"));
+    client.update(batchWriter, mutation("row1", "cf", "cq", "x"));
+    try {
+      client.flush(batchWriter);
+      fail("constraint did not fire");
+    } catch (MutationsRejectedException ex) {}
+    try {
+      client.closeWriter(batchWriter);
+      fail("constraint did not fire");
+    } catch (MutationsRejectedException e) {}
+
+    client.removeConstraint(creds, TABLE_TEST, 2);
+
+    writerOptions = new WriterOptions();
+    writerOptions.setLatencyMs(10000);
+    writerOptions.setMaxMemory(3000);
+    writerOptions.setThreads(1);
+    writerOptions.setTimeoutMs(100000);
+
+    batchWriter = client.createWriter(creds, TABLE_TEST, writerOptions);
+
+    client.update(batchWriter, mutation("row1", "cf", "cq", "x"));
+    client.flush(batchWriter);
+    client.closeWriter(batchWriter);
+
+    String scanner = client.createScanner(creds, TABLE_TEST, null);
+    ScanResult more = client.nextK(scanner, 2);
+    assertEquals(1, more.getResults().size());
+    client.closeScanner(scanner);
+
+    client.deleteTable(creds, TABLE_TEST);
+  }
+
+  @Test
+  public void testTableOperations() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+    // constraints
+    client.addConstraint(creds, TABLE_TEST, NumericValueConstraint.class.getName());
+    assertEquals(2, client.listConstraints(creds, TABLE_TEST).size());
+    client.updateAndFlush(creds, TABLE_TEST, mutation("row1", "cf", "cq", "123"));
+
+    try {
+      client.updateAndFlush(creds, TABLE_TEST, mutation("row1", "cf", "cq", "x"));
+      fail("constraint did not fire");
+    } catch (MutationsRejectedException ex) {}
+
+    client.removeConstraint(creds, TABLE_TEST, 2);
+    assertEquals(1, client.listConstraints(creds, TABLE_TEST).size());
+    client.updateAndFlush(creds, TABLE_TEST, mutation("row1", "cf", "cq", "x"));
+    String scanner = client.createScanner(creds, TABLE_TEST, null);
+    ScanResult more = client.nextK(scanner, 2);
+    client.closeScanner(scanner);
+    assertFalse(more.isMore());
+    assertEquals(1, more.getResults().size());
+    assertEquals(s2bb("x"), more.getResults().get(0).value);
+    // splits, merge
+    client.addSplits(creds, TABLE_TEST, new HashSet<ByteBuffer>(Arrays.asList(s2bb("a"), s2bb("m"), s2bb("z"))));
+    List<ByteBuffer> splits = client.listSplits(creds, TABLE_TEST, 1);
+    assertEquals(Arrays.asList(s2bb("m")), splits);
+    client.mergeTablets(creds, TABLE_TEST, null, s2bb("m"));
+    splits = client.listSplits(creds, TABLE_TEST, 10);
+    assertEquals(Arrays.asList(s2bb("m"), s2bb("z")), splits);
+    client.mergeTablets(creds, TABLE_TEST, null, null);
+    splits = client.listSplits(creds, TABLE_TEST, 10);
+    List<ByteBuffer> empty = Collections.emptyList();
+    assertEquals(empty, splits);
+    // iterators
+    client.deleteTable(creds, TABLE_TEST);
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+    HashMap<String,String> options = new HashMap<String,String>();
+    options.put("type", "STRING");
+    options.put("columns", "cf");
+    IteratorSetting setting = new IteratorSetting(10, TABLE_TEST, SummingCombiner.class.getName(), options);
+    client.attachIterator(creds, TABLE_TEST, setting, EnumSet.allOf(IteratorScope.class));
+    for (int i = 0; i < 10; i++) {
+      client.updateAndFlush(creds, TABLE_TEST, mutation("row1", "cf", "cq", "1"));
+    }
+    scanner = client.createScanner(creds, TABLE_TEST, null);
+    more = client.nextK(scanner, 2);
+    client.closeScanner(scanner);
+    assertEquals("10", new String(more.getResults().get(0).getValue()));
+    try {
+      client.checkIteratorConflicts(creds, TABLE_TEST, setting, EnumSet.allOf(IteratorScope.class));
+      fail("checkIteratorConflicts did not throw an exception");
+    } catch (Exception ex) {}
+    client.deleteRows(creds, TABLE_TEST, null, null);
+    client.removeIterator(creds, TABLE_TEST, "test", EnumSet.allOf(IteratorScope.class));
+    for (int i = 0; i < 10; i++) {
+      client.updateAndFlush(creds, TABLE_TEST, mutation("row" + i, "cf", "cq", "" + i));
+      client.flushTable(creds, TABLE_TEST, null, null, true);
+    }
+    scanner = client.createScanner(creds, TABLE_TEST, null);
+    more = client.nextK(scanner, 100);
+    client.closeScanner(scanner);
+    assertEquals(10, more.getResults().size());
+    // clone
+    final String TABLE_TEST2 = makeTableName();
+    client.cloneTable(creds, TABLE_TEST, TABLE_TEST2, true, null, null);
+    scanner = client.createScanner(creds, TABLE_TEST2, null);
+    more = client.nextK(scanner, 100);
+    client.closeScanner(scanner);
+    assertEquals(10, more.getResults().size());
+    client.deleteTable(creds, TABLE_TEST2);
+
+    // don't know how to test this, call it just for fun
+    client.clearLocatorCache(creds, TABLE_TEST);
+
+    // compact
+    client.compactTable(creds, TABLE_TEST, null, null, null, true, true);
+    assertEquals(1, countFiles(TABLE_TEST));
+
+    // get disk usage
+    client.cloneTable(creds, TABLE_TEST, TABLE_TEST2, true, null, null);
+    Set<String> tablesToScan = new HashSet<String>();
+    tablesToScan.add(TABLE_TEST);
+    tablesToScan.add(TABLE_TEST2);
+    tablesToScan.add("foo");
+    client.createTable(creds, "foo", true, TimeType.MILLIS);
+    List<DiskUsage> diskUsage = (client.getDiskUsage(creds, tablesToScan));
+    assertEquals(2, diskUsage.size());
+    assertEquals(1, diskUsage.get(0).getTables().size());
+    assertEquals(2, diskUsage.get(1).getTables().size());
+    client.compactTable(creds, TABLE_TEST2, null, null, null, true, true);
+    diskUsage = (client.getDiskUsage(creds, tablesToScan));
+    assertEquals(3, diskUsage.size());
+    assertEquals(1, diskUsage.get(0).getTables().size());
+    assertEquals(1, diskUsage.get(1).getTables().size());
+    assertEquals(1, diskUsage.get(2).getTables().size());
+    client.deleteTable(creds, "foo");
+    client.deleteTable(creds, TABLE_TEST2);
+
+    // export/import
+    File dir = tempFolder.newFolder("test");
+    File destDir = tempFolder.newFolder("test_dest");
+    client.offlineTable(creds, TABLE_TEST, false);
+    client.exportTable(creds, TABLE_TEST, dir.getAbsolutePath());
+    // copy files to a new location
+    FileSystem fs = FileSystem.get(new Configuration());
+    FSDataInputStream is = fs.open(new Path(dir + "/distcp.txt"));
+    BufferedReader r = new BufferedReader(new InputStreamReader(is));
+    while (true) {
+      String line = r.readLine();
+      if (line == null)
+        break;
+      Path srcPath = new Path(line);
+      FileUtils.copyFile(new File(srcPath.toUri().getPath()), new File(destDir, srcPath.getName()));
+    }
+    client.deleteTable(creds, TABLE_TEST);
+    client.importTable(creds, "testify", destDir.getAbsolutePath());
+    scanner = client.createScanner(creds, "testify", null);
+    more = client.nextK(scanner, 100);
+    client.closeScanner(scanner);
+    assertEquals(10, more.results.size());
+
+    try {
+      // ACCUMULO-1558 a second import from the same dir should fail, the first import moved the files
+      client.importTable(creds, "testify2", destDir.getAbsolutePath());
+      fail();
+    } catch (Exception e) {}
+
+    assertFalse(client.listTables(creds).contains("testify2"));
+
+    // Locality groups
+    client.createTable(creds, "test", true, TimeType.MILLIS);
+    Map<String,Set<String>> groups = new HashMap<String,Set<String>>();
+    groups.put("group1", Collections.singleton("cf1"));
+    groups.put("group2", Collections.singleton("cf2"));
+    client.setLocalityGroups(creds, "test", groups);
+    assertEquals(groups, client.getLocalityGroups(creds, "test"));
+    // table properties
+    Map<String,String> orig = client.getTableProperties(creds, "test");
+    client.setTableProperty(creds, "test", "table.split.threshold", "500M");
+    Map<String,String> update = client.getTableProperties(creds, "test");
+    for (int i = 0; i < 5; i++) {
+      if (update.get("table.split.threshold").equals("500M"))
+        break;
+      UtilWaitThread.sleep(200);
+    }
+    assertEquals(update.get("table.split.threshold"), "500M");
+    client.removeTableProperty(creds, "test", "table.split.threshold");
+    update = client.getTableProperties(creds, "test");
+    assertEquals(orig, update);
+    // rename table
+    Map<String,String> tables = client.tableIdMap(creds);
+    client.renameTable(creds, "test", "bar");
+    Map<String,String> tables2 = client.tableIdMap(creds);
+    assertEquals(tables.get("test"), tables2.get("bar"));
+    // table exists
+    assertTrue(client.tableExists(creds, "bar"));
+    assertFalse(client.tableExists(creds, "test"));
+    // bulk import
+    String filename = dir + "/bulk/import/rfile.rf";
+    FileSKVWriter writer = FileOperations.getInstance().openWriter(filename, fs, fs.getConf(), DefaultConfiguration.getInstance());
+    writer.startDefaultLocalityGroup();
+    writer.append(new org.apache.accumulo.core.data.Key(new Text("a"), new Text("b"), new Text("c")), new Value("value".getBytes()));
+    writer.close();
+    fs.mkdirs(new Path(dir + "/bulk/fail"));
+    client.importDirectory(creds, "bar", dir + "/bulk/import", dir + "/bulk/fail", true);
+    scanner = client.createScanner(creds, "bar", null);
+    more = client.nextK(scanner, 100);
+    client.closeScanner(scanner);
+    assertEquals(1, more.results.size());
+    ByteBuffer maxRow = client.getMaxRow(creds, "bar", null, null, false, null, false);
+    assertEquals(s2bb("a"), maxRow);
+
+    assertFalse(client.testTableClassLoad(creds, "bar", "abc123", SortedKeyValueIterator.class.getName()));
+    assertTrue(client.testTableClassLoad(creds, "bar", VersioningIterator.class.getName(), SortedKeyValueIterator.class.getName()));
+  }
+
+  private Condition newCondition(String cf, String cq) {
+    return new Condition(new Column(s2bb(cf), s2bb(cq), s2bb("")));
+  }
+
+  private Condition newCondition(String cf, String cq, String val) {
+    return newCondition(cf, cq).setValue(s2bb(val));
+  }
+
+  private Condition newCondition(String cf, String cq, long ts, String val) {
+    return newCondition(cf, cq).setValue(s2bb(val)).setTimestamp(ts);
+  }
+
+  private ColumnUpdate newColUpdate(String cf, String cq, String val) {
+    return new ColumnUpdate(s2bb(cf), s2bb(cq)).setValue(s2bb(val));
+  }
+
+  private ColumnUpdate newColUpdate(String cf, String cq, long ts, String val) {
+    return new ColumnUpdate(s2bb(cf), s2bb(cq)).setTimestamp(ts).setValue(s2bb(val));
+  }
+
+  private void assertScan(String[][] expected, String table) throws Exception {
+    String scid = client.createScanner(creds, table, new ScanOptions());
+    ScanResult keyValues = client.nextK(scid, expected.length + 1);
+
+    assertEquals(expected.length, keyValues.results.size());
+    assertFalse(keyValues.more);
+
+    for (int i = 0; i < keyValues.results.size(); i++) {
+      checkKey(expected[i][0], expected[i][1], expected[i][2], expected[i][3], keyValues.results.get(i));
+    }
+
+    client.closeScanner(scid);
+  }
+
+  @Test
+  public void testConditionalWriter() throws Exception {
+    final String TABLE_TEST = makeTableName();
+
+    client.createTable(creds, TABLE_TEST, true, TimeType.MILLIS);
+
+    client.addConstraint(creds, TABLE_TEST, NumericValueConstraint.class.getName());
+
+    String cwid = client.createConditionalWriter(creds, TABLE_TEST, new ConditionalWriterOptions());
+
+    Map<ByteBuffer,ConditionalUpdates> updates = new HashMap<ByteBuffer,ConditionalUpdates>();
+
+    updates.put(
+        s2bb("00345"),
+        new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq")), Arrays.asList(newColUpdate("meta", "seq", 10, "1"),
+            newColUpdate("data", "img", "73435435"))));
+
+    Map<ByteBuffer,ConditionalStatus> results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00345")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "73435435"}, {"00345", "meta", "seq", "1"}}, TABLE_TEST);
+
+    // test not setting values on conditions
+    updates.clear();
+
+    updates.put(s2bb("00345"), new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq")), Arrays.asList(newColUpdate("meta", "seq", "2"))));
+    updates.put(s2bb("00346"), new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq")), Arrays.asList(newColUpdate("meta", "seq", "1"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(2, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00345")));
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00346")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "73435435"}, {"00345", "meta", "seq", "1"}, {"00346", "meta", "seq", "1"}}, TABLE_TEST);
+
+    // test setting values on conditions
+    updates.clear();
+
+    updates.put(
+        s2bb("00345"),
+        new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq", "1")), Arrays.asList(newColUpdate("meta", "seq", 20, "2"),
+            newColUpdate("data", "img", "567890"))));
+
+    updates.put(s2bb("00346"), new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq", "2")), Arrays.asList(newColUpdate("meta", "seq", "3"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(2, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00345")));
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00346")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "567890"}, {"00345", "meta", "seq", "2"}, {"00346", "meta", "seq", "1"}}, TABLE_TEST);
+
+    // test setting timestamp on condition to a non-existant version
+    updates.clear();
+
+    updates.put(
+        s2bb("00345"),
+        new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq", 10, "2")), Arrays.asList(newColUpdate("meta", "seq", 30, "3"),
+            newColUpdate("data", "img", "1234567890"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00345")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "567890"}, {"00345", "meta", "seq", "2"}, {"00346", "meta", "seq", "1"}}, TABLE_TEST);
+
+    // test setting timestamp to an existing version
+
+    updates.clear();
+
+    updates.put(
+        s2bb("00345"),
+        new ConditionalUpdates(Arrays.asList(newCondition("meta", "seq", 20, "2")), Arrays.asList(newColUpdate("meta", "seq", 30, "3"),
+            newColUpdate("data", "img", "1234567890"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00345")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"}}, TABLE_TEST);
+
+    // run test w/ condition that has iterators
+    // following should fail w/o iterator
+    client.updateAndFlush(creds, TABLE_TEST, Collections.singletonMap(s2bb("00347"), Arrays.asList(newColUpdate("data", "count", "1"))));
+    client.updateAndFlush(creds, TABLE_TEST, Collections.singletonMap(s2bb("00347"), Arrays.asList(newColUpdate("data", "count", "1"))));
+    client.updateAndFlush(creds, TABLE_TEST, Collections.singletonMap(s2bb("00347"), Arrays.asList(newColUpdate("data", "count", "1"))));
+
+    updates.clear();
+    updates.put(s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "count", "3")), Arrays.asList(newColUpdate("data", "img", "1234567890"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}}, TABLE_TEST);
+
+    // following test w/ iterator setup should succeed
+    Condition iterCond = newCondition("data", "count", "3");
+    Map<String,String> props = new HashMap<String,String>();
+    props.put("type", "STRING");
+    props.put("columns", "data:count");
+    IteratorSetting is = new IteratorSetting(1, "sumc", SummingCombiner.class.getName(), props);
+    iterCond.setIterators(Arrays.asList(is));
+
+    updates.clear();
+    updates.put(s2bb("00347"), new ConditionalUpdates(Arrays.asList(iterCond), Arrays.asList(newColUpdate("data", "img", "1234567890"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}, {"00347", "data", "img", "1234567890"}}, TABLE_TEST);
+
+    // test a mutation that violated a constraint
+    updates.clear();
+    updates.put(s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "img", "1234567890")), Arrays.asList(newColUpdate("data", "count", "A"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.VIOLATED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}, {"00347", "data", "img", "1234567890"}}, TABLE_TEST);
+
+    // run test with two conditions
+    // both conditions should fail
+    updates.clear();
+    updates.put(
+        s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "img", "565"), newCondition("data", "count", "2")), Arrays.asList(
+            newColUpdate("data", "count", "3"), newColUpdate("data", "img", "0987654321"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}, {"00347", "data", "img", "1234567890"}}, TABLE_TEST);
+
+    // one condition should fail
+    updates.clear();
+    updates.put(
+        s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "img", "1234567890"), newCondition("data", "count", "2")), Arrays.asList(
+            newColUpdate("data", "count", "3"), newColUpdate("data", "img", "0987654321"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}, {"00347", "data", "img", "1234567890"}}, TABLE_TEST);
+
+    // one condition should fail
+    updates.clear();
+    updates.put(
+        s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "img", "565"), newCondition("data", "count", "1")), Arrays.asList(
+            newColUpdate("data", "count", "3"), newColUpdate("data", "img", "0987654321"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00347")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "1"}, {"00347", "data", "img", "1234567890"}}, TABLE_TEST);
+
+    // both conditions should succeed
+
+    ConditionalStatus result = client.updateRowConditionally(
+        creds,
+        TABLE_TEST,
+        s2bb("00347"),
+        new ConditionalUpdates(Arrays.asList(newCondition("data", "img", "1234567890"), newCondition("data", "count", "1")), Arrays.asList(
+            newColUpdate("data", "count", "3"), newColUpdate("data", "img", "0987654321"))));
+
+    assertEquals(ConditionalStatus.ACCEPTED, result);
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "3"}, {"00347", "data", "img", "0987654321"}}, TABLE_TEST);
+
+    client.closeConditionalWriter(cwid);
+    try {
+      client.updateRowsConditionally(cwid, updates);
+      fail("conditional writer not closed");
+    } catch (UnknownWriter uk) {}
+
+    // run test with colvis
+    client.createLocalUser(creds, "cwuser", s2bb("bestpasswordever"));
+    client.changeUserAuthorizations(creds, "cwuser", Collections.singleton(s2bb("A")));
+    client.grantTablePermission(creds, "cwuser", TABLE_TEST, TablePermission.WRITE);
+    client.grantTablePermission(creds, "cwuser", TABLE_TEST, TablePermission.READ);
+
+    ByteBuffer cwuCreds = client.login("cwuser", Collections.singletonMap("password", "bestpasswordever"));
+
+    cwid = client.createConditionalWriter(cwuCreds, TABLE_TEST, new ConditionalWriterOptions().setAuthorizations(Collections.singleton(s2bb("A"))));
+
+    updates.clear();
+    updates.put(
+        s2bb("00348"),
+        new ConditionalUpdates(Arrays.asList(new Condition(new Column(s2bb("data"), s2bb("c"), s2bb("A")))), Arrays.asList(newColUpdate("data", "seq", "1"),
+            newColUpdate("data", "c", "1").setColVisibility(s2bb("A")))));
+    updates.put(s2bb("00349"),
+        new ConditionalUpdates(Arrays.asList(new Condition(new Column(s2bb("data"), s2bb("c"), s2bb("B")))), Arrays.asList(newColUpdate("data", "seq", "1"))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(2, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00348")));
+    assertEquals(ConditionalStatus.INVISIBLE_VISIBILITY, results.get(s2bb("00349")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "3"}, {"00347", "data", "img", "0987654321"}, {"00348", "data", "seq", "1"}}, TABLE_TEST);
+
+    updates.clear();
+
+    updates.clear();
+    updates.put(
+        s2bb("00348"),
+        new ConditionalUpdates(Arrays.asList(new Condition(new Column(s2bb("data"), s2bb("c"), s2bb("A"))).setValue(s2bb("0"))), Arrays.asList(
+            newColUpdate("data", "seq", "2"), newColUpdate("data", "c", "2").setColVisibility(s2bb("A")))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.REJECTED, results.get(s2bb("00348")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "3"}, {"00347", "data", "img", "0987654321"}, {"00348", "data", "seq", "1"}}, TABLE_TEST);
+
+    updates.clear();
+    updates.put(
+        s2bb("00348"),
+        new ConditionalUpdates(Arrays.asList(new Condition(new Column(s2bb("data"), s2bb("c"), s2bb("A"))).setValue(s2bb("1"))), Arrays.asList(
+            newColUpdate("data", "seq", "2"), newColUpdate("data", "c", "2").setColVisibility(s2bb("A")))));
+
+    results = client.updateRowsConditionally(cwid, updates);
+
+    assertEquals(1, results.size());
+    assertEquals(ConditionalStatus.ACCEPTED, results.get(s2bb("00348")));
+
+    assertScan(new String[][] { {"00345", "data", "img", "1234567890"}, {"00345", "meta", "seq", "3"}, {"00346", "meta", "seq", "1"},
+        {"00347", "data", "count", "3"}, {"00347", "data", "img", "0987654321"}, {"00348", "data", "seq", "2"}}, TABLE_TEST);
+
+    client.closeConditionalWriter(cwid);
+    try {
+      client.updateRowsConditionally(cwid, updates);
+      fail("conditional writer not closed");
+    } catch (UnknownWriter uk) {}
+
+    client.dropLocalUser(creds, "cwuser");
+
+  }
+
+  private void checkKey(String row, String cf, String cq, String val, KeyValue keyValue) {
+    assertEquals(row, ByteBufferUtil.toString(keyValue.key.row));
+    assertEquals(cf, ByteBufferUtil.toString(keyValue.key.colFamily));
+    assertEquals(cq, ByteBufferUtil.toString(keyValue.key.colQualifier));
+    assertEquals("", ByteBufferUtil.toString(keyValue.key.colVisibility));
+    assertEquals(val, ByteBufferUtil.toString(keyValue.value));
+  }
+
+  // scan !METADATA table for file entries for the given table
+  private int countFiles(String table) throws Exception {
+    Map<String,String> tableIdMap = client.tableIdMap(creds);
+    String tableId = tableIdMap.get(table);
+    Key start = new Key();
+    start.row = s2bb(tableId + ";");
+    Key end = new Key();
+    end.row = s2bb(tableId + "<");
+    end = client.getFollowing(end, PartialKey.ROW);
+    ScanOptions opt = new ScanOptions();
+    opt.range = new Range(start, true, end, false);
+    opt.columns = Collections.singletonList(new ScanColumn(s2bb("file")));
+    String scanner = client.createScanner(creds, MetadataTable.NAME, opt);
+    int result = 0;
+    while (true) {
+      ScanResult more = client.nextK(scanner, 100);
+      result += more.getResults().size();
+      if (!more.more)
+        break;
+    }
+    return result;
+  }
+
+  private Map<ByteBuffer,List<ColumnUpdate>> mutation(String row, String cf, String cq, String value) {
+    ColumnUpdate upd = new ColumnUpdate(s2bb(cf), s2bb(cq));
+    upd.setValue(value.getBytes());
+    return Collections.singletonMap(s2bb(row), Collections.singletonList(upd));
+  }
+
+  private ByteBuffer s2bb(String cf) {
+    return ByteBuffer.wrap(cf.getBytes());
+  }
+
+  private Map<String,String> s2pp(String cf) {
+    Map<String,String> toRet = new TreeMap<String,String>();
+    toRet.put("password", cf);
+    return toRet;
+  }
+
+  @AfterClass
+  public static void tearDownMiniCluster() throws Exception {
+    accumulo.stop();
+  }
+}