You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by sy...@apache.org on 2017/03/31 22:54:34 UTC

[4/9] hbase git commit: HBASE-17520 Implement isTableEnabled/Disabled/Available methods

HBASE-17520 Implement isTableEnabled/Disabled/Available methods


Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/752b258b
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/752b258b
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/752b258b

Branch: refs/heads/hbase-12439
Commit: 752b258b7c30aa375b5bb7a33abf435f37e8c877
Parents: b290d14
Author: Guanghao Zhang <zg...@apache.org>
Authored: Thu Mar 30 13:37:17 2017 +0800
Committer: Guanghao Zhang <zg...@apache.org>
Committed: Thu Mar 30 13:37:17 2017 +0800

----------------------------------------------------------------------
 .../hadoop/hbase/AsyncMetaTableAccessor.java    | 432 ++++++++++++++++++-
 .../apache/hadoop/hbase/client/AsyncAdmin.java  |  31 ++
 .../hadoop/hbase/client/AsyncHBaseAdmin.java    | 109 ++++-
 .../hbase/client/TestAsyncTableAdminApi.java    |  34 +-
 4 files changed, 603 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/752b258b/hbase-client/src/main/java/org/apache/hadoop/hbase/AsyncMetaTableAccessor.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/AsyncMetaTableAccessor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/AsyncMetaTableAccessor.java
index d09d29e..6988047 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/AsyncMetaTableAccessor.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/AsyncMetaTableAccessor.java
@@ -20,20 +20,33 @@ package org.apache.hadoop.hbase;
 import static org.apache.hadoop.hbase.TableName.META_TABLE_NAME;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Optional;
+import java.util.SortedMap;
 import java.util.concurrent.CompletableFuture;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.MetaTableAccessor.CollectingVisitor;
+import org.apache.hadoop.hbase.MetaTableAccessor.QueryType;
+import org.apache.hadoop.hbase.MetaTableAccessor.Visitor;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
-import org.apache.hadoop.hbase.client.AsyncConnection;
+import org.apache.hadoop.hbase.client.Consistency;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.RawAsyncTable;
+import org.apache.hadoop.hbase.client.RawScanResultConsumer;
+import org.apache.hadoop.hbase.client.RegionReplicaUtil;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.exceptions.DeserializationException;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.Pair;
 
