accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ktur...@apache.org
Subject [accumulo] branch master updated: fix #816 abstract metadata and change root metadata schema (#1174)
Date Thu, 06 Jun 2019 18:05:12 GMT
This is an automated email from the ASF dual-hosted git repository.

kturner pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/master by this push:
     new 221aaf8  fix #816 abstract metadata and change root metadata schema (#1174)
221aaf8 is described below

commit 221aaf8f8805b5abfe07be5244dfbb28f8fc79d0
Author: Keith Turner <kturner@apache.org>
AuthorDate: Thu Jun 6 14:05:06 2019 -0400

    fix #816 abstract metadata and change root metadata schema (#1174)
    
    There are three major changes in this commit :
    
     * An abstraction layer for interacting with Accumulo's persisted
       metadata called Ample was introduced.  The goal is to eventually make
       all metadata read and write operations use Ample.
     * How the root tablet's metadata is stored in zookeeper was changed. It
       was changed to use a single zookeeper node (which is good for making
       atomic updates to multiple fields). In this single zookeeper node a
       json value is stored.  This json value has the same schema as all
       other metadata tablets, it uses the same column families and
       qualifiers.  This makes updating the json using Accumulo mutations
       easy and reading the json as Accumulo Key values easy.
     * Alot of the root tablet code that used to interact directly with
       Zookeeper was updated to use Ample.  In follow on changes, a lot of
       specialized root tablet code can be completely removed.  Those
       changes were not made in this commit inorder to keep it from
       becoming too large, making it hard to review.
    
    This change is starting point to support many other changes like #816,  #817, #936, #1121
---
 .../accumulo/core/client/ZooKeeperInstance.java    |  16 +-
 .../accumulo/core/clientImpl/ClientContext.java    |  20 +-
 .../accumulo/core/clientImpl/OfflineIterator.java  |   5 +-
 .../core/clientImpl/RootTabletLocator.java         |  19 +-
 .../core/clientImpl/TableOperationsImpl.java       |   4 +-
 .../clientImpl/bulk/ConcurrentKeyExtentCache.java  |   4 +-
 .../apache/accumulo/core/metadata/RootTable.java   |   8 +-
 .../accumulo/core/metadata/schema/Ample.java       | 169 ++++++++++++
 .../accumulo/core/metadata/schema/AmpleImpl.java   |  29 +-
 .../core/metadata/schema/RootTabletMetadata.java   | 189 +++++++++++++
 .../core/metadata/schema/TabletMetadata.java       |  42 +--
 .../core/metadata/schema/TabletsMetadata.java      | 206 +++++++-------
 .../org/apache/accumulo/core/summary/Gatherer.java |  13 +-
 .../java/org/apache/accumulo/core/util/Merge.java  |   5 +-
 .../core/metadata/schema/TabletMetadataTest.java   |   9 +-
 .../apache/accumulo/server/ServerConstants.java    |  12 +-
 .../org/apache/accumulo/server/ServerContext.java  |   6 +
 .../org/apache/accumulo/server/fs/FileRef.java     |   5 +-
 .../org/apache/accumulo/server/fs/VolumeUtil.java  |   7 +-
 .../apache/accumulo/server/init/Initialize.java    |  11 +-
 .../server/master/balancer/GroupBalancer.java      |   6 +-
 .../accumulo/server/master/state/Assignment.java   |  14 +-
 .../server/master/state/TServerInstance.java       |   5 +-
 .../server/master/state/TabletStateStore.java      |   2 +-
 .../accumulo/server/master/state/ZooStore.java     |  95 -------
 .../server/master/state/ZooTabletStateStore.java   | 119 +++-----
 .../server/metadata/RootTabletMutatorImpl.java     | 121 +++++++++
 .../accumulo/server/metadata/ServerAmpleImpl.java  | 111 ++++++++
 .../server/metadata/TabletMutatorBase.java         | 162 +++++++++++
 .../TabletMutatorImpl.java}                        |  32 ++-
 .../server/metadata/TabletsMutatorImpl.java        |  91 +++++++
 .../accumulo/server/util/FindOfflineTablets.java   |   3 +-
 .../accumulo/server/util/ListVolumesUsed.java      |  11 +-
 .../accumulo/server/util/MasterMetadataUtil.java   |  55 ++--
 .../accumulo/server/util/MetadataTableUtil.java    | 302 ++++-----------------
 .../apache/accumulo/gc/SimpleGarbageCollector.java |   5 +-
 .../java/org/apache/accumulo/master/Master.java    |  17 +-
 .../master/MasterClientServiceHandler.java         |   8 +-
 .../apache/accumulo/master/TabletGroupWatcher.java |   3 +-
 .../master/tableOps/bulkVer2/LoadFiles.java        |   5 +-
 .../master/tableOps/bulkVer2/PrepBulkImport.java   |   3 +-
 .../master/tableOps/compact/CompactionDriver.java  |   8 +-
 .../accumulo/master/upgrade/Upgrader9to10.java     | 223 +++++++++++++++
 .../master/state/RootTabletStateStoreTest.java     | 128 ++-------
 .../org/apache/accumulo/tserver/TabletServer.java  |  29 +-
 .../apache/accumulo/tserver/tablet/TabletData.java |  10 +-
 .../java/org/apache/accumulo/test/VolumeIT.java    |  17 +-
 .../accumulo/test/functional/BulkLoadIT.java       |   7 +-
 48 files changed, 1543 insertions(+), 828 deletions(-)

diff --git a/core/src/main/java/org/apache/accumulo/core/client/ZooKeeperInstance.java b/core/src/main/java/org/apache/accumulo/core/client/ZooKeeperInstance.java
index 58ff546..9b3bd24 100644
--- a/core/src/main/java/org/apache/accumulo/core/client/ZooKeeperInstance.java
+++ b/core/src/main/java/org/apache/accumulo/core/client/ZooKeeperInstance.java
@@ -33,7 +33,9 @@ import org.apache.accumulo.core.clientImpl.ClientInfoImpl;
 import org.apache.accumulo.core.clientImpl.InstanceOperationsImpl;
 import org.apache.accumulo.core.conf.ClientProperty;
 import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
-import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
+import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
 import org.apache.accumulo.core.singletons.SingletonManager;
 import org.apache.accumulo.core.singletons.SingletonManager.Mode;
 import org.apache.accumulo.core.util.OpTimer;