@@ -46,6 +59,14 @@ public class AsyncMetaTableAccessor {
 
   private static final Log LOG = LogFactory.getLog(AsyncMetaTableAccessor.class);
 
+
+  /** The delimiter for meta columns for replicaIds &gt; 0 */
+  private static final char META_REPLICA_ID_DELIMITER = '_';
+
+  /** A regex for parsing server columns from meta. See above javadoc for meta layout */
+  private static final Pattern SERVER_COLUMN_PATTERN = Pattern
+      .compile("^server(_[0-9a-fA-F]{4})?$");
+
   public static CompletableFuture<Boolean> tableExists(RawAsyncTable metaTable, TableName tableName) {
     if (tableName.equals(META_TABLE_NAME)) {
       return CompletableFuture.completedFuture(true);
@@ -122,6 +143,350 @@ public class AsyncMetaTableAccessor {
   }
 
   /**
+   * Used to get table regions' info and server.
+   * @param metaTable
+   * @param tableName table we're looking for, can be null for getting all regions
+   * @return the list of regioninfos and server. The return value will be wrapped by a
+   *         {@link CompletableFuture}.
+   */
+  public static CompletableFuture<List<Pair<HRegionInfo, ServerName>>> getTableRegionsAndLocations(
+      RawAsyncTable metaTable, final Optional<TableName> tableName) {
+    return getTableRegionsAndLocations(metaTable, tableName, true);
+  }
+
+  /**
+   * Used to get table regions' info and server.
+   * @param metaTable
+   * @param tableName table we're looking for, can be null for getting all regions
+   * @param excludeOfflinedSplitParents don't return split parents
+   * @return the list of regioninfos and server. The return value will be wrapped by a
+   *         {@link CompletableFuture}.
+   */
+  public static CompletableFuture<List<Pair<HRegionInfo, ServerName>>> getTableRegionsAndLocations(
+      RawAsyncTable metaTable, final Optional<TableName> tableName,
+      final boolean excludeOfflinedSplitParents) {
+    CompletableFuture<List<Pair<HRegionInfo, ServerName>>> future = new CompletableFuture<>();
+    if (tableName.filter((t) -> t.equals(TableName.META_TABLE_NAME)).isPresent()) {
+      future.completeExceptionally(new IOException(
+          "This method can't be used to locate meta regions;" + " use MetaTableLocator instead"));
+    }
+
+    // Make a version of CollectingVisitor that collects HRegionInfo and ServerAddress
+    CollectingVisitor<Pair<HRegionInfo, ServerName>> visitor = new CollectingVisitor<Pair<HRegionInfo, ServerName>>() {
+      private Optional<RegionLocations> current = null;
+
+      @Override
+      public boolean visit(Result r) throws IOException {
+        current = getRegionLocations(r);
+        if (!current.isPresent() || current.get().getRegionLocation().getRegionInfo() == null) {
+          LOG.warn("No serialized HRegionInfo in " + r);
+          return true;
+        }
+        HRegionInfo hri = current.get().getRegionLocation().getRegionInfo();
+        if (excludeOfflinedSplitParents && hri.isSplitParent()) return true;
+        // Else call super and add this Result to the collection.
+        return super.visit(r);
+      }
+
+      @Override
+      void add(Result r) {
+        if (!current.isPresent()) {
+          return;
+        }
+        for (HRegionLocation loc : current.get().getRegionLocations()) {
+          if (loc != null) {
+            this.results.add(new Pair<HRegionInfo, ServerName>(loc.getRegionInfo(), loc
+                .getServerName()));
+          }
+        }
+      }
+    };
+
+    scanMeta(metaTable, tableName, QueryType.REGION, visitor).whenComplete((v, error) -> {
+      if (error != null) {
+        future.completeExceptionally(error);
+        return;
+      }
+      future.complete(visitor.getResults());
+    });
+    return future;
+  }
+
+  /**
+   * Performs a scan of META table for given table.
+   * @param metaTable
+   * @param tableName table withing we scan
+   * @param type scanned part of meta
+   * @param visitor Visitor invoked against each row
+   */
+  private static CompletableFuture<Void> scanMeta(RawAsyncTable metaTable,
+      Optional<TableName> tableName, QueryType type, final Visitor visitor) {
+    return scanMeta(metaTable, getTableStartRowForMeta(tableName, type),
+      getTableStopRowForMeta(tableName, type), type, Integer.MAX_VALUE, visitor);
+  }
+
+  /**
+   * Performs a scan of META table for given table.
+   * @param metaTable
+   * @param startRow Where to start the scan
+   * @param stopRow Where to stop the scan
+   * @param type scanned part of meta
+   * @param maxRows maximum rows to return
+   * @param visitor Visitor invoked against each row
+   */
+  private static CompletableFuture<Void> scanMeta(RawAsyncTable metaTable, Optional<byte[]> startRow,
+      Optional<byte[]> stopRow, QueryType type, int maxRows, final Visitor visitor) {
+    int rowUpperLimit = maxRows > 0 ? maxRows : Integer.MAX_VALUE;
+    Scan scan = getMetaScan(metaTable, rowUpperLimit);
+    for (byte[] family : type.getFamilies()) {
+      scan.addFamily(family);
+    }
+    startRow.ifPresent(scan::withStartRow);
+    stopRow.ifPresent(scan::withStopRow);
+
+    if (LOG.isTraceEnabled()) {
+      LOG.trace("Scanning META" + " starting at row=" + Bytes.toStringBinary(scan.getStartRow())
+          + " stopping at row=" + Bytes.toStringBinary(scan.getStopRow()) + " for max="
+          + rowUpperLimit + " with caching=" + scan.getCaching());
+    }
+
+    CompletableFuture<Void> future = new CompletableFuture<Void>();
+    metaTable.scan(scan, new MetaTableRawScanResultConsumer(rowUpperLimit, visitor, future));
+    return future;
+  }
+
+  private static final class MetaTableRawScanResultConsumer implements RawScanResultConsumer {
+
+    private int currentRowCount;
+
+    private final int rowUpperLimit;
+
+    private final Visitor visitor;
+
+    private final CompletableFuture<Void> future;
+
+    MetaTableRawScanResultConsumer(int rowUpperLimit, Visitor visitor, CompletableFuture<Void> future) {
+      this.rowUpperLimit = rowUpperLimit;
+      this.visitor = visitor;
+      this.future = future;
+      this.currentRowCount = 0;
+    }
+
+    @Override
+    public void onError(Throwable error) {
+      future.completeExceptionally(error);
+    }
+
+    @Override
+    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_NONNULL_PARAM_VIOLATION",
+      justification = "https://github.com/findbugsproject/findbugs/issues/79")
+    public void onComplete() {
+      future.complete(null);
+    }
+
+    @Override
+    public void onNext(Result[] results, ScanController controller) {
+      for (Result result : results) {
+        try {
+          if (!visitor.visit(result)) {
+            controller.terminate();
+          }
+        } catch (IOException e) {
+          future.completeExceptionally(e);
+          controller.terminate();
+        }
+        if (++currentRowCount >= rowUpperLimit) {
+          controller.terminate();
+        }
+      }
+    }
+  }
+
+  private static Scan getMetaScan(RawAsyncTable metaTable, int rowUpperLimit) {
+    Scan scan = new Scan();
+    int scannerCaching = metaTable.getConfiguration().getInt(HConstants.HBASE_META_SCANNER_CACHING,
+      HConstants.DEFAULT_HBASE_META_SCANNER_CACHING);
+    if (metaTable.getConfiguration().getBoolean(HConstants.USE_META_REPLICAS,
+      HConstants.DEFAULT_USE_META_REPLICAS)) {
+      scan.setConsistency(Consistency.TIMELINE);
+    }
+    if (rowUpperLimit <= scannerCaching) {
+      scan.setLimit(rowUpperLimit);
+    }
+    int rows = Math.min(rowUpperLimit, scannerCaching);
+    scan.setCaching(rows);
+    return scan;
+  }
+
+  /**
+   * Returns an HRegionLocationList extracted from the result.
+   * @return an HRegionLocationList containing all locations for the region range or null if we
+   *         can't deserialize the result.
+   */
+  private static Optional<RegionLocations> getRegionLocations(final Result r) {
+    if (r == null) return Optional.empty();
+    Optional<HRegionInfo> regionInfo = getHRegionInfo(r, getRegionInfoColumn());
+    if (!regionInfo.isPresent()) return Optional.empty();
+
+    List<HRegionLocation> locations = new ArrayList<HRegionLocation>(1);
+    NavigableMap<byte[], NavigableMap<byte[], byte[]>> familyMap = r.getNoVersionMap();
+
+    locations.add(getRegionLocation(r, regionInfo.get(), 0));
+
+    NavigableMap<byte[], byte[]> infoMap = familyMap.get(getCatalogFamily());
+    if (infoMap == null) return Optional.of(new RegionLocations(locations));
+
+    // iterate until all serverName columns are seen
+    int replicaId = 0;
+    byte[] serverColumn = getServerColumn(replicaId);
+    SortedMap<byte[], byte[]> serverMap = null;
+    serverMap = infoMap.tailMap(serverColumn, false);
+
+    if (serverMap.isEmpty()) return Optional.of(new RegionLocations(locations));
+
+    for (Map.Entry<byte[], byte[]> entry : serverMap.entrySet()) {
+      replicaId = parseReplicaIdFromServerColumn(entry.getKey());
+      if (replicaId < 0) {
+        break;
+      }
+      HRegionLocation location = getRegionLocation(r, regionInfo.get(), replicaId);
+      // In case the region replica is newly created, it's location might be null. We usually do not
+      // have HRL's in RegionLocations object with null ServerName. They are handled as null HRLs.
+      if (location == null || location.getServerName() == null) {
+        locations.add(null);
+      } else {
+        locations.add(location);
+      }
+    }
+
+    return Optional.of(new RegionLocations(locations));
+  }
+
+  /**
+   * Returns the HRegionLocation parsed from the given meta row Result
+   * for the given regionInfo and replicaId. The regionInfo can be the default region info
+   * for the replica.
+   * @param r the meta row result
+   * @param regionInfo RegionInfo for default replica
+   * @param replicaId the replicaId for the HRegionLocation
+   * @return HRegionLocation parsed from the given meta row Result for the given replicaId
+   */
+  private static HRegionLocation getRegionLocation(final Result r, final HRegionInfo regionInfo,
+      final int replicaId) {
+    Optional<ServerName> serverName = getServerName(r, replicaId);
+    long seqNum = getSeqNumDuringOpen(r, replicaId);
+    HRegionInfo replicaInfo = RegionReplicaUtil.getRegionInfoForReplica(regionInfo, replicaId);
+    return new HRegionLocation(replicaInfo, serverName.orElse(null), seqNum);
+  }
+
+  /**
+   * Returns a {@link ServerName} from catalog table {@link Result}.
+   * @param r Result to pull from
+   * @return A ServerName instance.
+   */
+  private static Optional<ServerName> getServerName(final Result r, final int replicaId) {
+    byte[] serverColumn = getServerColumn(replicaId);
+    Cell cell = r.getColumnLatestCell(getCatalogFamily(), serverColumn);
+    if (cell == null || cell.getValueLength() == 0) return Optional.empty();
+    String hostAndPort = Bytes.toString(cell.getValueArray(), cell.getValueOffset(),
+      cell.getValueLength());
+    byte[] startcodeColumn = getStartCodeColumn(replicaId);
+    cell = r.getColumnLatestCell(getCatalogFamily(), startcodeColumn);
+    if (cell == null || cell.getValueLength() == 0) return Optional.empty();
+    try {
+      return Optional.of(ServerName.valueOf(hostAndPort,
+        Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength())));
+    } catch (IllegalArgumentException e) {
+      LOG.error("Ignoring invalid region for server " + hostAndPort + "; cell=" + cell, e);
+      return Optional.empty();
+    }
+  }
+
+  /**
+   * The latest seqnum that the server writing to meta observed when opening the region.
+   * E.g. the seqNum when the result of {@link #getServerName(Result, int)} was written.
+   * @param r Result to pull the seqNum from
+   * @return SeqNum, or HConstants.NO_SEQNUM if there's no value written.
+   */
+  private static long getSeqNumDuringOpen(final Result r, final int replicaId) {
+    Cell cell = r.getColumnLatestCell(getCatalogFamily(), getSeqNumColumn(replicaId));
+    if (cell == null || cell.getValueLength() == 0) return HConstants.NO_SEQNUM;
+    return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
+  }
+
+  /**
+   * @param tableName table we're working with
+   * @return start row for scanning META according to query type
+   */
+  private static Optional<byte[]> getTableStartRowForMeta(Optional<TableName> tableName,
+      QueryType type) {
+    return tableName.map((table) -> {
+      switch (type) {
+      case REGION:
+        byte[] startRow = new byte[table.getName().length + 2];
+        System.arraycopy(table.getName(), 0, startRow, 0, table.getName().length);
+        startRow[startRow.length - 2] = HConstants.DELIMITER;
+        startRow[startRow.length - 1] = HConstants.DELIMITER;
+        return startRow;
+      case ALL:
+      case TABLE:
+      default:
+        return table.getName();
+      }
+    });
+  }
+
+  /**
+   * @param tableName table we're working with
+   * @return stop row for scanning META according to query type
+   */
+  private static Optional<byte[]> getTableStopRowForMeta(Optional<TableName> tableName,
+      QueryType type) {
+    return tableName.map((table) -> {
+      final byte[] stopRow;
+      switch (type) {
+      case REGION:
+        stopRow = new byte[table.getName().length + 3];
+        System.arraycopy(table.getName(), 0, stopRow, 0, table.getName().length);
+        stopRow[stopRow.length - 3] = ' ';
+        stopRow[stopRow.length - 2] = HConstants.DELIMITER;
+        stopRow[stopRow.length - 1] = HConstants.DELIMITER;
+        break;
+      case ALL:
+      case TABLE:
+      default:
+        stopRow = new byte[table.getName().length + 1];
+        System.arraycopy(table.getName(), 0, stopRow, 0, table.getName().length);
+        stopRow[stopRow.length - 1] = ' ';
+        break;
+      }
+      return stopRow;
+    });
+  }
+
+  /**
+   * Returns the HRegionInfo object from the column {@link HConstants#CATALOG_FAMILY} and
+   * <code>qualifier</code> of the catalog table result.
+   * @param r a Result object from the catalog table scan
+   * @param qualifier Column family qualifier
+   * @return An HRegionInfo instance.
+   */
+  private static Optional<HRegionInfo> getHRegionInfo(final Result r, byte[] qualifier) {
+    Cell cell = r.getColumnLatestCell(getCatalogFamily(), qualifier);
+    if (cell == null) return Optional.empty();
+    return Optional.ofNullable(HRegionInfo.parseFromOrNull(cell.getValueArray(),
+      cell.getValueOffset(), cell.getValueLength()));
+  }
+
+  /**
+   * Returns the column family used for meta columns.
+   * @return HConstants.CATALOG_FAMILY.
+   */
+  private static byte[] getCatalogFamily() {
+    return HConstants.CATALOG_FAMILY;
+  }
+
+  /**
    * Returns the column family used for table columns.
    * @return HConstants.TABLE_FAMILY.
    */
@@ -130,10 +495,75 @@ public class AsyncMetaTableAccessor {
   }
 
   /**
+   * Returns the column qualifier for serialized region info
+   * @return HConstants.REGIONINFO_QUALIFIER
+   */
+  private static byte[] getRegionInfoColumn() {
+    return HConstants.REGIONINFO_QUALIFIER;
+  }
+
+  /**
    * Returns the column qualifier for serialized table state
    * @return HConstants.TABLE_STATE_QUALIFIER
    */
   private static byte[] getStateColumn() {
     return HConstants.TABLE_STATE_QUALIFIER;
   }
+
+  /**
+   * Returns the column qualifier for server column for replicaId
+   * @param replicaId the replicaId of the region
+   * @return a byte[] for server column qualifier
+   */
+  private static byte[] getServerColumn(int replicaId) {
+    return replicaId == 0
+      ? HConstants.SERVER_QUALIFIER
+      : Bytes.toBytes(HConstants.SERVER_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
+      + String.format(HRegionInfo.REPLICA_ID_FORMAT, replicaId));
+  }
+
+  /**
+   * Returns the column qualifier for server start code column for replicaId
+   * @param replicaId the replicaId of the region
+   * @return a byte[] for server start code column qualifier
+   */
+  private static byte[] getStartCodeColumn(int replicaId) {
+    return replicaId == 0
+      ? HConstants.STARTCODE_QUALIFIER
+      : Bytes.toBytes(HConstants.STARTCODE_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
+      + String.format(HRegionInfo.REPLICA_ID_FORMAT, replicaId));
+  }
+
+  /**
+   * Returns the column qualifier for seqNum column for replicaId
+   * @param replicaId the replicaId of the region
+   * @return a byte[] for seqNum column qualifier
+   */
+  private static byte[] getSeqNumColumn(int replicaId) {
+    return replicaId == 0
+      ? HConstants.SEQNUM_QUALIFIER
+      : Bytes.toBytes(HConstants.SEQNUM_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
+      + String.format(HRegionInfo.REPLICA_ID_FORMAT, replicaId));
+  }
+
+  /**
+   * Parses the replicaId from the server column qualifier. See top of the class javadoc
+   * for the actual meta layout
+   * @param serverColumn the column qualifier
+   * @return an int for the replicaId
+   */
+  private static int parseReplicaIdFromServerColumn(byte[] serverColumn) {
+    String serverStr = Bytes.toString(serverColumn);
+
+    Matcher matcher = SERVER_COLUMN_PATTERN.matcher(serverStr);
+    if (matcher.matches() && matcher.groupCount() > 0) {
+      String group = matcher.group(1);
+      if (group != null && group.length() > 0) {
+        return Integer.parseInt(group.substring(1), 16);
+      } else {
+        return 0;
+      }
+    }
+    return -1;
+  }
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/752b258b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index 5a13ede..9945c40 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -222,6 +222,30 @@ public interface AsyncAdmin {
   CompletableFuture<HTableDescriptor[]> disableTables(Pattern pattern);
 
   /**
+   * @param tableName name of table to check
+   * @return true if table is off-line. The return value will be wrapped by a
+   *         {@link CompletableFuture}.
+   */
+  CompletableFuture<Boolean> isTableDisabled(TableName tableName);
+
+  /**
+   * @param tableName name of table to check
+   * @return true if all regions of the table are available. The return value will be wrapped by a
+   *         {@link CompletableFuture}.
+   */
+  CompletableFuture<Boolean> isTableAvailable(TableName tableName);
+
+  /**
+   * Use this api to check if the table has been created with the specified number of splitkeys
+   * which was used while creating the given table. Note : If this api is used after a table's
+   * region gets splitted, the api may return false. The return value will be wrapped by a
+   * {@link CompletableFuture}.
+   * @param tableName name of table to check
+   * @param splitKeys keys to check if the table has been created with all split keys
+   */
+  CompletableFuture<Boolean> isTableAvailable(TableName tableName, byte[][] splitKeys);
+
+  /**
    * Get the status of alter command - indicates how many regions have received the updated schema
    * Asynchronous operation.
    * @param tableName TableName instance
@@ -286,6 +310,13 @@ public interface AsyncAdmin {
   CompletableFuture<NamespaceDescriptor[]> listNamespaceDescriptors();
 
   /**
+   * @param tableName name of table to check
+   * @return true if table is on-line. The return value will be wrapped by a
+   *         {@link CompletableFuture}.
+   */
+  CompletableFuture<Boolean> isTableEnabled(TableName tableName);
+
+  /**
    * Turn the load balancer on or off.
    * @param on
    * @return Previous balancer value wrapped by a {@link CompletableFuture}.

http://git-wip-us.apache.org/repos/asf/hbase/blob/752b258b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index 5ae30d7..54f0766 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -44,9 +45,9 @@ import org.apache.hadoop.hbase.NotServingRegionException;
 import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.AsyncMetaTableAccessor;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.UnknownRegionException;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -452,6 +453,112 @@ public class AsyncHBaseAdmin implements AsyncAdmin {
 
 
   @Override
+  public CompletableFuture<Boolean> isTableEnabled(TableName tableName) {
+    CompletableFuture<Boolean> future = new CompletableFuture<>();
+    AsyncMetaTableAccessor.getTableState(metaTable, tableName).whenComplete((state, error) -> {
+      if (error != null) {
+        future.completeExceptionally(error);
+        return;
+      }
+      if (state.isPresent()) {
+        future.complete(state.get().inStates(TableState.State.ENABLED));
+      } else {
+        future.completeExceptionally(new TableNotFoundException(tableName));
+      }
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<Boolean> isTableDisabled(TableName tableName) {
+    CompletableFuture<Boolean> future = new CompletableFuture<>();
+    AsyncMetaTableAccessor.getTableState(metaTable, tableName).whenComplete((state, error) -> {
+      if (error != null) {
+        future.completeExceptionally(error);
+        return;
+      }
+      if (state.isPresent()) {
+        future.complete(state.get().inStates(TableState.State.DISABLED));
+      } else {
+        future.completeExceptionally(new TableNotFoundException(tableName));
+      }
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<Boolean> isTableAvailable(TableName tableName) {
+    return isTableAvailable(tableName, null);
+  }
+
+  @Override
+  public CompletableFuture<Boolean> isTableAvailable(TableName tableName, byte[][] splitKeys) {
+    CompletableFuture<Boolean> future = new CompletableFuture<>();
+    isTableEnabled(tableName).whenComplete(
+      (enabled, error) -> {
+        if (error != null) {
+          future.completeExceptionally(error);
+          return;
+        }
+        if (!enabled) {
+          future.complete(false);
+        } else {
+          AsyncMetaTableAccessor.getTableRegionsAndLocations(metaTable, Optional.of(tableName))
+              .whenComplete(
+                (locations, error1) -> {
+                  if (error1 != null) {
+                    future.completeExceptionally(error1);
+                    return;
+                  }
+                  int notDeployed = 0;
+                  int regionCount = 0;
+                  for (Pair<HRegionInfo, ServerName> pair : locations) {
+                    HRegionInfo info = pair.getFirst();
+                    if (pair.getSecond() == null) {
+                      if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table " + tableName + " has not deployed region "
+                            + pair.getFirst().getEncodedName());
+                      }
+                      notDeployed++;
+                    } else if (splitKeys != null
+                        && !Bytes.equals(info.getStartKey(), HConstants.EMPTY_BYTE_ARRAY)) {
+                      for (byte[] splitKey : splitKeys) {
+                        // Just check if the splitkey is available
+                        if (Bytes.equals(info.getStartKey(), splitKey)) {
+                          regionCount++;
+                          break;
+                        }
+                      }
+                    } else {
+                      // Always empty start row should be counted
+                      regionCount++;
+                    }
+                  }
+                  if (notDeployed > 0) {
+                    if (LOG.isDebugEnabled()) {
+                      LOG.debug("Table " + tableName + " has " + notDeployed + " regions");
+                    }
+                    future.complete(false);
+                  } else if (splitKeys != null && regionCount != splitKeys.length + 1) {
+                    if (LOG.isDebugEnabled()) {
+                      LOG.debug("Table " + tableName + " expected to have "
+                          + (splitKeys.length + 1) + " regions, but only " + regionCount
+                          + " available");
+                    }
+                    future.complete(false);
+                  } else {
+                    if (LOG.isDebugEnabled()) {
+                      LOG.debug("Table " + tableName + " should be available");
+                    }
+                    future.complete(true);
+                  }
+                });
+        }
+      });
+    return future;
+  }
+
+  @Override
   public CompletableFuture<Pair<Integer, Integer>> getAlterStatus(TableName tableName) {
     return this
         .<Pair<Integer, Integer>>newMasterCaller()

http://git-wip-us.apache.org/repos/asf/hbase/blob/752b258b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableAdminApi.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableAdminApi.java
index 50cd9c6..b7430fe 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableAdminApi.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableAdminApi.java
@@ -17,6 +17,9 @@
  */
 package org.apache.hadoop.hbase.client;
 
+import static org.apache.hadoop.hbase.TableName.META_TABLE_NAME;
+import static org.apache.hadoop.hbase.client.AsyncProcess.START_LOG_ERRORS_AFTER_COUNT_KEY;
+import static org.apache.hadoop.hbase.TableName.META_TABLE_NAME;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -213,7 +216,7 @@ public class TestAsyncTableAdminApi extends TestAsyncAdminBase {
   }
 
   @Test(timeout = 300000)
-  public void testCreateTableWithRegions() throws IOException, InterruptedException {
+  public void testCreateTableWithRegions() throws Exception {
     final TableName tableName = TableName.valueOf(name.getMethodName());
 
     byte[][] splitKeys = { new byte[] { 1, 1, 1 }, new byte[] { 2, 2, 2 }, new byte[] { 3, 3, 3 },
@@ -225,6 +228,9 @@ public class TestAsyncTableAdminApi extends TestAsyncAdminBase {
     desc.addFamily(new HColumnDescriptor(HConstants.CATALOG_FAMILY));
     admin.createTable(desc, splitKeys).join();
 
+    boolean tableAvailable = admin.isTableAvailable(tableName, splitKeys).get();
+    assertTrue("Table should be created with splitKyes + 1 rows in META", tableAvailable);
+
     List<HRegionLocation> regions;
     Iterator<HRegionLocation> hris;
     HRegionInfo hri;
@@ -835,4 +841,30 @@ public class TestAsyncTableAdminApi extends TestAsyncAdminBase {
     }
   }
 
+  @Test
+  public void testIsTableEnabledAndDisabled() throws Exception {
+    final TableName table = TableName.valueOf("testIsTableEnabledAndDisabled");
+    HTableDescriptor desc = new HTableDescriptor(table);
+    desc.addFamily(new HColumnDescriptor(FAMILY));
+    admin.createTable(desc).join();
+    assertTrue(admin.isTableEnabled(table).get());
+    assertFalse(admin.isTableDisabled(table).get());
+    admin.disableTable(table).join();
+    assertFalse(admin.isTableEnabled(table).get());
+    assertTrue(admin.isTableDisabled(table).get());
+    admin.deleteTable(table).join();
+  }
+
+  @Test
+  public void testTableAvailableWithRandomSplitKeys() throws Exception {
+    TableName tableName = TableName.valueOf("testTableAvailableWithRandomSplitKeys");
+    HTableDescriptor desc = new HTableDescriptor(tableName);
+    desc.addFamily(new HColumnDescriptor("col"));
+    byte[][] splitKeys = new byte[1][];
+    splitKeys = new byte[][] { new byte[] { 1, 1, 1 }, new byte[] { 2, 2, 2 } };
+    admin.createTable(desc).join();
+    boolean tableAvailable = admin.isTableAvailable(tableName, splitKeys).get();
+    assertFalse("Table should be created with 1 row in META", tableAvailable);
+  }
+
 }