@@ -174,8 +176,6 @@ public class ZooKeeperInstance implements Instance {
 
   @Override
   public String getRootTabletLocation() {
-    String zRootLocPath = ZooUtil.getRoot(getInstanceID()) + RootTable.ZROOT_TABLET_LOCATION;
-
     OpTimer timer = null;
 
     if (log.isTraceEnabled()) {
@@ -184,20 +184,20 @@ public class ZooKeeperInstance implements Instance {
       timer = new OpTimer().start();
     }
 
-    byte[] loc = zooCache.get(zRootLocPath);
+    Location loc =
+        TabletsMetadata.getRootMetadata(ZooUtil.getRoot(getInstanceID()), zooCache).getLocation();
 
     if (timer != null) {
       timer.stop();
-      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(),
-          (loc == null ? "null" : new String(loc, UTF_8)),
+      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(), loc,
           String.format("%.3f secs", timer.scale(TimeUnit.SECONDS)));
     }
 
-    if (loc == null) {
+    if (loc == null || loc.getType() != LocationType.CURRENT) {
       return null;
     }
 
-    return new String(loc, UTF_8).split("\\|")[0];
+    return loc.getHostAndPort().toString();
   }
 
   @Override
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java
index 01474b2..fa8b73e 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/ClientContext.java
@@ -18,6 +18,7 @@ package org.apache.accumulo.core.clientImpl;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
 
 import java.nio.file.Path;
 import java.util.Collections;
@@ -55,6 +56,10 @@ import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.master.state.tables.TableState;
 import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.AmpleImpl;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.rpc.SaslConnectionParams;
 import org.apache.accumulo.core.rpc.SslConnectionParams;
 import org.apache.accumulo.core.security.Authorizations;
@@ -200,6 +205,11 @@ public class ClientContext implements AccumuloClient {
     };
   }
 
+  public Ample getAmple() {
+    ensureOpen();
+    return new AmpleImpl(this);
+  }
+
   /**
    * Retrieve the credentials used to construct this context
    */
@@ -325,7 +335,6 @@ public class ClientContext implements AccumuloClient {
    */
   public String getRootTabletLocation() {
     ensureOpen();
-    String zRootLocPath = getZooKeeperRoot() + RootTable.ZROOT_TABLET_LOCATION;
 
     OpTimer timer = null;
 
@@ -335,20 +344,19 @@ public class ClientContext implements AccumuloClient {
       timer = new OpTimer().start();
     }
 
-    byte[] loc = zooCache.get(zRootLocPath);
+    Location loc = getAmple().readTablet(RootTable.EXTENT, LOCATION).getLocation();
 
     if (timer != null) {
       timer.stop();
-      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(),
-          (loc == null ? "null" : new String(loc, UTF_8)),
+      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(), loc,
           String.format("%.3f secs", timer.scale(TimeUnit.SECONDS)));
     }
 
-    if (loc == null) {
+    if (loc == null || loc.getType() != LocationType.CURRENT) {
       return null;
     }
 
-    return new String(loc, UTF_8).split("\\|")[0];
+    return loc.getHostAndPort().toString();
   }
 
   /**
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
index 9abe346..38394da 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/OfflineIterator.java
@@ -16,6 +16,9 @@
  */
 package org.apache.accumulo.core.clientImpl;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.io.IOException;
@@ -286,7 +289,7 @@ class OfflineIterator implements Iterator<Entry<Key,Value>> {
 
   private TabletMetadata getTabletFiles(Range nextRange) {
     try (TabletsMetadata tablets = TabletsMetadata.builder().scanMetadataTable()
-        .overRange(nextRange).fetchFiles().fetchLocation().fetchPrev().build(context)) {
+        .overRange(nextRange).fetch(FILES, LOCATION, PREV_ROW).build(context)) {
       return tablets.iterator().next();
     }
   }
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/RootTabletLocator.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/RootTabletLocator.java
index e4e9d2e..1797ec3 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/RootTabletLocator.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/RootTabletLocator.java
@@ -16,6 +16,7 @@
  */
 package org.apache.accumulo.core.clientImpl;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.util.Collection;
@@ -30,6 +31,8 @@ import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.util.OpTimer;
 import org.apache.accumulo.fate.zookeeper.ZooCache;
 import org.apache.hadoop.io.Text;
@@ -91,9 +94,6 @@ public class RootTabletLocator extends TabletLocator {
   public void invalidateCache() {}
 
   protected TabletLocation getRootTabletLocation(ClientContext context) {
-    String zRootLocPath = context.getZooKeeperRoot() + RootTable.ZROOT_TABLET_LOCATION;
-    ZooCache zooCache = context.getZooCache();
-
     Logger log = LoggerFactory.getLogger(this.getClass());
 
     OpTimer timer = null;
@@ -104,23 +104,22 @@ public class RootTabletLocator extends TabletLocator {
       timer = new OpTimer().start();
     }
 
-    byte[] loc = zooCache.get(zRootLocPath);
+    Location loc = context.getAmple().readTablet(RootTable.EXTENT, LOCATION).getLocation();
 
     if (timer != null) {
       timer.stop();
-      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(),
-          (loc == null ? "null" : new String(loc)),
+      log.trace("tid={} Found root tablet at {} in {}", Thread.currentThread().getId(), loc,
           String.format("%.3f secs", timer.scale(TimeUnit.SECONDS)));
     }
 
-    if (loc == null) {
+    if (loc == null || loc.getType() != LocationType.CURRENT) {
       return null;
     }
 
-    String[] tokens = new String(loc).split("\\|");
+    String server = loc.getHostAndPort().toString();
 
-    if (lockChecker.isLockHeld(tokens[0], tokens[1]))
-      return new TabletLocation(RootTable.EXTENT, tokens[0], tokens[1]);
+    if (lockChecker.isLockHeld(server, loc.getSession()))
+      return new TabletLocation(RootTable.EXTENT, server, loc.getSession());
     else
       return null;
   }
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
index 0861faf..ea6e27b 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
@@ -22,6 +22,8 @@ import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toSet;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.io.BufferedReader;
@@ -1240,7 +1242,7 @@ public class TableOperationsImpl extends TableOperationsHelper {
         range = new Range(startRow, lastRow);
 
       TabletsMetadata tablets = TabletsMetadata.builder().scanMetadataTable().overRange(range)
-          .fetchLocation().fetchPrev().build(context);
+          .fetch(LOCATION, PREV_ROW).build(context);
 
       KeyExtent lastExtent = null;
 
diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/bulk/ConcurrentKeyExtentCache.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/bulk/ConcurrentKeyExtentCache.java
index 6c3ed42..88d4bc1 100644
--- a/core/src/main/java/org/apache/accumulo/core/clientImpl/bulk/ConcurrentKeyExtentCache.java
+++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/bulk/ConcurrentKeyExtentCache.java
@@ -16,6 +16,8 @@
  */
 package org.apache.accumulo.core.clientImpl.bulk;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -84,7 +86,7 @@ class ConcurrentKeyExtentCache implements KeyExtentCache {
   protected Stream<KeyExtent> lookupExtents(Text row)
       throws TableNotFoundException, AccumuloException, AccumuloSecurityException {
     return TabletsMetadata.builder().forTable(tableId).overlapping(row, null).checkConsistency()
-        .fetchPrev().build(ctx).stream().limit(100).map(TabletMetadata::getExtent);
+        .fetch(PREV_ROW).build(ctx).stream().limit(100).map(TabletMetadata::getExtent);
   }
 
   @Override
diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/RootTable.java b/core/src/main/java/org/apache/accumulo/core/metadata/RootTable.java
index 1d3b330..4e08c46 100644
--- a/core/src/main/java/org/apache/accumulo/core/metadata/RootTable.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/RootTable.java
@@ -33,15 +33,9 @@ public class RootTable {
   public static final String ROOT_TABLET_LOCATION = "/root_tablet";
 
   /**
-   * ZK path relative to the instance directory for information about the root tablet
+   * ZK path relative to the zookeeper node where the root tablet metadata is stored.
    */
   public static final String ZROOT_TABLET = ROOT_TABLET_LOCATION;
-  public static final String ZROOT_TABLET_LOCATION = ZROOT_TABLET + "/location";
-  public static final String ZROOT_TABLET_FUTURE_LOCATION = ZROOT_TABLET + "/future_location";
-  public static final String ZROOT_TABLET_LAST_LOCATION = ZROOT_TABLET + "/lastlocation";
-  public static final String ZROOT_TABLET_WALOGS = ZROOT_TABLET + "/walogs";
-  public static final String ZROOT_TABLET_CURRENT_LOGS = ZROOT_TABLET + "/current_logs";
-  public static final String ZROOT_TABLET_PATH = ZROOT_TABLET + "/dir";
 
   public static final KeyExtent EXTENT = new KeyExtent(ID, null, null);
   public static final KeyExtent OLD_EXTENT =
diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
new file mode 100644
index 0000000..ce470e4
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
@@ -0,0 +1,169 @@
+/*
+ * 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.core.metadata.schema;
+
+import java.util.Collection;
+
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
+import org.apache.accumulo.core.tabletserver.log.LogEntry;
+import org.apache.accumulo.core.util.HostAndPort;
+import org.apache.accumulo.fate.zookeeper.ZooLock;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.Text;
+
+/**
+ * Accumulo Metadata Persistence Layer. Entry point and abstractions layer for reading and updating
+ * persisted Accumulo metadata. This metadata may be stored in Zookeeper or in Accumulo system
+ * tables.
+ *
+ * <p>
+ * This interface seeks to satisfy the following goals.
+ *
+ * <UL>
+ * <LI>Provide a single entry point for all reading and writing of Accumulo Metadata.
+ * <LI>The root tablet persists its data in Zookeeper. Metadata tablets persist their data in root
+ * tablet. All other tablets persist their data in the metadata table. This interface abstracts how
+ * and where information for a tablet is actually persisted.
+ * <LI>Before the creation of this interface, many concurrent metadata table updates resulted in
+ * separate synchronous RPCs. The design of this interface allows batching of metadata table updates
+ * within a tablet server for cluster wide efficiencies. Batching is not required by
+ * implementations, but the design of the interface makes it possible.
+ * <LI>Make code that updates Accumulo persistent metadata more concise. Before this interface
+ * existed, there was a lot of redundant and verbose code for updating metadata.
+ * <LI>Reduce specialized code for the root tablet. Currently there is specialized code to manage
+ * the root tablets files that is different from all other tablets. This interface is the beginning
+ * of an effort to remove this specialized code. See #936
+ * </UL>
+ */
+public interface Ample {
+
+  /**
+   * Read a single tablets metadata. No checking is done for prev row, so it could differ.
+   *
+   * @param extent
+   *          Reads tablet metadata using the table id and end row from this extent.
+   * @param colsToFetch
+   *          What tablets columns to fetch. If empty, then everything is fetched.
+   */
+  TabletMetadata readTablet(KeyExtent extent, ColumnType... colsToFetch);
+
+  /**
+   * Initiates mutating a single tablets persistent metadata. No data is persisted until the
+   * {@code mutate()} method is called on the returned object. If updating multiple tablets,
+   * consider using {@link #mutateTablets()}
+   *
+   * @param extent
+   *          Mutates a tablet that has this table id and end row. The prev end row is not
+   *          considered or checked.
+   */
+  default TabletMutator mutateTablet(KeyExtent extent) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Use this when updating multiple tablets. Ensure the returns TabletsMutator is closed, or data
+   * may not be persisted.
+   */
+  default TabletsMutator mutateTablets() {
+    throw new UnsupportedOperationException();
+  }
+
+  default void putGcCandidates(TableId tableId, Collection<? extends Ample.FileMeta> candidates) {
+    throw new UnsupportedOperationException();
+  }
+
+  default void deleteGcCandidates(TableId tableId, Collection<String> paths) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * This interface allows efficiently updating multiple tablets. Unless close is called, changes
+   * may not be persisted.
+   */
+  public interface TabletsMutator extends AutoCloseable {
+    TabletMutator mutateTablet(KeyExtent extent);
+
+    @Override
+    void close();
+  }
+
+  /**
+   * Temporary interface, place holder for some server side types like TServerInstance. Need to
+   * simplify and possibly combine these type.
+   */
+  interface TServer {
+    HostAndPort getLocation();
+
+    String getSession();
+  }
+
+  /**
+   * Temporary interface, place holder for the server side type FileRef. Need to simplify this type.
+   */
+  interface FileMeta {
+    public Text meta();
+
+    public Path path();
+  }
+
+  /**
+   * Interface for changing a tablets persistent data.
+   */
+  interface TabletMutator {
+    public TabletMutator putPrevEndRow(Text per);
+
+    public TabletMutator putFile(FileMeta path, DataFileValue dfv);
+
+    public TabletMutator putScan(FileMeta path);
+
+    public TabletMutator deleteFile(FileMeta path);
+
+    public TabletMutator putCompactionId(long compactionId);
+
+    public TabletMutator putLocation(TServer tserver, LocationType type);
+
+    public TabletMutator deleteLocation(TServer tserver, LocationType type);
+
+    public TabletMutator putZooLock(ZooLock zooLock);
+
+    public TabletMutator putDir(String dir);
+
+    public TabletMutator putWal(LogEntry logEntry);
+
+    public TabletMutator deleteWal(String wal);
+
+    public TabletMutator deleteWal(LogEntry logEntry);
+
+    /**
+     * This method persist (or queues for persisting) previous put and deletes against this object.
+     * Unless this method is called, previous calls will never be persisted. The purpose of this
+     * method is to prevent partial changes in the case of an exception.
+     *
+     * <p>
+     * Implementors of this interface should ensure either all requested changes are persisted or
+     * none.
+     *
+     * <p>
+     * After this method is called, calling any method on this object will result in an exception.
+     */
+    public void mutate();
+  }
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/AmpleImpl.java
similarity index 51%
copy from server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java
copy to core/src/main/java/org/apache/accumulo/core/metadata/schema/AmpleImpl.java
index 1495068..c7411b7 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/AmpleImpl.java
@@ -14,16 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.accumulo.server.master.state;
 
+package org.apache.accumulo.core.metadata.schema;
+
+import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
+import org.apache.accumulo.core.metadata.schema.TabletsMetadata.Options;
+
+import com.google.common.collect.Iterables;
+
+public class AmpleImpl implements Ample {
+  private final AccumuloClient client;
+
+  public AmpleImpl(AccumuloClient client) {
+    this.client = client;
+  }
 
-public class Assignment {
-  public KeyExtent tablet;
-  public TServerInstance server;
+  @Override
+  public TabletMetadata readTablet(KeyExtent extent, ColumnType... colsToFetch) {
+    Options builder = TabletsMetadata.builder().forTablet(extent);
+    if (colsToFetch.length > 0)
+      builder.fetch(colsToFetch);
 
-  public Assignment(KeyExtent tablet, TServerInstance server) {
-    this.tablet = tablet;
-    this.server = server;
+    try (TabletsMetadata tablets = builder.build(client)) {
+      return Iterables.getOnlyElement(tablets);
+    }
   }
 }
diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/RootTabletMetadata.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/RootTabletMetadata.java
new file mode 100644
index 0000000..59b4244
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/RootTabletMetadata.java
@@ -0,0 +1,189 @@
+/*
+ * 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.core.metadata.schema;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.data.ArrayByteSequence;
+import org.apache.accumulo.core.data.ByteSequence;
+import org.apache.accumulo.core.data.ColumnUpdate;
+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.RootTable;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
+import org.apache.hadoop.io.Text;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Serializes the root tablet metadata as Json using Accumulo's standard metadata table schema.
+ */
+public class RootTabletMetadata {
+
+  private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+  private static final ByteSequence CURR_LOC_FAM =
+      new ArrayByteSequence(TabletsSection.CurrentLocationColumnFamily.STR_NAME);
+  private static final ByteSequence FUTURE_LOC_FAM =
+      new ArrayByteSequence(TabletsSection.FutureLocationColumnFamily.STR_NAME);
+
+  private TreeMap<Key,Value> entries = new TreeMap<>();
+
+  // This class is used to serialize and deserialize root tablet metadata using GSon. Any changes to
+  // this class must consider persisted data.
+  private static class GSonData {
+    int version = 1;
+
+    // Map<column_family, Map<column_qualifier, value>>
+    Map<String,Map<String,String>> columnValues;
+  }
+
+  /**
+   * Apply a metadata table mutation to update internal json.
+   */
+  public void update(Mutation m) {
+    Preconditions.checkArgument(new Text(m.getRow()).equals(RootTable.EXTENT.getMetadataEntry()));
+
+    m.getUpdates().forEach(cup -> {
+      Preconditions.checkArgument(!cup.hasTimestamp());
+      Preconditions.checkArgument(cup.getColumnVisibility().length == 0);
+    });
+
+    for (ColumnUpdate cup : m.getUpdates()) {
+      Key newKey = new Key(m.getRow(), cup.getColumnFamily(), cup.getColumnQualifier(),
+          cup.getColumnVisibility(), 1, false, false);
+
+      if (cup.isDeleted()) {
+        entries.remove(newKey);
+      } else {
+        entries.put(newKey, new Value(cup.getValue()));
+      }
+    }
+
+    // Ensure there is ever only one location
+    long locsSeen = entries.keySet().stream().map(Key::getColumnFamilyData)
+        .filter(fam -> fam.equals(CURR_LOC_FAM) || fam.equals(FUTURE_LOC_FAM)).count();
+
+    if (locsSeen > 1) {
+      throw new IllegalStateException(
+          "After mutation, root tablet has multiple locations : " + m + " " + entries);
+    }
+
+  }
+
+  /**
+   * Convert json to tablet metadata. *
+   */
+  public TabletMetadata convertToTabletMetadata() {
+    return TabletMetadata.convertRow(entries.entrySet().iterator(), EnumSet.allOf(ColumnType.class),
+        false);
+  }
+
+  private static String bs2Str(byte[] bs) {
+    String str = new String(bs, UTF_8);
+
+    // The expectation is that all data stored in the root tablet can be converted to UTF8. This is
+    // a sanity check to ensure the byte sequence can be converted from byte[] to UTF8 to byte[] w/o
+    // data corruption. Not all byte arrays can be converted to UTF8.
+    Preconditions.checkArgument(Arrays.equals(bs, str.getBytes(UTF_8)),
+        "Unsuccessful conversion of %s to utf8", str);
+
+    return str;
+  }
+
+  /**
+   * @return a json representation of this object, use {@link #fromJson(String)} to convert the json
+   *         back to an object.
+   */
+  public String toJson() {
+    GSonData gd = new GSonData();
+    gd.columnValues = new TreeMap<>();
+
+    Set<Entry<Key,Value>> es = entries.entrySet();
+    for (Entry<Key,Value> entry : es) {
+      String fam = bs2Str(entry.getKey().getColumnFamilyData().toArray());
+      String qual = bs2Str(entry.getKey().getColumnQualifierData().toArray());
+      String val = bs2Str(entry.getValue().get());
+
+      gd.columnValues.computeIfAbsent(fam, k -> new TreeMap<>()).put(qual, val);
+    }
+
+    return GSON.toJson(gd);
+  }
+
+  /**
+   * Converts created by calling {@link #toJson()} back to an object.
+   */
+  public static RootTabletMetadata fromJson(String json) {
+    GSonData gd = GSON.fromJson(json, GSonData.class);
+
+    Preconditions.checkArgument(gd.version == 1);
+
+    String row = RootTable.EXTENT.getMetadataEntry().toString();
+
+    TreeMap<Key,Value> entries = new TreeMap<>();
+
+    gd.columnValues.forEach((fam, qualVals) -> {
+      qualVals.forEach((qual, val) -> {
+        Key k = new Key(row, fam, qual, 1);
+        Value v = new Value(val);
+
+        entries.put(k, v);
+      });
+    });
+
+    RootTabletMetadata rtm = new RootTabletMetadata();
+    rtm.entries = entries;
+
+    return rtm;
+  }
+
+  /**
+   * Converts created by calling {@link #toJson()} back to an object. Assumes the json is UTF8
+   * encoded.
+   */
+  public static RootTabletMetadata fromJson(byte[] bs) {
+    return fromJson(new String(bs, UTF_8));
+  }
+
+  /**
+   * Generate initial json for the root tablet metadata.
+   */
+  public static byte[] getInitialJson(String dir) {
+    Mutation mutation = RootTable.EXTENT.getPrevRowUpdateMutation();
+    TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN.put(mutation,
+        new Value(dir.getBytes(UTF_8)));
+
+    RootTabletMetadata rtm = new RootTabletMetadata();
+
+    rtm.update(mutation);
+
+    return rtm.toJson().getBytes(UTF_8);
+  }
+}
diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
index 3929425..bc5b64b 100644
--- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
@@ -76,7 +76,7 @@ public class TabletMetadata {
   private Map<String,DataFileValue> files;
   private List<String> scans;
   private Set<String> loadedFiles;
-  private EnumSet<FetchedColumns> fetchedCols;
+  private EnumSet<ColumnType> fetchedCols;
   private KeyExtent extent;
   private Location last;
   private String dir;
@@ -91,7 +91,7 @@ public class TabletMetadata {
     CURRENT, FUTURE, LAST
   }
 
-  public static enum FetchedColumns {
+  public static enum ColumnType {
     LOCATION, PREV_ROW, FILES, LAST, LOADED, SCANS, DIR, TIME, CLONED, FLUSH_ID, LOGS, COMPACT_ID
   }
 
@@ -142,12 +142,12 @@ public class TabletMetadata {
     return extent;
   }
 
-  private void ensureFetched(FetchedColumns col) {
+  private void ensureFetched(ColumnType col) {
     Preconditions.checkState(fetchedCols.contains(col), "%s was not fetched", col);
   }
 
   public Text getPrevEndRow() {
-    ensureFetched(FetchedColumns.PREV_ROW);
+    ensureFetched(ColumnType.PREV_ROW);
     if (!sawPrevEndRow)
       throw new IllegalStateException(
           "No prev endrow seen.  tableId: " + tableId + " endrow: " + endRow);
@@ -155,7 +155,7 @@ public class TabletMetadata {
   }
 
   public boolean sawPrevEndRow() {
-    ensureFetched(FetchedColumns.PREV_ROW);
+    ensureFetched(ColumnType.PREV_ROW);
     return sawPrevEndRow;
   }
 
@@ -164,67 +164,67 @@ public class TabletMetadata {
   }
 
   public Location getLocation() {
-    ensureFetched(FetchedColumns.LOCATION);
+    ensureFetched(ColumnType.LOCATION);
     return location;
   }
 
   public boolean hasCurrent() {
-    ensureFetched(FetchedColumns.LOCATION);
+    ensureFetched(ColumnType.LOCATION);
     return location != null && location.getType() == LocationType.CURRENT;
   }
 
   public Set<String> getLoaded() {
-    ensureFetched(FetchedColumns.LOADED);
+    ensureFetched(ColumnType.LOADED);
     return loadedFiles;
   }
 
   public Location getLast() {
-    ensureFetched(FetchedColumns.LAST);
+    ensureFetched(ColumnType.LAST);
     return last;
   }
 
   public Collection<String> getFiles() {
-    ensureFetched(FetchedColumns.FILES);
+    ensureFetched(ColumnType.FILES);
     return files.keySet();
   }
 
   public Map<String,DataFileValue> getFilesMap() {
-    ensureFetched(FetchedColumns.FILES);
+    ensureFetched(ColumnType.FILES);
     return files;
   }
 
   public Collection<LogEntry> getLogs() {
-    ensureFetched(FetchedColumns.LOGS);
+    ensureFetched(ColumnType.LOGS);
     return logs;
   }
 
   public List<String> getScans() {
-    ensureFetched(FetchedColumns.SCANS);
+    ensureFetched(ColumnType.SCANS);
     return scans;
   }
 
   public String getDir() {
-    ensureFetched(FetchedColumns.DIR);
+    ensureFetched(ColumnType.DIR);
     return dir;
   }
 
   public String getTime() {
-    ensureFetched(FetchedColumns.TIME);
+    ensureFetched(ColumnType.TIME);
     return time;
   }
 
   public String getCloned() {
-    ensureFetched(FetchedColumns.CLONED);
+    ensureFetched(ColumnType.CLONED);
     return cloned;
   }
 
   public OptionalLong getFlushId() {
-    ensureFetched(FetchedColumns.FLUSH_ID);
+    ensureFetched(ColumnType.FLUSH_ID);
     return flush;
   }
 
   public OptionalLong getCompactId() {
-    ensureFetched(FetchedColumns.COMPACT_ID);
+    ensureFetched(ColumnType.COMPACT_ID);
     return compact;
   }
 
@@ -234,7 +234,7 @@ public class TabletMetadata {
   }
 
   static TabletMetadata convertRow(Iterator<Entry<Key,Value>> rowIter,
-      EnumSet<FetchedColumns> fetchedColumns, boolean buildKeyValueMap) {
+      EnumSet<ColumnType> fetchedColumns, boolean buildKeyValueMap) {
     Objects.requireNonNull(rowIter);
 
     TabletMetadata te = new TabletMetadata();
@@ -340,7 +340,7 @@ public class TabletMetadata {
     location = new Location(val, qual, lt);
   }
 
-  static Iterable<TabletMetadata> convert(Scanner input, EnumSet<FetchedColumns> fetchedColumns,
+  static Iterable<TabletMetadata> convert(Scanner input, EnumSet<ColumnType> fetchedColumns,
       boolean checkConsistency, boolean buildKeyValueMap) {
 
     Range range = input.getRange();
@@ -367,7 +367,7 @@ public class TabletMetadata {
     te.sawPrevEndRow = true;
     te.prevEndRow = prevEndRow == null ? null : new Text(prevEndRow);
     te.endRow = endRow == null ? null : new Text(endRow);
-    te.fetchedCols = EnumSet.of(FetchedColumns.PREV_ROW);
+    te.fetchedCols = EnumSet.of(ColumnType.PREV_ROW);
     return te;
   }
 }
diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
index 0843f62..3af28b3 100644
--- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
@@ -24,6 +24,7 @@ import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSec
 import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily.PREV_ROW_COLUMN;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
@@ -35,6 +36,7 @@ import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.IsolatedScanner;
 import org.apache.accumulo.core.client.Scanner;
 import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.clientImpl.ClientContext;
 import org.apache.accumulo.core.data.Range;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
@@ -49,9 +51,10 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Fu
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LastLocationColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
-import org.apache.accumulo.core.metadata.schema.TabletMetadata.FetchedColumns;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.util.ColumnFQ;
+import org.apache.accumulo.fate.zookeeper.ZooCache;
 import org.apache.hadoop.io.Text;
 
 import com.google.common.base.Preconditions;
@@ -68,20 +71,40 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
     private List<ColumnFQ> qualifiers = new ArrayList<>();
     private String table = MetadataTable.NAME;
     private Range range;
-    private EnumSet<FetchedColumns> fetchedCols = EnumSet.noneOf(FetchedColumns.class);
+    private EnumSet<ColumnType> fetchedCols = EnumSet.noneOf(ColumnType.class);
     private Text endRow;
     private boolean checkConsistency = false;
     private boolean saveKeyValues;
     private TableId tableId;
 
+    // An internal constant that represents a fictional table where the root tablet stores its
+    // metadata
+    private static String SEED_TABLE = "accumulo.seed";
+
     @Override
     public TabletsMetadata build(AccumuloClient client) {
+      if (table.equals(SEED_TABLE)) {
+        return buildSeed(client);
+      } else {
+        return buildNonSeed(client);
+      }
+    }
+
+    private TabletsMetadata buildSeed(AccumuloClient client) {
+      ClientContext ctx = ((ClientContext) client);
+      ZooCache zc = ctx.getZooCache();
+      String zkRoot = ctx.getZooKeeperRoot();
+
+      return new TabletsMetadata(getRootMetadata(zkRoot, zc));
+    }
+
+    private TabletsMetadata buildNonSeed(AccumuloClient client) {
       try {
         Scanner scanner = new IsolatedScanner(client.createScanner(table, Authorizations.EMPTY));
         scanner.setRange(range);
 
-        if (checkConsistency && !fetchedCols.contains(FetchedColumns.PREV_ROW)) {
-          fetchPrev();
+        if (checkConsistency && !fetchedCols.contains(ColumnType.PREV_ROW)) {
+          fetch(ColumnType.PREV_ROW);
         }
 
         for (Text fam : families) {
@@ -93,7 +116,7 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
         }
 
         if (families.size() == 0 && qualifiers.size() == 0) {
-          fetchedCols = EnumSet.allOf(FetchedColumns.class);
+          fetchedCols = EnumSet.allOf(ColumnType.class);
         }
 
         Iterable<TabletMetadata> tmi =
@@ -118,95 +141,65 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
     }
 
     @Override
-    public Options fetchCloned() {
-      fetchedCols.add(FetchedColumns.CLONED);
-      families.add(ClonedColumnFamily.NAME);
-      return this;
-    }
-
-    @Override
-    public Options fetchCompactId() {
-      fetchedCols.add(FetchedColumns.COMPACT_ID);
-      qualifiers.add(COMPACT_COLUMN);
-      return this;
-    }
-
-    @Override
-    public Options fetchDir() {
-      fetchedCols.add(FetchedColumns.DIR);
-      qualifiers.add(DIRECTORY_COLUMN);
-      return this;
-    }
-
-    @Override
-    public Options fetchFiles() {
-      fetchedCols.add(FetchedColumns.FILES);
-      families.add(DataFileColumnFamily.NAME);
-      return this;
-    }
-
-    @Override
-    public Options fetchFlushId() {
-      fetchedCols.add(FetchedColumns.FLUSH_ID);
-      qualifiers.add(FLUSH_COLUMN);
-      return this;
-    }
-
-    @Override
-    public Options fetchLast() {
-      fetchedCols.add(FetchedColumns.LAST);
-      families.add(LastLocationColumnFamily.NAME);
-      return this;
-    }
-
-    @Override
-    public Options fetchLoaded() {
-      fetchedCols.add(FetchedColumns.LOADED);
-      families.add(BulkFileColumnFamily.NAME);
-      return this;
-    }
-
-    @Override
-    public Options fetchLocation() {
-      fetchedCols.add(FetchedColumns.LOCATION);
-      families.add(CurrentLocationColumnFamily.NAME);
-      families.add(FutureLocationColumnFamily.NAME);
-      return this;
-    }
+    public Options fetch(ColumnType... colsToFetch) {
+      Preconditions.checkArgument(colsToFetch.length > 0);
+
+      for (ColumnType colToFetch : colsToFetch) {
+
+        fetchedCols.add(colToFetch);
+
+        switch (colToFetch) {
+          case CLONED:
+            families.add(ClonedColumnFamily.NAME);
+            break;
+          case COMPACT_ID:
+            qualifiers.add(COMPACT_COLUMN);
+            break;
+          case DIR:
+            qualifiers.add(DIRECTORY_COLUMN);
+            break;
+          case FILES:
+            families.add(DataFileColumnFamily.NAME);
+            break;
+          case FLUSH_ID:
+            qualifiers.add(FLUSH_COLUMN);
+            break;
+          case LAST:
+            families.add(LastLocationColumnFamily.NAME);
+            break;
+          case LOADED:
+            families.add(BulkFileColumnFamily.NAME);
+            break;
+          case LOCATION:
+            families.add(CurrentLocationColumnFamily.NAME);
+            families.add(FutureLocationColumnFamily.NAME);
+            break;
+          case LOGS:
+            families.add(LogColumnFamily.NAME);
+            break;
+          case PREV_ROW:
+            qualifiers.add(PREV_ROW_COLUMN);
+            break;
+          case SCANS:
+            families.add(ScanFileColumnFamily.NAME);
+            break;
+          case TIME:
+            qualifiers.add(TIME_COLUMN);
+            break;
+          default:
+            throw new IllegalArgumentException("Unknown col type " + colToFetch);
 
-    @Override
-    public Options fetchLogs() {
-      fetchedCols.add(FetchedColumns.LOGS);
-      families.add(LogColumnFamily.NAME);
-      return this;
-    }
-
-    @Override
-    public Options fetchPrev() {
-      fetchedCols.add(FetchedColumns.PREV_ROW);
-      qualifiers.add(PREV_ROW_COLUMN);
-      return this;
-    }
-
-    @Override
-    public Options fetchScans() {
-      fetchedCols.add(FetchedColumns.SCANS);
-      families.add(ScanFileColumnFamily.NAME);
-      return this;
-    }
+        }
+      }
 
-    @Override
-    public Options fetchTime() {
-      fetchedCols.add(FetchedColumns.TIME);
-      qualifiers.add(TIME_COLUMN);
       return this;
     }
 
     @Override
     public TableRangeOptions forTable(TableId tableId) {
-      Preconditions.checkArgument(!tableId.equals(RootTable.ID),
-          "Getting tablet metadata for " + RootTable.NAME + " not supported at this time.");
-      if (tableId.equals(MetadataTable.ID)) {
+      if (tableId.equals(RootTable.ID)) {
+        this.table = SEED_TABLE;
+      } else if (tableId.equals(MetadataTable.ID)) {
         this.table = RootTable.NAME;
       } else {
         this.table = MetadataTable.NAME;
@@ -246,6 +239,7 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
 
     @Override
     public RangeOptions scanTable(String tableName) {
+      Preconditions.checkArgument(!tableName.equals(SEED_TABLE));
       this.table = tableName;
       this.range = TabletsSection.getRange();
       return this;
@@ -260,29 +254,7 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
      */
     Options checkConsistency();
 
-    Options fetchCloned();
-
-    Options fetchCompactId();
-
-    Options fetchDir();
-
-    Options fetchFiles();
-
-    Options fetchFlushId();
-
-    Options fetchLast();
-
-    Options fetchLoaded();
-
-    Options fetchLocation();
-
-    Options fetchLogs();
-
-    Options fetchPrev();
-
-    Options fetchScans();
-
-    Options fetchTime();
+    Options fetch(ColumnType... columnsToFetch);
 
     /**
      * Saves the key values seen in the metadata table for each tablet.
@@ -378,10 +350,20 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
     return new Builder();
   }
 
+  public static TabletMetadata getRootMetadata(String zkRoot, ZooCache zc) {
+    return RootTabletMetadata.fromJson(zc.get(zkRoot + RootTable.ZROOT_TABLET))
+        .convertToTabletMetadata();
+  }
+
   private Scanner scanner;
 
   private Iterable<TabletMetadata> tablets;
 
+  private TabletsMetadata(TabletMetadata tm) {
+    this.scanner = null;
+    this.tablets = Collections.singleton(tm);
+  }
+
   private TabletsMetadata(Scanner scanner, Iterable<TabletMetadata> tmi) {
     this.scanner = scanner;
     this.tablets = tmi;
@@ -389,7 +371,9 @@ public class TabletsMetadata implements Iterable<TabletMetadata>, AutoCloseable
 
   @Override
   public void close() {
-    scanner.close();
+    if (scanner != null) {
+      scanner.close();
+    }
   }
 
   @Override
diff --git a/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java b/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
index b522487..65430b6 100644
--- a/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
+++ b/core/src/main/java/org/apache/accumulo/core/summary/Gatherer.java
@@ -18,6 +18,10 @@
 package org.apache.accumulo.core.summary;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LAST;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -163,9 +167,8 @@ public class Gatherer {
   private Map<String,Map<String,List<TRowRange>>>
       getFilesGroupedByLocation(Predicate<String> fileSelector) {
 
-    Iterable<TabletMetadata> tmi =
-        TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow).fetchFiles()
-            .fetchLocation().fetchLast().fetchPrev().build(ctx);
+    Iterable<TabletMetadata> tmi = TabletsMetadata.builder().forTable(tableId)
+        .overlapping(startRow, endRow).fetch(FILES, LOCATION, LAST, PREV_ROW).build(ctx);
 
     // get a subset of files
     Map<String,List<TabletMetadata>> files = new HashMap<>();
@@ -527,8 +530,8 @@ public class Gatherer {
 
   private int countFiles() {
     // TODO use a batch scanner + iterator to parallelize counting files
-    return TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow).fetchFiles()
-        .fetchPrev().build(ctx).stream().mapToInt(tm -> tm.getFiles().size()).sum();
+    return TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow)
+        .fetch(FILES, PREV_ROW).build(ctx).stream().mapToInt(tm -> tm.getFiles().size()).sum();
   }
 
   private class GatherRequest implements Supplier<SummaryCollection> {
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Merge.java b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
index 26e927d..0e81ba9 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Merge.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
@@ -16,6 +16,9 @@
  */
 package org.apache.accumulo.core.util;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -231,7 +234,7 @@ public class Merge {
       ClientContext context = (ClientContext) client;
       tableId = Tables.getTableId(context, tablename);
       tablets = TabletsMetadata.builder().scanMetadataTable()
-          .overRange(new KeyExtent(tableId, end, start).toMetadataRange()).fetchFiles().fetchPrev()
+          .overRange(new KeyExtent(tableId, end, start).toMetadataRange()).fetch(FILES, PREV_ROW)
           .build(context);
     } catch (Exception e) {
       throw new MergeException(e);
diff --git a/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java b/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
index 9232f0c..9d0ba57 100644
--- a/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
@@ -42,7 +42,7 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Da
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.FutureLocationColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LastLocationColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
-import org.apache.accumulo.core.metadata.schema.TabletMetadata.FetchedColumns;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
 import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.core.util.HostAndPort;
@@ -92,7 +92,7 @@ public class TabletMetadataTest {
     SortedMap<Key,Value> rowMap = toRowMap(mutation);
 
     TabletMetadata tm = TabletMetadata.convertRow(rowMap.entrySet().iterator(),
-        EnumSet.allOf(FetchedColumns.class), true);
+        EnumSet.allOf(ColumnType.class), true);
 
     assertEquals("OK", tm.getCloned());
     assertEquals(5L, tm.getCompactId().getAsLong());
@@ -131,7 +131,7 @@ public class TabletMetadataTest {
     SortedMap<Key,Value> rowMap = toRowMap(mutation);
 
     TabletMetadata tm = TabletMetadata.convertRow(rowMap.entrySet().iterator(),
-        EnumSet.allOf(FetchedColumns.class), false);
+        EnumSet.allOf(ColumnType.class), false);
 
     assertEquals(extent, tm.getExtent());
     assertEquals(HostAndPort.fromParts("server1", 8555), tm.getLocation().getHostAndPort());
@@ -150,8 +150,7 @@ public class TabletMetadataTest {
 
     SortedMap<Key,Value> rowMap = toRowMap(mutation);
 
-    TabletMetadata.convertRow(rowMap.entrySet().iterator(), EnumSet.allOf(FetchedColumns.class),
-        false);
+    TabletMetadata.convertRow(rowMap.entrySet().iterator(), EnumSet.allOf(ColumnType.class), false);
   }
 
   private SortedMap<Key,Value> toRowMap(Mutation mutation) {
diff --git a/server/base/src/main/java/org/apache/accumulo/server/ServerConstants.java b/server/base/src/main/java/org/apache/accumulo/server/ServerConstants.java
index 62a4821..4cd597a 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/ServerConstants.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/ServerConstants.java
@@ -44,10 +44,17 @@ public class ServerConstants {
   public static final String INSTANCE_ID_DIR = "instance_id";
 
   /**
+   * version (10) reflects changes to how root tablet metadata is serialized in zookeeper starting
+   * with 2.1
+   */
+  public static final int ROOT_TABLET_META_CHANGES = 10;
+
+  /**
    * version (9) reflects changes to crypto that resulted in RFiles and WALs being serialized
    * differently in version 2.0.0. Also RFiles in 2.0.0 may have summary data.
    */
   public static final int CRYPTO_CHANGES = 9;
+
   /**
    * version (8) reflects changes to RFile index (ACCUMULO-1124) AND the change to WAL tracking in
    * ZK in version 1.8.0
@@ -66,9 +73,10 @@ public class ServerConstants {
    *
    *
    */
-  public static final int DATA_VERSION = CRYPTO_CHANGES;
+  public static final int DATA_VERSION = ROOT_TABLET_META_CHANGES;
 
-  public static final Set<Integer> CAN_RUN = ImmutableSet.of(SHORTEN_RFILE_KEYS, DATA_VERSION);
+  public static final Set<Integer> CAN_RUN =
+      ImmutableSet.of(SHORTEN_RFILE_KEYS, CRYPTO_CHANGES, DATA_VERSION);
   public static final Set<Integer> NEEDS_UPGRADE =
       Sets.difference(CAN_RUN, ImmutableSet.of(DATA_VERSION));
 
diff --git a/server/base/src/main/java/org/apache/accumulo/server/ServerContext.java b/server/base/src/main/java/org/apache/accumulo/server/ServerContext.java
index ac9415a..d2345fe 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/ServerContext.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/ServerContext.java
@@ -28,11 +28,13 @@ import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.conf.SiteConfiguration;
 import org.apache.accumulo.core.crypto.CryptoServiceFactory;
 import org.apache.accumulo.core.crypto.CryptoServiceFactory.ClassloaderType;
+import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.core.rpc.SslConnectionParams;
 import org.apache.accumulo.core.spi.crypto.CryptoService;
 import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
 import org.apache.accumulo.server.conf.ServerConfigurationFactory;
 import org.apache.accumulo.server.fs.VolumeManager;
+import org.apache.accumulo.server.metadata.ServerAmpleImpl;
 import org.apache.accumulo.server.rpc.SaslServerConnectionParams;
 import org.apache.accumulo.server.rpc.ThriftServerType;
 import org.apache.accumulo.server.security.SecurityUtil;
@@ -209,4 +211,8 @@ public class ServerContext extends ClientContext {
     return cryptoService;
   }
 
+  @Override
+  public Ample getAmple() {
+    return new ServerAmpleImpl(this);
+  }
 }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/fs/FileRef.java b/server/base/src/main/java/org/apache/accumulo/server/fs/FileRef.java
index f0f21ae..361aaab 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/fs/FileRef.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/fs/FileRef.java
@@ -17,6 +17,7 @@
 package org.apache.accumulo.server.fs;
 
 import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.server.fs.VolumeManager.FileType;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.Text;
@@ -26,7 +27,7 @@ import org.apache.hadoop.io.Text;
  * contain old relative file references. This class keeps track of the short file reference, so it
  * can be removed properly from the metadata tables.
  */
-public class FileRef implements Comparable<FileRef> {
+public class FileRef implements Ample.FileMeta, Comparable<FileRef> {
   private String metaReference; // something like ../2/d-00000/A00001.rf
   private Path fullReference; // something like hdfs://nn:9001/accumulo/tables/2/d-00000/A00001.rf
   private Path suffix;
@@ -50,10 +51,12 @@ public class FileRef implements Comparable<FileRef> {
     return fullReference.toString();
   }
 
+  @Override
   public Path path() {
     return fullReference;
   }
 
+  @Override
   public Text meta() {
     return new Text(metaReference);
   }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/fs/VolumeUtil.java b/server/base/src/main/java/org/apache/accumulo/server/fs/VolumeUtil.java
index 6805962..04d1d1f 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/fs/VolumeUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/fs/VolumeUtil.java
@@ -27,6 +27,7 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 
 import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.metadata.schema.DataFileValue;
 import org.apache.accumulo.core.protobuf.ProtobufUtil;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
@@ -162,7 +163,7 @@ public class VolumeUtil {
     String newLocation = switchVolume(location, FileType.TABLE,
         ServerConstants.getVolumeReplacements(context.getConfiguration(), context.getHadoopConf()));
     if (newLocation != null) {
-      MetadataTableUtil.setRootTabletDir(context, newLocation);
+      context.getAmple().mutateTablet(RootTable.EXTENT).putDir(newLocation).mutate();
       log.info("Volume replaced: {} -> {}", location, newLocation);
       return new Path(newLocation).toString();
     }
@@ -302,7 +303,7 @@ public class VolumeUtil {
 
         // only set the new location in zookeeper after a successful copy
         log.info("setting root tablet location to {}", newDir);
-        MetadataTableUtil.setRootTabletDir(context, newDir.toString());
+        context.getAmple().mutateTablet(RootTable.EXTENT).putDir(newDir.toString()).mutate();
 
         // rename the old dir to avoid confusion when someone looks at filesystem... its ok if we
         // fail here and this does not happen because the location in
@@ -313,7 +314,7 @@ public class VolumeUtil {
 
       } else {
         log.info("setting root tablet location to {}", newDir);
-        MetadataTableUtil.setRootTabletDir(context, newDir.toString());
+        context.getAmple().mutateTablet(RootTable.EXTENT).putDir(newDir.toString()).mutate();
       }
 
       return newDir.toString();
diff --git a/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java b/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
index 2f855b8..366f547 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
@@ -69,6 +69,7 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Fu
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
+import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
 import org.apache.accumulo.core.replication.ReplicationConstants;
 import org.apache.accumulo.core.replication.ReplicationSchema.StatusSection;
 import org.apache.accumulo.core.replication.ReplicationSchema.WorkSection;
@@ -639,14 +640,8 @@ public class Initialize implements KeywordExecutable {
         NodeExistsPolicy.FAIL);
     zoo.putPersistentData(zkInstanceRoot + Constants.ZPROBLEMS, EMPTY_BYTE_ARRAY,
         NodeExistsPolicy.FAIL);
-    zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET, EMPTY_BYTE_ARRAY,
-        NodeExistsPolicy.FAIL);
-    zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET_WALOGS, EMPTY_BYTE_ARRAY,
-        NodeExistsPolicy.FAIL);
-    zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET_CURRENT_LOGS, EMPTY_BYTE_ARRAY,
-        NodeExistsPolicy.FAIL);
-    zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET_PATH,
-        rootTabletDir.getBytes(UTF_8), NodeExistsPolicy.FAIL);
+    zoo.putPersistentData(zkInstanceRoot + RootTable.ZROOT_TABLET,
+        RootTabletMetadata.getInitialJson(rootTabletDir), NodeExistsPolicy.FAIL);
     zoo.putPersistentData(zkInstanceRoot + Constants.ZMASTERS, EMPTY_BYTE_ARRAY,
         NodeExistsPolicy.FAIL);
     zoo.putPersistentData(zkInstanceRoot + Constants.ZMASTER_LOCK, EMPTY_BYTE_ARRAY,
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/balancer/GroupBalancer.java b/server/base/src/main/java/org/apache/accumulo/server/master/balancer/GroupBalancer.java
index ad80156..4dc910b 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/balancer/GroupBalancer.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/master/balancer/GroupBalancer.java
@@ -19,6 +19,8 @@ package org.apache.accumulo.server.master.balancer;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -79,8 +81,8 @@ public abstract class GroupBalancer extends TabletBalancer {
   protected Iterable<Pair<KeyExtent,Location>> getLocationProvider() {
     return () -> {
       try {
-        return TabletsMetadata.builder().forTable(tableId).fetchLocation().fetchPrev()
-            .build(context).stream().map(tm -> {
+        return TabletsMetadata.builder().forTable(tableId).fetch(LOCATION, PREV_ROW).build(context)
+            .stream().map(tm -> {
               Location loc = Location.NONE;
               if (tm.hasCurrent()) {
                 loc = new Location(new TServerInstance(tm.getLocation()));
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java b/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java
index 1495068..f5ed6db 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/master/state/Assignment.java
@@ -17,8 +17,10 @@
 package org.apache.accumulo.server.master.state;
 
 import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.util.HostAndPort;
 
-public class Assignment {
+public class Assignment implements Ample.TServer {
   public KeyExtent tablet;
   public TServerInstance server;
 
@@ -26,4 +28,14 @@ public class Assignment {
     this.tablet = tablet;
     this.server = server;
   }
+
+  @Override
+  public HostAndPort getLocation() {
+    return server.getLocation();
+  }
+
+  @Override
+  public String getSession() {
+    return server.getSession();
+  }
 }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/TServerInstance.java b/server/base/src/main/java/org/apache/accumulo/server/master/state/TServerInstance.java
index 45f13d3..c36748a 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/TServerInstance.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/master/state/TServerInstance.java
@@ -25,6 +25,7 @@ import java.io.Serializable;
 
 import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
 import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
 import org.apache.accumulo.core.util.AddressUtil;
@@ -37,7 +38,7 @@ import org.apache.hadoop.io.Text;
  * Therefore tablet assignments can be considered out-of-date if the tablet server instance
  * information has been changed.
  */
-public class TServerInstance implements Comparable<TServerInstance>, Serializable {
+public class TServerInstance implements Ample.TServer, Comparable<TServerInstance>, Serializable {
 
   private static final long serialVersionUID = 1L;
 
@@ -143,10 +144,12 @@ public class TServerInstance implements Comparable<TServerInstance>, Serializabl
     return new Value(getLocation().toString().getBytes(UTF_8));
   }
 
+  @Override
   public HostAndPort getLocation() {
     return location;
   }
 
+  @Override
   public String getSession() {
     return session;
   }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/TabletStateStore.java b/server/base/src/main/java/org/apache/accumulo/server/master/state/TabletStateStore.java
index 4b67163..840b5e3 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/TabletStateStore.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/master/state/TabletStateStore.java
@@ -102,7 +102,7 @@ public abstract class TabletStateStore implements Iterable<TabletLocationState>
 
   protected static TabletStateStore getStoreForTablet(KeyExtent extent, ServerContext context) {
     if (extent.isRootTablet()) {
-      return new ZooTabletStateStore(context);
+      return new ZooTabletStateStore(context.getAmple());
     } else if (extent.isMeta()) {
       return new RootTabletStateStore(context);
     } else {
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooStore.java b/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooStore.java
deleted file mode 100644
index 73bcdd6..0000000
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooStore.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.server.master.state;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.util.List;
-
-import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
-import org.apache.accumulo.fate.zookeeper.ZooCache;
-import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
-import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
-import org.apache.accumulo.server.ServerContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ZooStore implements DistributedStore {
-
-  private static final Logger log = LoggerFactory.getLogger(ZooStore.class);
-
-  private ServerContext context;
-  private String basePath;
-  private ZooCache cache;
-
-  public ZooStore(ServerContext context) {
-    this.context = context;
-    cache = new ZooCache(context.getZooReaderWriter(), null);
-    String zkRoot = context.getZooKeeperRoot();
-    if (zkRoot.endsWith("/"))
-      zkRoot = zkRoot.substring(0, zkRoot.length() - 1);
-    this.basePath = zkRoot;
-  }
-
-  @Override
-  public byte[] get(String path) throws DistributedStoreException {
-    try {
-      return cache.get(relative(path));
-    } catch (Exception ex) {
-      throw new DistributedStoreException(ex);
-    }
-  }
-
-  private String relative(String path) {
-    return basePath + path;
-  }
-
-  @Override
-  public List<String> getChildren(String path) throws DistributedStoreException {
-    try {
-      return cache.getChildren(relative(path));
-    } catch (Exception ex) {
-      throw new DistributedStoreException(ex);
-    }
-  }
-
-  @Override
-  public void put(String path, byte[] bs) throws DistributedStoreException {
-    try {
-      path = relative(path);
-      context.getZooReaderWriter().putPersistentData(path, bs, NodeExistsPolicy.OVERWRITE);
-      cache.clear();
-      log.debug("Wrote {} to {}", new String(bs, UTF_8), path);
-    } catch (Exception ex) {
-      throw new DistributedStoreException(ex);
-    }
-  }
-
-  @Override
-  public void remove(String path) throws DistributedStoreException {
-    try {
-      log.debug("Removing {}", path);
-      path = relative(path);
-      IZooReaderWriter zoo = context.getZooReaderWriter();
-      if (zoo.exists(path))
-        zoo.recursiveDelete(path, NodeMissingPolicy.SKIP);
-      cache.clear();
-    } catch (Exception ex) {
-      throw new DistributedStoreException(ex);
-    }
-  }
-}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooTabletStateStore.java b/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooTabletStateStore.java
index 3389ea9..e04c935 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooTabletStateStore.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/master/state/ZooTabletStateStore.java
@@ -16,20 +16,19 @@
  */
 package org.apache.accumulo.server.master.state;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.Ample.TabletMutator;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
-import org.apache.accumulo.core.util.HostAndPort;
-import org.apache.accumulo.server.ServerContext;
 import org.apache.commons.lang.NotImplementedException;
 import org.apache.hadoop.fs.Path;
 import org.slf4j.Logger;
@@ -38,18 +37,15 @@ import org.slf4j.LoggerFactory;
 public class ZooTabletStateStore extends TabletStateStore {
 
   private static final Logger log = LoggerFactory.getLogger(ZooTabletStateStore.class);
-  private final DistributedStore store;
-
-  public ZooTabletStateStore(DistributedStore store) {
-    this.store = store;
-  }
+  private final Ample ample;
 
-  public ZooTabletStateStore(ServerContext context) {
-    store = new ZooStore(context);
+  public ZooTabletStateStore(Ample ample) {
+    this.ample = ample;
   }
 
   @Override
   public ClosableIterator<TabletLocationState> iterator() {
+
     return new ClosableIterator<TabletLocationState>() {
       boolean finished = false;
 
@@ -62,33 +58,31 @@ public class ZooTabletStateStore extends TabletStateStore {
       public TabletLocationState next() {
         finished = true;
         try {
-          byte[] future = store.get(RootTable.ZROOT_TABLET_FUTURE_LOCATION);
-          byte[] current = store.get(RootTable.ZROOT_TABLET_LOCATION);
-          byte[] last = store.get(RootTable.ZROOT_TABLET_LAST_LOCATION);
+
+          TabletMetadata rootMeta = ample.readTablet(RootTable.EXTENT);
 
           TServerInstance currentSession = null;
           TServerInstance futureSession = null;
           TServerInstance lastSession = null;
 
-          if (future != null)
-            futureSession = parse(future);
+          Location loc = rootMeta.getLocation();
+
+          if (loc != null && loc.getType() == LocationType.FUTURE)
+            futureSession = new TServerInstance(loc);
 
-          if (last != null)
-            lastSession = parse(last);
+          if (rootMeta.getLast() != null)
+            lastSession = new TServerInstance(rootMeta.getLast());
 
-          if (current != null) {
-            currentSession = parse(current);
-            futureSession = null;
+          if (loc != null && loc.getType() == LocationType.CURRENT) {
+            currentSession = new TServerInstance(loc);
           }
+
           List<Collection<String>> logs = new ArrayList<>();
-          for (String entry : store.getChildren(RootTable.ZROOT_TABLET_WALOGS)) {
-            byte[] logInfo = store.get(RootTable.ZROOT_TABLET_WALOGS + "/" + entry);
-            if (logInfo != null) {
-              LogEntry logEntry = LogEntry.fromBytes(logInfo);
-              logs.add(Collections.singleton(logEntry.filename));
-              log.debug("root tablet log {}", logEntry.filename);
-            }
-          }
+          rootMeta.getLogs().forEach(logEntry -> {
+            logs.add(Collections.singleton(logEntry.filename));
+            log.debug("root tablet log {}", logEntry.filename);
+          });
+
           TabletLocationState result = new TabletLocationState(RootTable.EXTENT, futureSession,
               currentSession, lastSession, null, logs, false);
           log.debug("Returning root tablet state: {}", result);
@@ -108,18 +102,6 @@ public class ZooTabletStateStore extends TabletStateStore {
     };
   }
 
-  protected TServerInstance parse(byte[] current) {
-    String str = new String(current, UTF_8);
-    String[] parts = str.split("[|]", 2);
-    HostAndPort address = HostAndPort.fromString(parts[0]);
-    if (parts.length > 1 && parts[1] != null && parts[1].length() > 0) {
-      return new TServerInstance(address, parts[1]);
-    } else {
-      // a 1.2 location specification: DO NOT WANT
-      return null;
-    }
-  }
-
   @Override
   public void setFutureLocations(Collection<Assignment> assignments)
       throws DistributedStoreException {
@@ -128,14 +110,10 @@ public class ZooTabletStateStore extends TabletStateStore {
     Assignment assignment = assignments.iterator().next();
     if (assignment.tablet.compareTo(RootTable.EXTENT) != 0)
       throw new IllegalArgumentException("You can only store the root tablet location");
-    String value = assignment.server.getLocation() + "|" + assignment.server.getSession();
-    Iterator<TabletLocationState> currentIter = iterator();
-    TabletLocationState current = currentIter.next();
-    if (current.current != null) {
-      throw new DistributedStoreException(
-          "Trying to set the root tablet location: it is already set to " + current.current);
-    }
-    store.put(RootTable.ZROOT_TABLET_FUTURE_LOCATION, value.getBytes(UTF_8));
+
+    TabletMutator tabletMutator = ample.mutateTablet(assignment.tablet);
+    tabletMutator.putLocation(assignment, LocationType.FUTURE);
+    tabletMutator.mutate();
   }
 
   @Override
@@ -145,21 +123,12 @@ public class ZooTabletStateStore extends TabletStateStore {
     Assignment assignment = assignments.iterator().next();
     if (assignment.tablet.compareTo(RootTable.EXTENT) != 0)
       throw new IllegalArgumentException("You can only store the root tablet location");
-    String value = assignment.server.getLocation() + "|" + assignment.server.getSession();
-    Iterator<TabletLocationState> currentIter = iterator();
-    TabletLocationState current = currentIter.next();
-    if (current.current != null) {
-      throw new DistributedStoreException(
-          "Trying to set the root tablet location: it is already set to " + current.current);
-    }
-    if (!current.future.equals(assignment.server)) {
-      throw new DistributedStoreException("Root tablet is already assigned to " + current.future);
-    }
-    store.put(RootTable.ZROOT_TABLET_LOCATION, value.getBytes(UTF_8));
-    store.put(RootTable.ZROOT_TABLET_LAST_LOCATION, value.getBytes(UTF_8));
-    // Make the following unnecessary by making the entire update atomic
-    store.remove(RootTable.ZROOT_TABLET_FUTURE_LOCATION);
-    log.debug("Put down root tablet location");
+
+    TabletMutator tabletMutator = ample.mutateTablet(assignment.tablet);
+    tabletMutator.putLocation(assignment, LocationType.CURRENT);
+    tabletMutator.deleteLocation(assignment, LocationType.FUTURE);
+
+    tabletMutator.mutate();
   }
 
   @Override
@@ -170,24 +139,24 @@ public class ZooTabletStateStore extends TabletStateStore {
     TabletLocationState tls = tablets.iterator().next();
     if (tls.extent.compareTo(RootTable.EXTENT) != 0)
       throw new IllegalArgumentException("You can only store the root tablet location");
+
+    TabletMutator tabletMutator = ample.mutateTablet(tls.extent);
+
+    tabletMutator.deleteLocation(tls.futureOrCurrent(), LocationType.FUTURE);
+    tabletMutator.deleteLocation(tls.futureOrCurrent(), LocationType.CURRENT);
     if (logsForDeadServers != null) {
       List<Path> logs = logsForDeadServers.get(tls.futureOrCurrent());
       if (logs != null) {
         for (Path entry : logs) {
           LogEntry logEntry = new LogEntry(RootTable.EXTENT, System.currentTimeMillis(),
               tls.futureOrCurrent().getLocation().toString(), entry.toString());
-          byte[] value;
-          try {
-            value = logEntry.toBytes();
-          } catch (IOException ex) {
-            throw new DistributedStoreException(ex);
-          }
-          store.put(RootTable.ZROOT_TABLET_WALOGS + "/" + logEntry.getUniqueID(), value);
+          tabletMutator.putWal(logEntry);
         }
       }
     }
-    store.remove(RootTable.ZROOT_TABLET_LOCATION);
-    store.remove(RootTable.ZROOT_TABLET_FUTURE_LOCATION);
+
+    tabletMutator.mutate();
+
     log.debug("unassign root tablet location");
   }
 
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java
new file mode 100644
index 0000000..4ba20f4
--- /dev/null
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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.server.metadata;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.util.List;
+
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
+import org.apache.accumulo.core.security.AuthorizationContainer;
+import org.apache.accumulo.fate.zookeeper.ZooUtil;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.accumulo.server.constraints.MetadataConstraints;
+import org.apache.accumulo.server.constraints.SystemEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RootTabletMutatorImpl extends TabletMutatorBase implements Ample.TabletMutator {
+  private ServerContext context;
+
+  private static final Logger log = LoggerFactory.getLogger(RootTabletMutatorImpl.class);
+
+  private static class RootEnv implements SystemEnvironment {
+
+    private ServerContext ctx;
+
+    RootEnv(ServerContext ctx) {
+      this.ctx = ctx;
+    }
+
+    @Override
+    public KeyExtent getExtent() {
+      return RootTable.EXTENT;
+    }
+
+    @Override
+    public String getUser() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AuthorizationContainer getAuthorizationsContainer() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServerContext getServerContext() {
+      return ctx;
+    }
+  }
+
+  RootTabletMutatorImpl(ServerContext context) {
+    super(context, RootTable.EXTENT);
+    this.context = context;
+  }
+
+  @Override
+  public void mutate() {
+
+    Mutation mutation = getMutation();
+
+    MetadataConstraints metaConstraint = new MetadataConstraints();
+    List<Short> violations = metaConstraint.check(new RootEnv(context), mutation);
+
+    if (violations != null && !violations.isEmpty()) {
+      throw new IllegalStateException(
+          "Mutation for root tablet metadata violated constraints : " + violations);
+    }
+
+    try {
+      String zpath = context.getZooKeeperRoot() + RootTable.ZROOT_TABLET;
+
+      context.getZooCache().clear(zpath);
+
+      // TODO examine implementation of getZooReaderWriter().mutate()
+      context.getZooReaderWriter().mutate(zpath, new byte[0], ZooUtil.PUBLIC, currVal -> {
+
+        String currJson = new String(currVal, UTF_8);
+
+        log.debug("Before mutating : {}, ", currJson);
+
+        RootTabletMetadata rtm = RootTabletMetadata.fromJson(currJson);
+
+        rtm.update(mutation);
+
+        String newJson = rtm.toJson();
+
+        log.debug("After mutating : {} ", newJson);
+
+        return newJson.getBytes(UTF_8);
+      });
+
+      // TODO this is racy...
+      context.getZooCache().clear(zpath);
+
+      if (closeAfterMutate != null)
+        closeAfterMutate.close();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java
new file mode 100644
index 0000000..2f5d9da
--- /dev/null
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java
@@ -0,0 +1,111 @@
+/*
+ * 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.server.metadata;
+
+import static org.apache.accumulo.server.util.MetadataTableUtil.EMPTY_TEXT;
+
+import java.util.Collection;
+
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.AmpleImpl;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.Text;
+
+import com.google.common.base.Preconditions;
+
+public class ServerAmpleImpl extends AmpleImpl implements Ample {
+
+  private ServerContext context;
+
+  public ServerAmpleImpl(ServerContext ctx) {
+    super(ctx);
+    this.context = ctx;
+  }
+
+  @Override
+  public Ample.TabletMutator mutateTablet(KeyExtent extent) {
+    TabletsMutator tmi = mutateTablets();
+    Ample.TabletMutator tabletMutator = tmi.mutateTablet(extent);
+    ((TabletMutatorBase) tabletMutator).setCloseAfterMutate(tmi);
+    return tabletMutator;
+  }
+
+  @Override
+  public TabletsMutator mutateTablets() {
+    return new TabletsMutatorImpl(context);
+  }
+
+  @Override
+  public void putGcCandidates(TableId tableId, Collection<? extends Ample.FileMeta> candidates) {
+    try (BatchWriter writer = createWriter(tableId)) {
+      for (Ample.FileMeta file : candidates) {
+        writer.addMutation(createDeleteMutation(context, tableId, file.path().toString()));
+      }
+    } catch (MutationsRejectedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public void deleteGcCandidates(TableId tableId, Collection<String> paths) {
+    try (BatchWriter writer = createWriter(tableId)) {
+      for (String path : paths) {
+        Mutation m = new Mutation(MetadataSchema.DeletesSection.getRowPrefix() + path);
+        m.putDelete(EMPTY_TEXT, EMPTY_TEXT);
+        writer.addMutation(m);
+      }
+    } catch (MutationsRejectedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private BatchWriter createWriter(TableId tableId) {
+
+    Preconditions.checkArgument(!RootTable.ID.equals(tableId));
+
+    try {
+      if (MetadataTable.ID.equals(tableId)) {
+        return context.createBatchWriter(RootTable.NAME);
+      } else {
+        return context.createBatchWriter(MetadataTable.NAME);
+      }
+    } catch (TableNotFoundException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static Mutation createDeleteMutation(ServerContext context, TableId tableId,
+      String pathToRemove) {
+    Path path = context.getVolumeManager().getFullPath(tableId, pathToRemove);
+    Mutation delFlag = new Mutation(new Text(MetadataSchema.DeletesSection.getRowPrefix() + path));
+    delFlag.put(EMPTY_TEXT, EMPTY_TEXT, new Value(new byte[] {}));
+    return delFlag;
+  }
+
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorBase.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorBase.java
new file mode 100644
index 0000000..c82efcd
--- /dev/null
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorBase.java
@@ -0,0 +1,162 @@
+/*
+ * 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.server.metadata;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.DataFileValue;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
+import org.apache.accumulo.core.tabletserver.log.LogEntry;
+import org.apache.accumulo.fate.zookeeper.ZooLock;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.hadoop.io.Text;
+
+import com.google.common.base.Preconditions;
+
+public abstract class TabletMutatorBase implements Ample.TabletMutator {
+
+  private final ServerContext context;
+  private final KeyExtent extent;
+  private final Mutation mutation;
+  protected AutoCloseable closeAfterMutate;
+  private boolean updatesEnabled = true;
+
+  protected TabletMutatorBase(ServerContext ctx, KeyExtent extent) {
+    this.extent = extent;
+    this.context = ctx;
+    mutation = new Mutation(extent.getMetadataEntry());
+  }
+
+  @Override
+  public Ample.TabletMutator putPrevEndRow(Text per) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    TabletsSection.TabletColumnFamily.PREV_ROW_COLUMN.put(mutation,
+        KeyExtent.encodePrevEndRow(extent.getPrevEndRow()));
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putDir(String dir) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN.put(mutation,
+        new Value(dir.getBytes(UTF_8)));
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putFile(Ample.FileMeta path, DataFileValue dfv) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.put(DataFileColumnFamily.NAME, path.meta(), new Value(dfv.encode()));
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putScan(Ample.FileMeta path) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.put(ScanFileColumnFamily.NAME, path.meta(), new Value(new byte[0]));
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator deleteFile(Ample.FileMeta path) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.putDelete(DataFileColumnFamily.NAME, path.meta());
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putCompactionId(long compactionId) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    TabletsSection.ServerColumnFamily.COMPACT_COLUMN.put(mutation,
+        new Value(("" + compactionId).getBytes()));
+    return this;
+  }
+
+  private String getLocationFamily(LocationType type) {
+    switch (type) {
+      case CURRENT:
+        return TabletsSection.CurrentLocationColumnFamily.STR_NAME;
+      case FUTURE:
+        return TabletsSection.FutureLocationColumnFamily.STR_NAME;
+      case LAST:
+        return TabletsSection.LastLocationColumnFamily.STR_NAME;
+      default:
+        throw new IllegalArgumentException();
+    }
+  }
+
+  @Override
+  public Ample.TabletMutator putLocation(Ample.TServer tsi, LocationType type) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.put(getLocationFamily(type), tsi.getSession(), tsi.getLocation().toString());
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator deleteLocation(Ample.TServer tsi, LocationType type) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.putDelete(getLocationFamily(type), tsi.getSession());
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putZooLock(ZooLock zooLock) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    TabletsSection.ServerColumnFamily.LOCK_COLUMN.put(mutation,
+        new Value(zooLock.getLockID().serialize(context.getZooKeeperRoot() + "/").getBytes(UTF_8)));
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator putWal(LogEntry logEntry) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.put(logEntry.getColumnFamily(), logEntry.getColumnQualifier(), logEntry.getValue());
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator deleteWal(LogEntry logEntry) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.putDelete(logEntry.getColumnFamily(), logEntry.getColumnQualifier());
+    return this;
+  }
+
+  @Override
+  public Ample.TabletMutator deleteWal(String wal) {
+    Preconditions.checkState(updatesEnabled, "Cannot make updates after calling mutate.");
+    mutation.putDelete(MetadataSchema.TabletsSection.LogColumnFamily.STR_NAME, wal);
+    return this;
+  }
+
+  protected Mutation getMutation() {
+    updatesEnabled = false;
+    return mutation;
+  }
+
+  public void setCloseAfterMutate(AutoCloseable closeable) {
+    this.closeAfterMutate = closeable;
+  }
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/master/state/DistributedStore.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorImpl.java
similarity index 51%
rename from server/base/src/main/java/org/apache/accumulo/server/master/state/DistributedStore.java
rename to server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorImpl.java
index 275c9d2..4a94984 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/master/state/DistributedStore.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletMutatorImpl.java
@@ -14,21 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.accumulo.server.master.state;
 
-import java.util.List;
+package org.apache.accumulo.server.metadata;
 
-/**
- * An abstract version of ZooKeeper that we can write tests against.
- */
-public interface DistributedStore {
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.server.ServerContext;
+
+class TabletMutatorImpl extends TabletMutatorBase implements Ample.TabletMutator {
 
-  List<String> getChildren(String path) throws DistributedStoreException;
+  private BatchWriter writer;
 
-  byte[] get(String path) throws DistributedStoreException;
+  TabletMutatorImpl(ServerContext context, KeyExtent extent, BatchWriter batchWriter) {
+    super(context, extent);
+    this.writer = batchWriter;
+  }
 
-  void put(String path, byte[] bs) throws DistributedStoreException;
+  @Override
+  public void mutate() {
+    try {
+      writer.addMutation(getMutation());
 
-  void remove(String path) throws DistributedStoreException;
+      if (closeAfterMutate != null)
+        closeAfterMutate.close();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
 
 }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletsMutatorImpl.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletsMutatorImpl.java
new file mode 100644
index 0000000..4ac5ba2
--- /dev/null
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/TabletsMutatorImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.server.metadata;
+
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.Ample.TabletsMutator;
+import org.apache.accumulo.server.ServerContext;
+
+import com.google.common.base.Preconditions;
+
+public class TabletsMutatorImpl implements TabletsMutator {
+
+  private ServerContext context;
+
+  private BatchWriter rootWriter;
+  private BatchWriter metaWriter;
+
+  public TabletsMutatorImpl(ServerContext context) {
+    this.context = context;
+  }
+
+  private BatchWriter getWriter(TableId tableId) {
+
+    Preconditions.checkArgument(!RootTable.ID.equals(tableId));
+
+    try {
+      if (MetadataTable.ID.equals(tableId)) {
+        if (rootWriter == null) {
+          rootWriter = context.createBatchWriter(RootTable.NAME);
+        }
+
+        return rootWriter;
+      } else {
+        if (metaWriter == null) {
+          metaWriter = context.createBatchWriter(MetadataTable.NAME);
+        }
+
+        return metaWriter;
+      }
+    } catch (TableNotFoundException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public Ample.TabletMutator mutateTablet(KeyExtent extent) {
+    if (extent.isRootTablet()) {
+      return new RootTabletMutatorImpl(context);
+    } else {
+      return new TabletMutatorImpl(context, extent, getWriter(extent.getTableId()));
+    }
+  }
+
+  @Override
+  public void close() {
+    try {
+      if (rootWriter != null) {
+        rootWriter.close();
+      }
+
+      if (metaWriter != null) {
+        metaWriter.close();
+      }
+    } catch (MutationsRejectedException e) {
+      throw new RuntimeException(e);
+    }
+
+  }
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/util/FindOfflineTablets.java b/server/base/src/main/java/org/apache/accumulo/server/util/FindOfflineTablets.java
index b0d8115..3ff7822 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/FindOfflineTablets.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/FindOfflineTablets.java
@@ -70,7 +70,8 @@ public class FindOfflineTablets {
     tservers.startListeningForTabletServerChanges();
     scanning.set(true);
 
-    Iterator<TabletLocationState> zooScanner = new ZooTabletStateStore(context).iterator();
+    Iterator<TabletLocationState> zooScanner =
+        new ZooTabletStateStore(context.getAmple()).iterator();
 
     int offline = 0;
 
diff --git a/server/base/src/main/java/org/apache/accumulo/server/util/ListVolumesUsed.java b/server/base/src/main/java/org/apache/accumulo/server/util/ListVolumesUsed.java
index b8dec24..e65b001 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/ListVolumesUsed.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/ListVolumesUsed.java
@@ -16,7 +16,6 @@
  */
 package org.apache.accumulo.server.util;
 
-import java.util.ArrayList;
 import java.util.Map.Entry;
 import java.util.TreeSet;
 
@@ -27,6 +26,7 @@ import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.metadata.MetadataTable;
 import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.server.ServerContext;
@@ -62,10 +62,11 @@ public class ListVolumesUsed {
     System.out.println("Listing volumes referenced in zookeeper");
     TreeSet<String> volumes = new TreeSet<>();
 
-    volumes.add(getTableURI(MetadataTableUtil.getRootTabletDir(context)));
-    ArrayList<LogEntry> result = new ArrayList<>();
-    MetadataTableUtil.getRootLogEntries(context, result);
-    for (LogEntry logEntry : result) {
+    TabletMetadata rootMeta = context.getAmple().readTablet(RootTable.EXTENT);
+
+    volumes.add(getTableURI(rootMeta.getDir()));
+
+    for (LogEntry logEntry : rootMeta.getLogs()) {
       getLogURIs(volumes, logEntry);
     }
 
diff --git a/server/base/src/main/java/org/apache/accumulo/server/util/MasterMetadataUtil.java b/server/base/src/main/java/org/apache/accumulo/server/util/MasterMetadataUtil.java
index 2027249..be31e56 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/MasterMetadataUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/MasterMetadataUtil.java
@@ -41,16 +41,18 @@ import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.Ample.TabletMutator;
 import org.apache.accumulo.core.metadata.schema.DataFileValue;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.util.ColumnFQ;
-import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
 import org.apache.accumulo.fate.zookeeper.ZooLock;
-import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.fs.FileRef;
 import org.apache.accumulo.server.master.state.TServerInstance;
@@ -209,35 +211,29 @@ public class MasterMetadataUtil {
       DataFileValue size, String address, TServerInstance lastLocation, ZooLock zooLock,
       boolean insertDeleteFlags) {
 
-    if (insertDeleteFlags) {
-      // add delete flags for those paths before the data file reference is removed
-      MetadataTableUtil.addDeleteEntries(extent, datafilesToDelete, context);
-    }
-
-    // replace data file references to old mapfiles with the new mapfiles
-    Mutation m = new Mutation(extent.getMetadataEntry());
+    context.getAmple().putGcCandidates(extent.getTableId(), datafilesToDelete);
 
-    for (FileRef pathToRemove : datafilesToDelete)
-      m.putDelete(DataFileColumnFamily.NAME, pathToRemove.meta());
+    Ample.TabletMutator tablet = context.getAmple().mutateTablet(extent);
 
-    for (FileRef scanFile : scanFiles)
-      m.put(ScanFileColumnFamily.NAME, scanFile.meta(), new Value(new byte[0]));
+    datafilesToDelete.forEach(tablet::deleteFile);
+    scanFiles.forEach(tablet::putScan);
 
     if (size.getNumEntries() > 0)
-      m.put(DataFileColumnFamily.NAME, path.meta(), new Value(size.encode()));
+      tablet.putFile(path, size);
 
     if (compactionId != null)
-      TabletsSection.ServerColumnFamily.COMPACT_COLUMN.put(m,
-          new Value(("" + compactionId).getBytes()));
+      tablet.putCompactionId(compactionId);
 
     TServerInstance self = getTServerInstance(address, zooLock);
-    self.putLastLocation(m);
+    tablet.putLocation(self, LocationType.LAST);
 
     // remove the old location
     if (lastLocation != null && !lastLocation.equals(self))
-      lastLocation.clearLastLocation(m);
+      tablet.deleteLocation(lastLocation, LocationType.LAST);
 
-    MetadataTableUtil.update(context, zooLock, m, extent);
+    tablet.putZooLock(zooLock);
+
+    tablet.mutate();
   }
 
   /**
@@ -266,24 +262,9 @@ public class MasterMetadataUtil {
    * Update the data file for the root tablet
    */
   private static void updateRootTabletDataFile(ServerContext context, Set<String> unusedWalLogs) {
-    IZooReaderWriter zk = context.getZooReaderWriter();
-    String root = MetadataTableUtil.getZookeeperLogLocation(context);
-    for (String entry : unusedWalLogs) {
-      String[] parts = entry.split("/");
-      String zpath = root + "/" + parts[parts.length - 1];
-      while (true) {
-        try {
-          if (zk.exists(zpath)) {
-            log.debug("Removing WAL reference for root table {}", zpath);
-            zk.recursiveDelete(zpath, NodeMissingPolicy.SKIP);
-          }
-          break;
-        } catch (KeeperException | InterruptedException e) {
-          log.error("{}", e.getMessage(), e);
-        }
-        sleepUninterruptibly(1, TimeUnit.SECONDS);
-      }
-    }
+    TabletMutator tablet = context.getAmple().mutateTablet(RootTable.EXTENT);
+    unusedWalLogs.forEach(tablet::deleteWal);
+    tablet.mutate();
   }
 
   /**
diff --git a/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java b/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java
index 73e3ecc..2c9a689 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java
@@ -17,6 +17,14 @@
 package org.apache.accumulo.server.util;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.CLONED;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.DIR;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LAST;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOGS;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.TIME;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.io.IOException;
@@ -57,15 +65,14 @@ import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.metadata.MetadataTable;
 import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.core.metadata.schema.DataFileValue;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ChoppedColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ClonedColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
-import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
-import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
 import org.apache.accumulo.core.metadata.schema.TabletDeletedException;
 import org.apache.accumulo.core.metadata.schema.TabletMetadata;
 import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
@@ -75,16 +82,14 @@ import org.apache.accumulo.core.tabletserver.thrift.ConstraintViolationException
 import org.apache.accumulo.core.util.ColumnFQ;
 import org.apache.accumulo.core.util.FastFormat;
 import org.apache.accumulo.core.util.Pair;
-import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
 import org.apache.accumulo.fate.zookeeper.ZooLock;
-import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
-import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
 import org.apache.accumulo.server.ServerConstants;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.fs.FileRef;
 import org.apache.accumulo.server.fs.VolumeChooserEnvironment;
 import org.apache.accumulo.server.fs.VolumeChooserEnvironmentImpl;
 import org.apache.accumulo.server.fs.VolumeManager;
+import org.apache.accumulo.server.metadata.ServerAmpleImpl;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.Text;
@@ -93,14 +98,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Preconditions;
 
 /**
  * provides a reference to the metadata table for updates by tablet servers
  */
 public class MetadataTableUtil {
 
-  private static final Text EMPTY_TEXT = new Text();
+  public static final Text EMPTY_TEXT = new Text();
   private static Map<Credentials,Writer> root_tables = new HashMap<>();
   private static Map<Credentials,Writer> metadata_tables = new HashMap<>();
   private static final Logger log = LoggerFactory.getLogger(MetadataTableUtil.class);
@@ -222,66 +227,21 @@ public class MetadataTableUtil {
 
       if (filesToRemove.size() != 0 || filesToAdd.size() != 0)
         throw new IllegalArgumentException("files not expected for " + extent);
+    }
 
-      // add before removing in case of process death
-      for (LogEntry logEntry : logsToAdd)
-        addRootLogEntry(context, zooLock, logEntry);
-
-      removeUnusedWALEntries(context, extent, logsToRemove, zooLock);
-    } else {
-      Mutation m = new Mutation(extent.getMetadataEntry());
-
-      for (LogEntry logEntry : logsToRemove)
-        m.putDelete(logEntry.getColumnFamily(), logEntry.getColumnQualifier());
-
-      for (LogEntry logEntry : logsToAdd)
-        m.put(logEntry.getColumnFamily(), logEntry.getColumnQualifier(), logEntry.getValue());
-
-      for (FileRef fileRef : filesToRemove)
-        m.putDelete(DataFileColumnFamily.NAME, fileRef.meta());
+    Ample.TabletMutator tabletMutator = context.getAmple().mutateTablet(extent);
+    logsToRemove.forEach(tabletMutator::deleteWal);
+    logsToAdd.forEach(tabletMutator::putWal);
 
-      for (Entry<FileRef,DataFileValue> entry : filesToAdd.entrySet())
-        m.put(DataFileColumnFamily.NAME, entry.getKey().meta(),
-            new Value(entry.getValue().encode()));
+    filesToRemove.forEach(tabletMutator::deleteFile);
+    filesToAdd.forEach(tabletMutator::putFile);
 
-      if (newDir != null)
-        ServerColumnFamily.DIRECTORY_COLUMN.put(m, new Value(newDir.getBytes(UTF_8)));
+    if (newDir != null)
+      tabletMutator.putDir(newDir);
 
-      update(context, m, extent);
-    }
-  }
+    tabletMutator.putZooLock(zooLock);
 
-  private interface ZooOperation {
-    void run(IZooReaderWriter rw) throws KeeperException, InterruptedException, IOException;
-  }
-
-  private static void retryZooKeeperUpdate(ServerContext context, ZooLock zooLock,
-      ZooOperation op) {
-    while (true) {
-      try {
-        IZooReaderWriter zoo = context.getZooReaderWriter();
-        if (zoo.isLockHeld(zooLock.getLockID())) {
-          op.run(zoo);
-        }
-        break;
-      } catch (Exception e) {
-        log.error("Unexpected exception {}", e.getMessage(), e);
-      }
-      sleepUninterruptibly(1, TimeUnit.SECONDS);
-    }
-  }
-
-  private static void addRootLogEntry(ServerContext context, ZooLock zooLock,
-      final LogEntry entry) {
-    retryZooKeeperUpdate(context, zooLock, new ZooOperation() {
-      @Override
-      public void run(IZooReaderWriter rw)
-          throws KeeperException, InterruptedException, IOException {
-        String root = getZookeeperLogLocation(context);
-        rw.putPersistentData(root + "/" + entry.getUniqueID(), entry.toBytes(),
-            NodeExistsPolicy.OVERWRITE);
-      }
-    });
+    tabletMutator.mutate();
   }
 
   public static SortedMap<FileRef,DataFileValue> getDataFileSizes(KeyExtent extent,
@@ -361,24 +321,17 @@ public class MetadataTableUtil {
     // TODO could use batch writer,would need to handle failure and retry like update does -
     // ACCUMULO-1294
     for (FileRef pathToRemove : datafilesToDelete) {
-      update(context, createDeleteMutation(context, tableId, pathToRemove.path().toString()),
+      update(context,
+          ServerAmpleImpl.createDeleteMutation(context, tableId, pathToRemove.path().toString()),
           extent);
     }
   }
 
   public static void addDeleteEntry(ServerContext context, TableId tableId, String path) {
-    update(context, createDeleteMutation(context, tableId, path),
+    update(context, ServerAmpleImpl.createDeleteMutation(context, tableId, path),
         new KeyExtent(tableId, null, null));
   }
 
-  public static Mutation createDeleteMutation(ServerContext context, TableId tableId,
-      String pathToRemove) {
-    Path path = context.getVolumeManager().getFullPath(tableId, pathToRemove);
-    Mutation delFlag = new Mutation(new Text(MetadataSchema.DeletesSection.getRowPrefix() + path));
-    delFlag.put(EMPTY_TEXT, EMPTY_TEXT, new Value(new byte[] {}));
-    return delFlag;
-  }
-
   public static void removeScanFiles(KeyExtent extent, Set<FileRef> scanFiles,
       ServerContext context, ZooLock zooLock) {
     Mutation m = new Mutation(extent.getMetadataEntry());
@@ -460,11 +413,13 @@ public class MetadataTableUtil {
 
           if (key.getColumnFamily().equals(DataFileColumnFamily.NAME)) {
             FileRef ref = new FileRef(context.getVolumeManager(), key);
-            bw.addMutation(createDeleteMutation(context, tableId, ref.meta().toString()));
+            bw.addMutation(
+                ServerAmpleImpl.createDeleteMutation(context, tableId, ref.meta().toString()));
           }
 
           if (TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN.hasColumns(key)) {
-            bw.addMutation(createDeleteMutation(context, tableId, cell.getValue().toString()));
+            bw.addMutation(
+                ServerAmpleImpl.createDeleteMutation(context, tableId, cell.getValue().toString()));
           }
         }
 
@@ -496,36 +451,6 @@ public class MetadataTableUtil {
     }
   }
 
-  static String getZookeeperLogLocation(ServerContext context) {
-    return context.getZooKeeperRoot() + RootTable.ZROOT_TABLET_WALOGS;
-  }
-
-  public static void setRootTabletDir(ServerContext context, String dir) throws IOException {
-    IZooReaderWriter zoo = context.getZooReaderWriter();
-    String zpath = context.getZooKeeperRoot() + RootTable.ZROOT_TABLET_PATH;
-    try {
-      zoo.putPersistentData(zpath, dir.getBytes(UTF_8), -1, NodeExistsPolicy.OVERWRITE);
-    } catch (KeeperException e) {
-      throw new IOException(e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new IOException(e);
-    }
-  }
-
-  public static String getRootTabletDir(ServerContext context) throws IOException {
-    IZooReaderWriter zoo = context.getZooReaderWriter();
-    String zpath = context.getZooKeeperRoot() + RootTable.ZROOT_TABLET_PATH;
-    try {
-      return new String(zoo.getData(zpath, null), UTF_8);
-    } catch (KeeperException e) {
-      throw new IOException(e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new IOException(e);
-    }
-  }
-
   public static Pair<List<LogEntry>,SortedMap<FileRef,DataFileValue>>
       getFileAndLogEntries(ServerContext context, KeyExtent extent)
           throws KeeperException, InterruptedException, IOException {
@@ -533,10 +458,19 @@ public class MetadataTableUtil {
     TreeMap<FileRef,DataFileValue> sizes = new TreeMap<>();
 
     VolumeManager fs = context.getVolumeManager();
+
+    TabletMetadata tablet = context.getAmple().readTablet(extent, FILES, LOGS, PREV_ROW, DIR);
+
+    if (!tablet.getExtent().equals(extent))
+      throw new RuntimeException("Unexpected extent " + tablet.getExtent() + " expected " + extent);
+
+    result.addAll(tablet.getLogs());
+
     if (extent.isRootTablet()) {
-      getRootLogEntries(context, result);
-      Path rootDir = new Path(getRootTabletDir(context));
-      FileStatus[] files = fs.listStatus(rootDir);
+      Preconditions.checkState(tablet.getFiles().isEmpty(),
+          "Saw unexpected files in root tablet metadata %s", tablet.getFiles());
+
+      FileStatus[] files = fs.listStatus(new Path(tablet.getDir()));
       for (FileStatus fileStatus : files) {
         if (fileStatus.getPath().toString().endsWith("_tmp")) {
           continue;
@@ -544,158 +478,20 @@ public class MetadataTableUtil {
         DataFileValue dfv = new DataFileValue(0, 0);
         sizes.put(new FileRef(fileStatus.getPath().toString(), fileStatus.getPath()), dfv);
       }
-
     } else {
-      try (TabletsMetadata tablets = TabletsMetadata.builder().forTablet(extent).fetchFiles()
-          .fetchLogs().fetchPrev().build(context)) {
-
-        TabletMetadata tablet = Iterables.getOnlyElement(tablets);
-
-        if (!tablet.getExtent().equals(extent))
-          throw new RuntimeException(
-              "Unexpected extent " + tablet.getExtent() + " expected " + extent);
-
-        result.addAll(tablet.getLogs());
-        tablet.getFilesMap().forEach((k, v) -> {
-          sizes.put(new FileRef(k, fs.getFullPath(tablet.getTableId(), k)), v);
-        });
-      }
+      tablet.getFilesMap().forEach((k, v) -> {
+        sizes.put(new FileRef(k, fs.getFullPath(tablet.getTableId(), k)), v);
+      });
     }
 
     return new Pair<>(result, sizes);
   }
 
-  public static List<LogEntry> getLogEntries(ServerContext context, KeyExtent extent)
-      throws IOException, KeeperException, InterruptedException {
-    log.info("Scanning logging entries for {}", extent);
-    ArrayList<LogEntry> result = new ArrayList<>();
-    if (extent.equals(RootTable.EXTENT)) {
-      log.info("Getting logs for root tablet from zookeeper");
-      getRootLogEntries(context, result);
-    } else {
-      log.info("Scanning metadata for logs used for tablet {}", extent);
-      Scanner scanner = getTabletLogScanner(context, extent);
-      Text pattern = extent.getMetadataEntry();
-      for (Entry<Key,Value> entry : scanner) {
-        Text row = entry.getKey().getRow();
-        if (entry.getKey().getColumnFamily().equals(LogColumnFamily.NAME)) {
-          if (row.equals(pattern)) {
-            result.add(LogEntry.fromKeyValue(entry.getKey(), entry.getValue()));
-          }
-        }
-      }
-    }
-
-    log.info("Returning logs {} for extent {}", result, extent);
-    return result;
-  }
-
-  static void getRootLogEntries(ServerContext context, final ArrayList<LogEntry> result)
-      throws KeeperException, InterruptedException, IOException {
-    IZooReaderWriter zoo = context.getZooReaderWriter();
-    String root = getZookeeperLogLocation(context);
-    // there's a little race between getting the children and fetching
-    // the data. The log can be removed in between.
-    while (true) {
-      result.clear();
-      for (String child : zoo.getChildren(root)) {
-        try {
-          LogEntry e = LogEntry.fromBytes(zoo.getData(root + "/" + child, null));
-          // upgrade from !0;!0<< -> +r<<
-          e = new LogEntry(RootTable.EXTENT, 0, e.server, e.filename);
-          result.add(e);
-        } catch (KeeperException.NoNodeException ex) {
-          continue;
-        }
-      }
-      break;
-    }
-  }
-
-  private static Scanner getTabletLogScanner(ServerContext context, KeyExtent extent) {
-    TableId tableId = MetadataTable.ID;
-    if (extent.isMeta())
-      tableId = RootTable.ID;
-    Scanner scanner = new ScannerImpl(context, tableId, Authorizations.EMPTY);
-    scanner.fetchColumnFamily(LogColumnFamily.NAME);
-    Text start = extent.getMetadataEntry();
-    Key endKey = new Key(start, LogColumnFamily.NAME);
-    endKey = endKey.followingKey(PartialKey.ROW_COLFAM);
-    scanner.setRange(new Range(new Key(start), endKey));
-    return scanner;
-  }
-
-  private static class LogEntryIterator implements Iterator<LogEntry> {
-
-    Iterator<LogEntry> zookeeperEntries = null;
-    Iterator<LogEntry> rootTableEntries = null;
-    Iterator<Entry<Key,Value>> metadataEntries = null;
-
-    LogEntryIterator(ServerContext context)
-        throws IOException, KeeperException, InterruptedException {
-      zookeeperEntries = getLogEntries(context, RootTable.EXTENT).iterator();
-      rootTableEntries =
-          getLogEntries(context, new KeyExtent(MetadataTable.ID, null, null)).iterator();
-      try {
-        Scanner scanner = context.createScanner(MetadataTable.NAME, Authorizations.EMPTY);
-        log.info("Setting range to {}", MetadataSchema.TabletsSection.getRange());
-        scanner.setRange(MetadataSchema.TabletsSection.getRange());
-        scanner.fetchColumnFamily(LogColumnFamily.NAME);
-        metadataEntries = scanner.iterator();
-      } catch (Exception ex) {
-        throw new IOException(ex);
-      }
-    }
-
-    @Override
-    public boolean hasNext() {
-      return zookeeperEntries.hasNext() || rootTableEntries.hasNext() || metadataEntries.hasNext();
-    }
-
-    @Override
-    public LogEntry next() {
-      if (zookeeperEntries.hasNext()) {
-        return zookeeperEntries.next();
-      }
-      if (rootTableEntries.hasNext()) {
-        return rootTableEntries.next();
-      }
-      Entry<Key,Value> entry = metadataEntries.next();
-      return LogEntry.fromKeyValue(entry.getKey(), entry.getValue());
-    }
-
-    @Override
-    public void remove() {
-      throw new UnsupportedOperationException();
-    }
-  }
-
-  public static Iterator<LogEntry> getLogEntries(ServerContext context)
-      throws IOException, KeeperException, InterruptedException {
-    return new LogEntryIterator(context);
-  }
-
   public static void removeUnusedWALEntries(ServerContext context, KeyExtent extent,
       final List<LogEntry> entries, ZooLock zooLock) {
-    if (extent.isRootTablet()) {
-      retryZooKeeperUpdate(context, zooLock, new ZooOperation() {
-        @Override
-        public void run(IZooReaderWriter rw) throws KeeperException, InterruptedException {
-          String root = getZookeeperLogLocation(context);
-          for (LogEntry entry : entries) {
-            String path = root + "/" + entry.getUniqueID();
-            log.debug("Removing " + path + " from zookeeper");
-            rw.recursiveDelete(path, NodeMissingPolicy.SKIP);
-          }
-        }
-      });
-    } else {
-      Mutation m = new Mutation(extent.getMetadataEntry());
-      for (LogEntry entry : entries) {
-        m.putDelete(entry.getColumnFamily(), entry.getColumnQualifier());
-      }
-      update(context, zooLock, m, extent);
-    }
+    Ample.TabletMutator tabletMutator = context.getAmple().mutateTablet(extent);
+    entries.forEach(tabletMutator::deleteWal);
+    tabletMutator.putZooLock(zooLock);
   }
 
   private static void getFiles(Set<String> files, Collection<String> tabletFiles,
@@ -753,8 +549,7 @@ public class MetadataTableUtil {
     }
 
     return TabletsMetadata.builder().scanTable(tableName).overRange(range).checkConsistency()
-        .saveKeyValues().fetchFiles().fetchLocation().fetchLast().fetchCloned().fetchPrev()
-        .fetchTime().build(client);
+        .saveKeyValues().fetch(FILES, LOCATION, LAST, CLONED, PREV_ROW, TIME).build(client);
   }
 
   @VisibleForTesting
@@ -1042,5 +837,4 @@ public class MetadataTableUtil {
 
     return tabletEntries;
   }
-
 }
diff --git a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java
index 147239a..51e0817 100644
--- a/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java
+++ b/server/gc/src/main/java/org/apache/accumulo/gc/SimpleGarbageCollector.java
@@ -16,6 +16,9 @@
  */
 package org.apache.accumulo.gc;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.DIR;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SCANS;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.io.FileNotFoundException;
@@ -235,7 +238,7 @@ public class SimpleGarbageCollector extends AbstractServer implements Iface {
     public Stream<Reference> getReferences() {
 
       Stream<TabletMetadata> tabletStream = TabletsMetadata.builder().scanTable(tableName)
-          .checkConsistency().fetchDir().fetchFiles().fetchScans().build(getContext()).stream();
+          .checkConsistency().fetch(DIR, FILES, SCANS).build(getContext()).stream();
 
       Stream<Reference> refStream = tabletStream.flatMap(tm -> {
         Stream<Reference> refs = Stream.concat(tm.getFiles().stream(), tm.getScans().stream())
diff --git a/server/master/src/main/java/org/apache/accumulo/master/Master.java b/server/master/src/main/java/org/apache/accumulo/master/Master.java
index 0efd08d..1cdd3b1 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/Master.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/Master.java
@@ -113,7 +113,6 @@ import org.apache.accumulo.server.master.state.TabletLocationState;
 import org.apache.accumulo.server.master.state.TabletMigration;
 import org.apache.accumulo.server.master.state.TabletServerState;
 import org.apache.accumulo.server.master.state.TabletState;
-import org.apache.accumulo.server.master.state.ZooStore;
 import org.apache.accumulo.server.master.state.ZooTabletStateStore;
 import org.apache.accumulo.server.replication.ZooKeeperInitialization;
 import org.apache.accumulo.server.rpc.HighlyAvailableServiceWrapper;
@@ -1060,14 +1059,14 @@ public class Master extends AbstractServer
           }
         });
 
-    watchers.add(new TabletGroupWatcher(this, new ZooTabletStateStore(new ZooStore(context)),
-        watchers.get(1)) {
-      @Override
-      boolean canSuspendTablets() {
-        // Never allow root tablet to enter suspended state.
-        return false;
-      }
-    });
+    watchers.add(
+        new TabletGroupWatcher(this, new ZooTabletStateStore(context.getAmple()), watchers.get(1)) {
+          @Override
+          boolean canSuspendTablets() {
+            // Never allow root tablet to enter suspended state.
+            return false;
+          }
+        });
     for (TabletGroupWatcher watcher : watchers) {
       watcher.start();
     }
diff --git a/server/master/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java b/server/master/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
index 49b1303..20c47b7 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
@@ -16,6 +16,10 @@
  */
 package org.apache.accumulo.master;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FLUSH_ID;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOGS;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.nio.ByteBuffer;
@@ -180,8 +184,8 @@ public class MasterClientServiceHandler extends FateServiceHandler
       serversToFlush.clear();
 
       try (TabletsMetadata tablets =
-          TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow).fetchFlushId()
-              .fetchLocation().fetchLogs().fetchPrev().build(master.getContext())) {
+          TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow)
+              .fetch(FLUSH_ID, LOCATION, LOGS, PREV_ROW).build(master.getContext())) {
         int tabletsToWaitFor = 0;
         int tabletCount = 0;
 
diff --git a/server/master/src/main/java/org/apache/accumulo/master/TabletGroupWatcher.java b/server/master/src/main/java/org/apache/accumulo/master/TabletGroupWatcher.java
index db00210..2ff8f8c 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/TabletGroupWatcher.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/TabletGroupWatcher.java
@@ -87,6 +87,7 @@ import org.apache.accumulo.server.master.state.TabletLocationState;
 import org.apache.accumulo.server.master.state.TabletLocationState.BadLocationStateException;
 import org.apache.accumulo.server.master.state.TabletState;
 import org.apache.accumulo.server.master.state.TabletStateStore;
+import org.apache.accumulo.server.metadata.ServerAmpleImpl;
 import org.apache.accumulo.server.tablets.TabletTime;
 import org.apache.accumulo.server.util.MetadataTableUtil;
 import org.apache.hadoop.fs.Path;
@@ -720,7 +721,7 @@ abstract class TabletGroupWatcher extends Daemon {
         } else if (TabletsSection.ServerColumnFamily.TIME_COLUMN.hasColumns(key)) {
           maxLogicalTime = TabletTime.maxMetadataTime(maxLogicalTime, value.toString());
         } else if (TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN.hasColumns(key)) {
-          bw.addMutation(MetadataTableUtil.createDeleteMutation(master.getContext(),
+          bw.addMutation(ServerAmpleImpl.createDeleteMutation(master.getContext(),
               range.getTableId(), entry.getValue().toString()));
         }
       }
diff --git a/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/LoadFiles.java b/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/LoadFiles.java
index 72241cc..af58a67 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/LoadFiles.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/LoadFiles.java
@@ -17,6 +17,9 @@
 package org.apache.accumulo.master.tableOps.bulkVer2;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOADED;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -313,7 +316,7 @@ class LoadFiles extends MasterRepo {
 
     Iterator<TabletMetadata> tabletIter =
         TabletsMetadata.builder().forTable(tableId).overlapping(startRow, null).checkConsistency()
-            .fetchPrev().fetchLocation().fetchLoaded().build(master.getContext()).iterator();
+            .fetch(PREV_ROW, LOCATION, LOADED).build(master.getContext()).iterator();
 
     Loader loader;
     if (bulkInfo.tableState == TableState.ONLINE) {
diff --git a/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/PrepBulkImport.java b/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/PrepBulkImport.java
index b1015c9..86c7c86 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/PrepBulkImport.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/tableOps/bulkVer2/PrepBulkImport.java
@@ -16,6 +16,7 @@
  */
 package org.apache.accumulo.master.tableOps.bulkVer2;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly;
 
 import java.io.IOException;
@@ -161,7 +162,7 @@ public class PrepBulkImport extends MasterRepo {
         BulkSerialize.readLoadMapping(bulkDir.toString(), bulkInfo.tableId, p -> fs.open(p))) {
 
       TabletIterFactory tabletIterFactory = startRow -> TabletsMetadata.builder()
-          .forTable(bulkInfo.tableId).overlapping(startRow, null).checkConsistency().fetchPrev()
+          .forTable(bulkInfo.tableId).overlapping(startRow, null).checkConsistency().fetch(PREV_ROW)
           .build(master.getContext()).stream().map(TabletMetadata::getExtent).iterator();
 
       checkForMerge(bulkInfo.tableId.canonical(), Iterators.transform(lmi, entry -> entry.getKey()),
diff --git a/server/master/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactionDriver.java b/server/master/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactionDriver.java
index 2f740db..d85e649 100644
--- a/server/master/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactionDriver.java
+++ b/server/master/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactionDriver.java
@@ -16,6 +16,10 @@
  */
 package org.apache.accumulo.master.tableOps.compact;
 
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.COMPACT_ID;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
+
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.clientImpl.AcceptableThriftTableOperationException;
 import org.apache.accumulo.core.clientImpl.Tables;
@@ -83,8 +87,8 @@ class CompactionDriver extends MasterRepo {
     int tabletCount = 0;
 
     TabletsMetadata tablets =
-        TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow).fetchLocation()
-            .fetchPrev().fetchCompactId().build(master.getContext());
+        TabletsMetadata.builder().forTable(tableId).overlapping(startRow, endRow)
+            .fetch(LOCATION, PREV_ROW, COMPACT_ID).build(master.getContext());
 
     for (TabletMetadata tablet : tablets) {
       if (tablet.getCompactId().orElse(-1) < compactId) {
diff --git a/server/master/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java b/server/master/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java
new file mode 100644
index 0000000..2fcc46b
--- /dev/null
+++ b/server/master/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java
@@ -0,0 +1,223 @@
+/*
+ * 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.master.upgrade;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.RootTable.ZROOT_TABLET;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
+import org.apache.accumulo.core.tabletserver.log.LogEntry;
+import org.apache.accumulo.core.util.HostAndPort;
+import org.apache.accumulo.fate.zookeeper.IZooReaderWriter;
+import org.apache.accumulo.fate.zookeeper.ZooUtil;
+import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.accumulo.server.master.state.TServerInstance;
+import org.apache.accumulo.server.metadata.TabletMutatorBase;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Handles upgrading from 2.0 to 2.1
+ */
+public class Upgrader9to10 implements Upgrader {
+
+  private static final Logger log = LoggerFactory.getLogger(Upgrader9to10.class);
+
+  public static final String ZROOT_TABLET_LOCATION = ZROOT_TABLET + "/location";
+  public static final String ZROOT_TABLET_FUTURE_LOCATION = ZROOT_TABLET + "/future_location";
+  public static final String ZROOT_TABLET_LAST_LOCATION = ZROOT_TABLET + "/lastlocation";
+  public static final String ZROOT_TABLET_WALOGS = ZROOT_TABLET + "/walogs";
+  public static final String ZROOT_TABLET_CURRENT_LOGS = ZROOT_TABLET + "/current_logs";
+  public static final String ZROOT_TABLET_PATH = ZROOT_TABLET + "/dir";
+
+  @Override
+  public void upgradeZookeeper(ServerContext ctx) {
+    upgradeRootTabletMetadata(ctx);
+  }
+
+  @Override
+  public void upgradeMetadata(ServerContext ctx) {
+
+  }
+
+  private void upgradeRootTabletMetadata(ServerContext ctx) {
+    String rootMetaSer = getFromZK(ctx, ZROOT_TABLET);
+
+    if (rootMetaSer.isEmpty()) {
+      String dir = getFromZK(ctx, ZROOT_TABLET_PATH);
+      List<LogEntry> logs = getRootLogEntries(ctx);
+
+      TServerInstance last = getLocation(ctx, ZROOT_TABLET_LAST_LOCATION);
+      TServerInstance future = getLocation(ctx, ZROOT_TABLET_FUTURE_LOCATION);
+      TServerInstance current = getLocation(ctx, ZROOT_TABLET_LOCATION);
+
+      UpgradeMutator tabletMutator = new UpgradeMutator(ctx);
+
+      tabletMutator.putPrevEndRow(RootTable.EXTENT.getPrevEndRow());
+
+      tabletMutator.putDir(dir);
+
+      if (last != null)
+        tabletMutator.putLocation(last, LocationType.LAST);
+
+      if (future != null)
+        tabletMutator.putLocation(future, LocationType.FUTURE);
+
+      if (current != null)
+        tabletMutator.putLocation(current, LocationType.CURRENT);
+
+      logs.forEach(tabletMutator::putWal);
+
+      tabletMutator.mutate();
+    }
+
+    // this operation must be idempotent, so deleting after updating is very important
+
+    delete(ctx, ZROOT_TABLET_CURRENT_LOGS);
+    delete(ctx, ZROOT_TABLET_FUTURE_LOCATION);
+    delete(ctx, ZROOT_TABLET_LAST_LOCATION);
+    delete(ctx, ZROOT_TABLET_LOCATION);
+    delete(ctx, ZROOT_TABLET_WALOGS);
+    delete(ctx, ZROOT_TABLET_PATH);
+  }
+
+  private static class UpgradeMutator extends TabletMutatorBase {
+
+    private ServerContext context;
+
+    UpgradeMutator(ServerContext context) {
+      super(context, RootTable.EXTENT);
+      this.context = context;
+    }
+
+    @Override
+    public void mutate() {
+      Mutation mutation = getMutation();
+
+      try {
+        context.getZooReaderWriter().mutate(context.getZooKeeperRoot() + RootTable.ZROOT_TABLET,
+            new byte[0], ZooUtil.PUBLIC, currVal -> {
+
+              // Earlier, it was checked that root tablet metadata did not exists. However the
+              // earlier check does handle race conditions. Race conditions are unexpected. This is
+              // a sanity check when making the update in ZK using compare and set. If this fails
+              // and its not a bug, then its likely some concurrency issue. For example two masters
+              // concurrently running upgrade could cause this to fail.
+              Preconditions.checkState(currVal.length == 0,
+                  "Expected root tablet metadata to be empty!");
+
+              RootTabletMetadata rtm = new RootTabletMetadata();
+
+              rtm.update(mutation);
+
+              String json = rtm.toJson();
+
+              log.info("Upgrading root tablet metadata, writing following to ZK : \n {}", json);
+
+              return json.getBytes(UTF_8);
+            });
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+
+    }
+
+  }
+
+  protected TServerInstance getLocation(ServerContext ctx, String relpath) {
+    String str = getFromZK(ctx, relpath);
+    if (str == null) {
+      return null;
+    }
+
+    String[] parts = str.split("[|]", 2);
+    HostAndPort address = HostAndPort.fromString(parts[0]);
+    if (parts.length > 1 && parts[1] != null && parts[1].length() > 0) {
+      return new TServerInstance(address, parts[1]);
+    } else {
+      // a 1.2 location specification: DO NOT WANT
+      return null;
+    }
+  }
+
+  static List<LogEntry> getRootLogEntries(ServerContext context) {
+
+    try {
+      ArrayList<LogEntry> result = new ArrayList<>();
+
+      IZooReaderWriter zoo = context.getZooReaderWriter();
+      String root = context.getZooKeeperRoot() + ZROOT_TABLET_WALOGS;
+      // there's a little race between getting the children and fetching
+      // the data. The log can be removed in between.
+      outer: while (true) {
+        result.clear();
+        for (String child : zoo.getChildren(root)) {
+          try {
+            LogEntry e = LogEntry.fromBytes(zoo.getData(root + "/" + child, null));
+            // upgrade from !0;!0<< -> +r<<
+            e = new LogEntry(RootTable.EXTENT, 0, e.server, e.filename);
+            result.add(e);
+          } catch (KeeperException.NoNodeException ex) {
+            // TODO I think this is a bug, probably meant to continue to while loop... was probably
+            // a bug in the original code.
+            continue outer;
+          }
+        }
+        break;
+      }
+
+      return result;
+    } catch (KeeperException | InterruptedException | IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private String getFromZK(ServerContext ctx, String relpath) {
+    try {
+      byte[] data = ctx.getZooReaderWriter().getData(ctx.getZooKeeperRoot() + relpath, null);
+      if (data == null)
+        return null;
+
+      return new String(data, StandardCharsets.UTF_8);
+    } catch (KeeperException | InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void delete(ServerContext ctx, String relpath) {
+    try {
+      ctx.getZooReaderWriter().recursiveDelete(ctx.getZooKeeperRoot() + relpath,
+          NodeMissingPolicy.SKIP);
+    } catch (KeeperException | InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/server/master/src/test/java/org/apache/accumulo/master/state/RootTabletStateStoreTest.java b/server/master/src/test/java/org/apache/accumulo/master/state/RootTabletStateStoreTest.java
index c455742..4e5ca47 100644
--- a/server/master/src/test/java/org/apache/accumulo/master/state/RootTabletStateStoreTest.java
+++ b/server/master/src/test/java/org/apache/accumulo/master/state/RootTabletStateStoreTest.java
@@ -16,141 +16,70 @@
  */
 package org.apache.accumulo.master.state;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 
+import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.metadata.RootTable;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
 import org.apache.accumulo.core.util.HostAndPort;
 import org.apache.accumulo.server.master.state.Assignment;
-import org.apache.accumulo.server.master.state.DistributedStore;
 import org.apache.accumulo.server.master.state.DistributedStoreException;
 import org.apache.accumulo.server.master.state.TServerInstance;
 import org.apache.accumulo.server.master.state.TabletLocationState;
 import org.apache.accumulo.server.master.state.TabletLocationState.BadLocationStateException;
 import org.apache.accumulo.server.master.state.ZooTabletStateStore;
+import org.apache.accumulo.server.metadata.TabletMutatorBase;
 import org.junit.Test;
 
-public class RootTabletStateStoreTest {
-
-  static class Node {
-    Node(String name) {
-      this.name = name;
-    }
-
-    List<Node> children = new ArrayList<>();
-    String name;
-    byte[] value = {};
-
-    Node find(String name) {
-      for (Node node : children)
-        if (node.name.equals(name))
-          return node;
-      return null;
-    }
-  }
-
-  static class FakeZooStore implements DistributedStore {
+import com.google.common.base.Preconditions;
 
-    Node root = new Node("/");
+public class RootTabletStateStoreTest {
 
-    private Node recurse(Node root, String[] path, int depth) {
-      if (depth == path.length)
-        return root;
-      Node child = root.find(path[depth]);
-      if (child == null)
-        return null;
-      return recurse(child, path, depth + 1);
-    }
+  private static class TestAmple implements Ample {
 
-    private Node navigate(String path) {
-      path = path.replaceAll("/$", "");
-      return recurse(root, path.split("/"), 1);
-    }
+    private String json =
+        new String(RootTabletMetadata.getInitialJson("/some/dir"), StandardCharsets.UTF_8);
 
     @Override
-    public List<String> getChildren(String path) {
-      Node node = navigate(path);
-      if (node == null)
-        return Collections.emptyList();
-      List<String> children = new ArrayList<>(node.children.size());
-      for (Node child : node.children)
-        children.add(child.name);
-      return children;
+    public TabletMetadata readTablet(KeyExtent extent, ColumnType... colsToFetch) {
+      Preconditions.checkArgument(extent.equals(RootTable.EXTENT));
+      return RootTabletMetadata.fromJson(json).convertToTabletMetadata();
     }
 
     @Override
-    public void put(String path, byte[] bs) {
-      create(path).value = bs;
-    }
+    public TabletMutator mutateTablet(KeyExtent extent) {
+      Preconditions.checkArgument(extent.equals(RootTable.EXTENT));
+      return new TabletMutatorBase(null, extent) {
 
-    private Node create(String path) {
-      String[] parts = path.split("/");
-      return recurseCreate(root, parts, 1);
-    }
+        @Override
+        public void mutate() {
+          Mutation m = getMutation();
 
-    private Node recurseCreate(Node root, String[] path, int index) {
-      if (path.length == index)
-        return root;
-      Node node = root.find(path[index]);
-      if (node == null) {
-        node = new Node(path[index]);
-        root.children.add(node);
-      }
-      return recurseCreate(node, path, index + 1);
-    }
+          RootTabletMetadata rtm = RootTabletMetadata.fromJson(json);
 
-    @Override
-    public void remove(String path) {
-      String[] parts = path.split("/");
-      String[] parentPath = Arrays.copyOf(parts, parts.length - 1);
-      Node parent = recurse(root, parentPath, 1);
-      if (parent == null)
-        return;
-      Node child = parent.find(parts[parts.length - 1]);
-      if (child != null)
-        parent.children.remove(child);
-    }
+          rtm.update(m);
 
-    @Override
-    public byte[] get(String path) {
-      Node node = navigate(path);
-      if (node != null)
-        return node.value;
-      return null;
+          json = rtm.toJson();
+        }
+      };
     }
-  }
 
-  @Test
-  public void testFakeZoo() throws DistributedStoreException {
-    DistributedStore store = new FakeZooStore();
-    store.put("/a/b/c", "abc".getBytes());
-    byte[] abc = store.get("/a/b/c");
-    assertArrayEquals(abc, "abc".getBytes());
-    byte[] empty = store.get("/a/b");
-    assertArrayEquals(empty, "".getBytes());
-    store.put("/a/b", "ab".getBytes());
-    assertArrayEquals(store.get("/a/b"), "ab".getBytes());
-    store.put("/a/b/b", "abb".getBytes());
-    List<String> children = store.getChildren("/a/b");
-    assertEquals(new HashSet<>(children), new HashSet<>(Arrays.asList("b", "c")));
-    store.remove("/a/b/c");
-    children = store.getChildren("/a/b");
-    assertEquals(new HashSet<>(children), new HashSet<>(Arrays.asList("b")));
   }
 
   @Test
   public void testRootTabletStateStore() throws DistributedStoreException {
-    ZooTabletStateStore tstore = new ZooTabletStateStore(new FakeZooStore());
+    ZooTabletStateStore tstore = new ZooTabletStateStore(new TestAmple());
     KeyExtent root = RootTable.EXTENT;
     String sessionId = "this is my unique session data";
     TServerInstance server =
@@ -212,7 +141,4 @@ public class RootTabletStateStoreTest {
       fail("should not get here");
     } catch (IllegalArgumentException ex) {}
   }
-
-  // @Test
-  // public void testMetaDataStore() { } // see functional test
 }
diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java
index 11df49f..557c53c 100644
--- a/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java
+++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java
@@ -120,6 +120,9 @@ import org.apache.accumulo.core.master.thrift.TabletServerStatus;
 import org.apache.accumulo.core.metadata.MetadataTable;
 import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
 import org.apache.accumulo.core.replication.ReplicationConstants;
 import org.apache.accumulo.core.replication.thrift.ReplicationServicer;
 import org.apache.accumulo.core.rpc.ThriftUtil;
@@ -191,7 +194,6 @@ import org.apache.accumulo.server.master.state.TServerInstance;
 import org.apache.accumulo.server.master.state.TabletLocationState;
 import org.apache.accumulo.server.master.state.TabletLocationState.BadLocationStateException;
 import org.apache.accumulo.server.master.state.TabletStateStore;
-import org.apache.accumulo.server.master.state.ZooTabletStateStore;
 import org.apache.accumulo.server.master.tableOps.UserCompactionConfig;
 import org.apache.accumulo.server.problems.ProblemReport;
 import org.apache.accumulo.server.problems.ProblemReports;
@@ -2926,24 +2928,25 @@ public class TabletServer extends AbstractServer {
 
   private static Pair<Text,KeyExtent> verifyRootTablet(ServerContext context,
       TServerInstance instance) throws AccumuloException {
-    ZooTabletStateStore store = new ZooTabletStateStore(context);
-    if (!store.iterator().hasNext()) {
+
+    TabletMetadata rootMeta = context.getAmple().readTablet(RootTable.EXTENT);
+
+    if (rootMeta.getLocation() == null) {
       throw new AccumuloException("Illegal state: location is not set in zookeeper");
     }
-    TabletLocationState next = store.iterator().next();
-    if (!instance.equals(next.future)) {
-      throw new AccumuloException("Future location is not to this server for the root tablet");
-    }
 
-    if (next.current != null) {
-      throw new AccumuloException("Root tablet already has a location set");
+    Location loc = rootMeta.getLocation();
+
+    if (loc.getType() == LocationType.FUTURE && !instance.equals(new TServerInstance(loc))) {
+      throw new AccumuloException(
+          "Future location is not to this server for the root tablet " + loc);
     }
 
-    try {
-      return new Pair<>(new Text(MetadataTableUtil.getRootTabletDir(context)), null);
-    } catch (IOException e) {
-      throw new AccumuloException(e);
+    if (loc.getType() == LocationType.CURRENT) {
+      throw new AccumuloException("Root tablet already has a location set " + loc);
     }
+
+    return new Pair<>(new Text(rootMeta.getDir()), null);
   }
 
   public static Pair<Text,KeyExtent> verifyTabletInformation(ServerContext context,
diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/TabletData.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/TabletData.java
index f29409c..919671d 100644
--- a/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/TabletData.java
+++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/TabletData.java
@@ -47,6 +47,7 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.La
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.fs.FileRef;
@@ -54,7 +55,6 @@ import org.apache.accumulo.server.fs.VolumeManager;
 import org.apache.accumulo.server.fs.VolumeUtil;
 import org.apache.accumulo.server.master.state.TServerInstance;
 import org.apache.accumulo.server.tablets.TabletTime;
-import org.apache.accumulo.server.util.MetadataTableUtil;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
@@ -134,8 +134,10 @@ public class TabletData {
   // Read basic root table metadata from zookeeper
   public TabletData(ServerContext context, VolumeManager fs, AccumuloConfiguration conf)
       throws IOException {
-    directory =
-        VolumeUtil.switchRootTableVolume(context, MetadataTableUtil.getRootTabletDir(context));
+
+    TabletMetadata rootMeta = context.getAmple().readTablet(RootTable.EXTENT);
+
+    directory = VolumeUtil.switchRootTableVolume(context, rootMeta.getDir());
 
     Path location = new Path(directory);
 
@@ -167,7 +169,7 @@ public class TabletData {
     }
 
     try {
-      logEntries = MetadataTableUtil.getLogEntries(context, RootTable.EXTENT);
+      logEntries = new ArrayList<>(rootMeta.getLogs());
     } catch (Exception ex) {
       throw new RuntimeException("Unable to read tablet log entries", ex);
     }
diff --git a/test/src/main/java/org/apache/accumulo/test/VolumeIT.java b/test/src/main/java/org/apache/accumulo/test/VolumeIT.java
index de3b6e0..1e9e01e 100644
--- a/test/src/main/java/org/apache/accumulo/test/VolumeIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/VolumeIT.java
@@ -16,7 +16,6 @@
  */
 package org.apache.accumulo.test;
 
-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.assertTrue;
@@ -45,6 +44,7 @@ import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.admin.DiskUsage;
 import org.apache.accumulo.core.client.admin.NewTableConfiguration;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.clientImpl.ClientContext;
 import org.apache.accumulo.core.conf.ClientProperty;
 import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.data.Key;
@@ -59,8 +59,6 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.security.TablePermission;
-import org.apache.accumulo.fate.zookeeper.ZooReader;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
 import org.apache.accumulo.server.ServerConstants;
 import org.apache.accumulo.server.init.Initialize;
@@ -479,10 +477,9 @@ public class VolumeIT extends ConfigurableMacBase {
       verifyVolumesUsed(client, tableNames[0], true, v2);
 
       // check that root tablet is not on volume 1
-      ZooReader zreader = new ZooReader(cluster.getZooKeepers(), 30000);
-      String zpath = ZooUtil.getRoot(client.instanceOperations().getInstanceID())
-          + RootTable.ZROOT_TABLET_PATH;
-      String rootTabletDir = new String(zreader.getData(zpath, false, null), UTF_8);
+      String rootTabletDir =
+          ((ClientContext) client).getAmple().readTablet(RootTable.EXTENT).getDir();
+
       assertTrue(rootTabletDir.startsWith(v2.toString()));
 
       client.tableOperations().clone(tableNames[0], tableNames[1], true, new HashMap<>(),
@@ -544,10 +541,8 @@ public class VolumeIT extends ConfigurableMacBase {
     verifyVolumesUsed(client, tableNames[1], true, v8, v9);
 
     // check that root tablet is not on volume 1 or 2
-    ZooReader zreader = new ZooReader(cluster.getZooKeepers(), 30000);
-    String zpath =
-        ZooUtil.getRoot(client.instanceOperations().getInstanceID()) + RootTable.ZROOT_TABLET_PATH;
-    String rootTabletDir = new String(zreader.getData(zpath, false, null), UTF_8);
+    String rootTabletDir =
+        ((ClientContext) client).getAmple().readTablet(RootTable.EXTENT).getDir();
     assertTrue(rootTabletDir.startsWith(v8.toString()) || rootTabletDir.startsWith(v9.toString()));
 
     client.tableOperations().clone(tableNames[1], tableNames[2], true, new HashMap<>(),
diff --git a/test/src/main/java/org/apache/accumulo/test/functional/BulkLoadIT.java b/test/src/main/java/org/apache/accumulo/test/functional/BulkLoadIT.java
index dd8ecbb..bd247f8 100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/BulkLoadIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/BulkLoadIT.java
@@ -17,6 +17,9 @@
 package org.apache.accumulo.test.functional;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOADED;
+import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -411,8 +414,8 @@ public class BulkLoadIT extends SharedMiniClusterBase {
     Set<String> endRowsSeen = new HashSet<>();
 
     String id = client.tableOperations().tableIdMap().get(tableName);
-    try (TabletsMetadata tablets = TabletsMetadata.builder().forTable(TableId.of(id)).fetchFiles()
-        .fetchLoaded().fetchPrev().build(client)) {
+    try (TabletsMetadata tablets = TabletsMetadata.builder().forTable(TableId.of(id))
+        .fetch(FILES, LOADED, PREV_ROW).build(client)) {
       for (TabletMetadata tablet : tablets) {
         assertTrue(tablet.getLoaded().isEmpty());
 


Mime
View raw message