You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zg...@apache.org on 2017/04/28 01:17:12 UTC

hbase git commit: HBASE-17865: Implement async listSnapshot/deleteSnapshot methods.

Repository: hbase
Updated Branches:
  refs/heads/master b81e00f5e -> 4bc0eb31c


HBASE-17865: Implement async listSnapshot/deleteSnapshot methods.

Signed-off-by: Guanghao Zhang <zg...@apache.org>


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

Branch: refs/heads/master
Commit: 4bc0eb31c36475d51b1ab3ec6652bcdf40dc2cac
Parents: b81e00f
Author: huzheng <op...@gmail.com>
Authored: Mon Apr 24 11:51:36 2017 +0800
Committer: Guanghao Zhang <zg...@apache.org>
Committed: Fri Apr 28 09:09:13 2017 +0800

----------------------------------------------------------------------
 .../apache/hadoop/hbase/client/AsyncAdmin.java  |  78 ++++++
 .../hadoop/hbase/client/AsyncHBaseAdmin.java    | 247 ++++++++++++++++++-
 .../apache/hadoop/hbase/client/HBaseAdmin.java  |   2 +-
 .../org/apache/hadoop/hbase/HConstants.java     |   5 +
 .../hbase/client/TestAsyncSnapshotAdminApi.java | 133 ++++++++++
 5 files changed, 462 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/4bc0eb31/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 352ef1b..0e1054d 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
@@ -663,4 +663,82 @@ public interface AsyncAdmin {
    * @param tableName name of the table where the snapshot will be restored
    */
   CompletableFuture<Void> cloneSnapshot(final String snapshotName, final TableName tableName);
+
+  /**
+   * List completed snapshots.
+   * @return a list of snapshot descriptors for completed snapshots wrapped by a
+   *         {@link CompletableFuture}
+   */
+  CompletableFuture<List<SnapshotDescription>> listSnapshots();
+
+  /**
+   * List all the completed snapshots matching the given regular expression.
+   * @param regex The regular expression to match against
+   * @return - returns a List of SnapshotDescription wrapped by a {@link CompletableFuture}
+   */
+  CompletableFuture<List<SnapshotDescription>> listSnapshots(String regex);
+
+  /**
+   * List all the completed snapshots matching the given pattern.
+   * @param pattern The compiled regular expression to match against
+   * @return - returns a List of SnapshotDescription wrapped by a {@link CompletableFuture}
+   */
+  CompletableFuture<List<SnapshotDescription>> listSnapshots(Pattern pattern);
+
+  /**
+   * List all the completed snapshots matching the given table name regular expression and snapshot
+   * name regular expression.
+   * @param tableNameRegex The table name regular expression to match against
+   * @param snapshotNameRegex The snapshot name regular expression to match against
+   * @return - returns a List of completed SnapshotDescription wrapped by a
+   *         {@link CompletableFuture}
+   */
+  CompletableFuture<List<SnapshotDescription>> listTableSnapshots(String tableNameRegex,
+      String snapshotNameRegex);
+
+  /**
+   * List all the completed snapshots matching the given table name regular expression and snapshot
+   * name regular expression.
+   * @param tableNamePattern The compiled table name regular expression to match against
+   * @param snapshotNamePattern The compiled snapshot name regular expression to match against
+   * @return - returns a List of completed SnapshotDescription wrapped by a
+   *         {@link CompletableFuture}
+   */
+  CompletableFuture<List<SnapshotDescription>> listTableSnapshots(Pattern tableNamePattern,
+      Pattern snapshotNamePattern);
+
+  /**
+   * Delete an existing snapshot.
+   * @param snapshotName name of the snapshot
+   */
+  CompletableFuture<Void> deleteSnapshot(String snapshotName);
+
+  /**
+   * Delete existing snapshots whose names match the pattern passed.
+   * @param regex The regular expression to match against
+   */
+  CompletableFuture<Void> deleteSnapshots(String regex);
+
+  /**
+   * Delete existing snapshots whose names match the pattern passed.
+   * @param pattern pattern for names of the snapshot to match
+   */
+  CompletableFuture<Void> deleteSnapshots(Pattern pattern);
+
+  /**
+   * Delete all existing snapshots matching the given table name regular expression and snapshot
+   * name regular expression.
+   * @param tableNameRegex The table name regular expression to match against
+   * @param snapshotNameRegex The snapshot name regular expression to match against
+   */
+  CompletableFuture<Void> deleteTableSnapshots(String tableNameRegex, String snapshotNameRegex);
+
+  /**
+   * Delete all existing snapshots matching the given table name regular expression and snapshot
+   * name regular expression.
+   * @param tableNamePattern The compiled table name regular expression to match against
+   * @param snapshotNamePattern The compiled snapshot name regular expression to match against
+   */
+  CompletableFuture<Void> deleteTableSnapshots(Pattern tableNamePattern,
+      Pattern snapshotNamePattern);
 }

http://git-wip-us.apache.org/repos/asf/hbase/blob/4bc0eb31/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 a54cc7a..daa97bc 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
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -54,6 +55,7 @@ import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableExistsException;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.AsyncMetaTableAccessor;
+import org.apache.hadoop.hbase.TableNotDisabledException;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.UnknownRegionException;
 import org.apache.hadoop.hbase.classification.InterfaceAudience;
@@ -91,12 +93,16 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateName
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateNamespaceResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteNamespaceRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteNamespaceResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteColumnRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteColumnResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetNamespaceDescriptorRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetNamespaceDescriptorResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetProcedureResultRequest;
@@ -155,6 +161,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.Remov
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.UpdateReplicationPeerConfigRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.UpdateReplicationPeerConfigResponse;
 import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
+import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
@@ -1485,11 +1492,118 @@ public class AsyncHBaseAdmin implements AsyncAdmin {
     return restoreSnapshot(snapshotName, takeFailSafeSnapshot);
   }
 
+  private CompletableFuture<Void> restoreSnapshotWithFailSafe(String snapshotName,
+      TableName tableName, boolean takeFailSafeSnapshot) {
+    if (takeFailSafeSnapshot) {
+      CompletableFuture<Void> future = new CompletableFuture<>();
+      // Step.1 Take a snapshot of the current state
+      String failSafeSnapshotSnapshotNameFormat =
+          this.connection.getConfiguration().get(HConstants.SNAPSHOT_RESTORE_FAILSAFE_NAME,
+            HConstants.DEFAULT_SNAPSHOT_RESTORE_FAILSAFE_NAME);
+      final String failSafeSnapshotSnapshotName =
+          failSafeSnapshotSnapshotNameFormat.replace("{snapshot.name}", snapshotName)
+              .replace("{table.name}", tableName.toString().replace(TableName.NAMESPACE_DELIM, '.'))
+              .replace("{restore.timestamp}", String.valueOf(EnvironmentEdgeManager.currentTime()));
+      LOG.info("Taking restore-failsafe snapshot: " + failSafeSnapshotSnapshotName);
+      snapshot(failSafeSnapshotSnapshotName, tableName).whenComplete((ret, err) -> {
+        if (err != null) {
+          future.completeExceptionally(err);
+        } else {
+          // Step.2 Restore snapshot
+          internalRestoreSnapshot(snapshotName, tableName).whenComplete((ret2, err2) -> {
+            if (err2 != null) {
+              // Step.3.a Something went wrong during the restore and try to rollback.
+              internalRestoreSnapshot(failSafeSnapshotSnapshotName, tableName)
+                  .whenComplete((ret3, err3) -> {
+                    if (err3 != null) {
+                      future.completeExceptionally(err3);
+                    } else {
+                      String msg =
+                          "Restore snapshot=" + snapshotName + " failed. Rollback to snapshot="
+                              + failSafeSnapshotSnapshotName + " succeeded.";
+                      future.completeExceptionally(new RestoreSnapshotException(msg));
+                    }
+                  });
+            } else {
+              // Step.3.b If the restore is succeeded, delete the pre-restore snapshot.
+              LOG.info("Deleting restore-failsafe snapshot: " + failSafeSnapshotSnapshotName);
+              deleteSnapshot(failSafeSnapshotSnapshotName).whenComplete((ret3, err3) -> {
+                if (err3 != null) {
+                  LOG.error(
+                    "Unable to remove the failsafe snapshot: " + failSafeSnapshotSnapshotName,
+                    err3);
+                  future.completeExceptionally(err3);
+                } else {
+                  future.complete(ret3);
+                }
+              });
+            }
+          });
+        }
+      });
+      return future;
+    } else {
+      return internalRestoreSnapshot(snapshotName, tableName);
+    }
+  }
+
   @Override
   public CompletableFuture<Void> restoreSnapshot(String snapshotName,
       boolean takeFailSafeSnapshot) {
-    // TODO It depend on listSnapshots() method.
-    return failedFuture(new UnsupportedOperationException("restoreSnapshot do not supported yet"));
+    CompletableFuture<Void> future = new CompletableFuture<>();
+    listSnapshots(snapshotName).whenComplete((snapshotDescriptions, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      TableName tableName = null;
+      if (snapshotDescriptions != null && !snapshotDescriptions.isEmpty()) {
+        for (SnapshotDescription snap : snapshotDescriptions) {
+          if (snap.getName().equals(snapshotName)) {
+            tableName = snap.getTableName();
+            break;
+          }
+        }
+      }
+      if (tableName == null) {
+        future.completeExceptionally(new RestoreSnapshotException(
+            "Unable to find the table name for snapshot=" + snapshotName));
+        return;
+      }
+      final TableName finalTableName = tableName;
+      tableExists(finalTableName).whenComplete((exists, err2) -> {
+        if (err2 != null) {
+          future.completeExceptionally(err2);
+        } else if (!exists) {
+          // if table does not exist, then just clone snapshot into new table.
+          internalRestoreSnapshot(snapshotName, finalTableName).whenComplete((ret, err3) -> {
+            if (err3 != null) {
+              future.completeExceptionally(err3);
+            } else {
+              future.complete(ret);
+            }
+          });
+        } else {
+          isTableDisabled(finalTableName).whenComplete((disabled, err4) -> {
+            if (err4 != null) {
+              future.completeExceptionally(err4);
+            } else if (!disabled) {
+              future.completeExceptionally(new TableNotDisabledException(finalTableName));
+            } else {
+              restoreSnapshotWithFailSafe(snapshotName, finalTableName, takeFailSafeSnapshot)
+                  .whenComplete((ret, err5) -> {
+                    if (err5 != null) {
+                      future.completeExceptionally(err5);
+                    } else {
+                      future.complete(ret);
+                    }
+                  });
+            }
+          });
+        }
+      });
+    });
+    return future;
   }
 
   @Override
@@ -1531,6 +1645,135 @@ public class AsyncHBaseAdmin implements AsyncAdmin {
         .call();
   }
 
+  @Override
+  public CompletableFuture<List<SnapshotDescription>> listSnapshots() {
+    return this.<List<SnapshotDescription>> newMasterCaller()
+        .action((controller, stub) -> this
+            .<GetCompletedSnapshotsRequest, GetCompletedSnapshotsResponse, List<SnapshotDescription>> call(
+              controller, stub, GetCompletedSnapshotsRequest.newBuilder().build(),
+              (s, c, req, done) -> s.getCompletedSnapshots(c, req, done),
+              resp -> resp.getSnapshotsList().stream().map(ProtobufUtil::createSnapshotDesc)
+                  .collect(Collectors.toList())))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<List<SnapshotDescription>> listSnapshots(String regex) {
+    return listSnapshots(Pattern.compile(regex));
+  }
+
+  @Override
+  public CompletableFuture<List<SnapshotDescription>> listSnapshots(Pattern pattern) {
+    CompletableFuture<List<SnapshotDescription>> future = new CompletableFuture<>();
+    listSnapshots().whenComplete((snapshotDescList, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      if (snapshotDescList == null || snapshotDescList.isEmpty()) {
+        future.complete(Collections.emptyList());
+        return;
+      }
+      future.complete(snapshotDescList.stream()
+          .filter(snap -> pattern.matcher(snap.getName()).matches()).collect(Collectors.toList()));
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<List<SnapshotDescription>> listTableSnapshots(String tableNameRegex,
+      String snapshotNameRegex) {
+    return listTableSnapshots(Pattern.compile(tableNameRegex), Pattern.compile(snapshotNameRegex));
+  }
+
+  @Override
+  public CompletableFuture<List<SnapshotDescription>> listTableSnapshots(Pattern tableNamePattern,
+      Pattern snapshotNamePattern) {
+    CompletableFuture<List<SnapshotDescription>> future = new CompletableFuture<>();
+    listTableNames(tableNamePattern, false).whenComplete((tableNames, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      if (tableNames == null || tableNames.length <= 0) {
+        future.complete(Collections.emptyList());
+        return;
+      }
+      List<TableName> tableNameList = Arrays.asList(tableNames);
+      listSnapshots(snapshotNamePattern).whenComplete((snapshotDescList, err2) -> {
+        if (err2 != null) {
+          future.completeExceptionally(err2);
+          return;
+        }
+        if (snapshotDescList == null || snapshotDescList.isEmpty()) {
+          future.complete(Collections.emptyList());
+          return;
+        }
+        future.complete(snapshotDescList.stream()
+            .filter(snap -> (snap != null && tableNameList.contains(snap.getTableName())))
+            .collect(Collectors.toList()));
+      });
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<Void> deleteSnapshot(String snapshotName) {
+    return internalDeleteSnapshot(new SnapshotDescription(snapshotName));
+  }
+
+  @Override
+  public CompletableFuture<Void> deleteSnapshots(String regex) {
+    return deleteSnapshots(Pattern.compile(regex));
+  }
+
+  @Override
+  public CompletableFuture<Void> deleteSnapshots(Pattern snapshotNamePattern) {
+    return deleteTableSnapshots(null, snapshotNamePattern);
+  }
+
+  @Override
+  public CompletableFuture<Void> deleteTableSnapshots(String tableNameRegex,
+      String snapshotNameRegex) {
+    return deleteTableSnapshots(Pattern.compile(tableNameRegex),
+      Pattern.compile(snapshotNameRegex));
+  }
+
+  @Override
+  public CompletableFuture<Void> deleteTableSnapshots(Pattern tableNamePattern,
+      Pattern snapshotNamePattern) {
+    CompletableFuture<Void> future = new CompletableFuture<>();
+    listTableSnapshots(tableNamePattern, snapshotNamePattern)
+        .whenComplete(((snapshotDescriptions, err) -> {
+          if (err != null) {
+            future.completeExceptionally(err);
+            return;
+          }
+          if (snapshotDescriptions == null || snapshotDescriptions.isEmpty()) {
+            future.complete(null);
+            return;
+          }
+          List<CompletableFuture<Void>> deleteSnapshotFutures = new ArrayList<>();
+          snapshotDescriptions
+              .forEach(snapDesc -> deleteSnapshotFutures.add(internalDeleteSnapshot(snapDesc)));
+          CompletableFuture
+              .allOf(deleteSnapshotFutures
+                  .toArray(new CompletableFuture<?>[deleteSnapshotFutures.size()]))
+              .thenAccept(v -> future.complete(v));
+        }));
+    return future;
+  }
+
+  private CompletableFuture<Void> internalDeleteSnapshot(SnapshotDescription snapshot) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this
+            .<DeleteSnapshotRequest, DeleteSnapshotResponse, Void> call(controller, stub,
+              DeleteSnapshotRequest.newBuilder()
+                  .setSnapshot(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot)).build(),
+              (s, c, req, done) -> s.deleteSnapshot(c, req, done), resp -> null))
+        .call();
+  }
+
   private byte[][] getSplitKeys(byte[] startKey, byte[] endKey, int numRegions) {
     if (numRegions < 3) {
       throw new IllegalArgumentException("Must create at least three regions");

http://git-wip-us.apache.org/repos/asf/hbase/blob/4bc0eb31/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
----------------------------------------------------------------------
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
index 14af586..7e90ff3 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
@@ -2576,7 +2576,7 @@ public class HBaseAdmin implements Admin {
         syncWaitTimeout,
         TimeUnit.MILLISECONDS);
     } catch (IOException e) {
-      // Somthing went wrong during the restore...
+      // Something went wrong during the restore...
       // if the pre-restore snapshot is available try to rollback
       if (takeFailSafeSnapshot) {
         try {

http://git-wip-us.apache.org/repos/asf/hbase/blob/4bc0eb31/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
----------------------------------------------------------------------
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
index c0dbfe4..3c1b021 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java
@@ -1355,6 +1355,11 @@ public final class HConstants {
       "hbase.snapshot.restore.take.failsafe.snapshot";
   public static final boolean DEFAULT_SNAPSHOT_RESTORE_TAKE_FAILSAFE_SNAPSHOT = false;
 
+  public static final String SNAPSHOT_RESTORE_FAILSAFE_NAME =
+      "hbase.snapshot.restore.failsafe.name";
+  public static final String DEFAULT_SNAPSHOT_RESTORE_FAILSAFE_NAME =
+      "hbase-failsafe-{snapshot.name}-{restore.timestamp}";
+
   private HConstants() {
     // Can't be instantiated with this ctor.
   }

http://git-wip-us.apache.org/repos/asf/hbase/blob/4bc0eb31/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncSnapshotAdminApi.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncSnapshotAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncSnapshotAdminApi.java
index 0eb3881..108fc7a 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncSnapshotAdminApi.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncSnapshotAdminApi.java
@@ -26,8 +26,10 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 @Category({ MediumTests.class, ClientTests.class })
 public class TestAsyncSnapshotAdminApi extends TestAsyncAdminBase {
@@ -109,4 +111,135 @@ public class TestAsyncSnapshotAdminApi extends TestAsyncAdminBase {
       TEST_UTIL.deleteTable(tableName);
     }
   }
+
+  private void assertResult(TableName tableName, int expectedRowCount) throws IOException {
+    try (Table table = TEST_UTIL.getConnection().getTable(tableName)) {
+      Scan scan = new Scan();
+      try (ResultScanner scanner = table.getScanner(scan)) {
+        Result result;
+        int rowCount = 0;
+        while ((result = scanner.next()) != null) {
+          Assert.assertArrayEquals(result.getRow(), Bytes.toBytes(rowCount));
+          Assert.assertArrayEquals(result.getValue(Bytes.toBytes("f1"), Bytes.toBytes("cq")),
+            Bytes.toBytes(rowCount));
+          rowCount += 1;
+        }
+        Assert.assertEquals(rowCount, expectedRowCount);
+      }
+    }
+  }
+
+  @Test
+  public void testRestoreSnapshot() throws Exception {
+    String snapshotName1 = "snapshotName1";
+    String snapshotName2 = "snapshotName2";
+    TableName tableName = TableName.valueOf("testRestoreSnapshot");
+    Admin syncAdmin = TEST_UTIL.getAdmin();
+
+    try {
+      Table table = TEST_UTIL.createTable(tableName, Bytes.toBytes("f1"));
+      for (int i = 0; i < 3000; i++) {
+        table.put(new Put(Bytes.toBytes(i)).addColumn(Bytes.toBytes("f1"), Bytes.toBytes("cq"),
+          Bytes.toBytes(i)));
+      }
+      Assert.assertEquals(admin.listSnapshots().get().size(), 0);
+
+      admin.snapshot(snapshotName1, tableName).get();
+      admin.snapshot(snapshotName2, tableName).get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 2);
+
+      admin.disableTable(tableName).get();
+      admin.restoreSnapshot(snapshotName1, true).get();
+      admin.enableTable(tableName).get();
+      assertResult(tableName, 3000);
+
+      admin.disableTable(tableName).get();
+      admin.restoreSnapshot(snapshotName2, false).get();
+      admin.enableTable(tableName).get();
+      assertResult(tableName, 3000);
+    } finally {
+      syncAdmin.deleteSnapshot(snapshotName1);
+      syncAdmin.deleteSnapshot(snapshotName2);
+      TEST_UTIL.deleteTable(tableName);
+    }
+  }
+
+  @Test
+  public void testListSnapshots() throws Exception {
+    String snapshotName1 = "snapshotName1";
+    String snapshotName2 = "snapshotName2";
+    String snapshotName3 = "snapshotName3";
+    TableName tableName = TableName.valueOf("testListSnapshots");
+    Admin syncAdmin = TEST_UTIL.getAdmin();
+
+    try {
+      Table table = TEST_UTIL.createTable(tableName, Bytes.toBytes("f1"));
+      for (int i = 0; i < 3000; i++) {
+        table.put(new Put(Bytes.toBytes(i)).addColumn(Bytes.toBytes("f1"), Bytes.toBytes("cq"),
+          Bytes.toBytes(i)));
+      }
+      Assert.assertEquals(admin.listSnapshots().get().size(), 0);
+
+      admin.snapshot(snapshotName1, tableName).get();
+      admin.snapshot(snapshotName2, tableName).get();
+      admin.snapshot(snapshotName3, tableName).get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 3);
+
+      Assert.assertEquals(admin.listSnapshots("(.*)").get().size(), 3);
+      Assert.assertEquals(admin.listSnapshots("snapshotName(\\d+)").get().size(), 3);
+      Assert.assertEquals(admin.listSnapshots("snapshotName[1|3]").get().size(), 2);
+      Assert.assertEquals(admin.listSnapshots(Pattern.compile("snapshot(.*)")).get().size(), 3);
+      Assert.assertEquals(admin.listTableSnapshots("testListSnapshots", "s(.*)").get().size(), 3);
+      Assert.assertEquals(admin.listTableSnapshots("fakeTableName", "snap(.*)").get().size(), 0);
+      Assert.assertEquals(admin.listTableSnapshots("test(.*)", "snap(.*)[1|3]").get().size(), 2);
+
+    } finally {
+      syncAdmin.deleteSnapshot(snapshotName1);
+      syncAdmin.deleteSnapshot(snapshotName2);
+      syncAdmin.deleteSnapshot(snapshotName3);
+      TEST_UTIL.deleteTable(tableName);
+    }
+  }
+
+  @Test
+  public void testDeleteSnapshots() throws Exception {
+    String snapshotName1 = "snapshotName1";
+    String snapshotName2 = "snapshotName2";
+    String snapshotName3 = "snapshotName3";
+    TableName tableName = TableName.valueOf("testDeleteSnapshots");
+
+    try {
+      Table table = TEST_UTIL.createTable(tableName, Bytes.toBytes("f1"));
+      for (int i = 0; i < 3000; i++) {
+        table.put(new Put(Bytes.toBytes(i)).addColumn(Bytes.toBytes("f1"), Bytes.toBytes("cq"),
+          Bytes.toBytes(i)));
+      }
+      Assert.assertEquals(admin.listSnapshots().get().size(), 0);
+
+      admin.snapshot(snapshotName1, tableName).get();
+      admin.snapshot(snapshotName2, tableName).get();
+      admin.snapshot(snapshotName3, tableName).get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 3);
+
+      admin.deleteSnapshot(snapshotName1).get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 2);
+
+      admin.deleteSnapshots("(.*)abc").get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 2);
+
+      admin.deleteSnapshots("(.*)1").get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 2);
+
+      admin.deleteTableSnapshots("(.*)", "(.*)1").get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 2);
+
+      admin.deleteTableSnapshots("(.*)", "(.*)2").get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 1);
+
+      admin.deleteTableSnapshots("(.*)", "(.*)3").get();
+      Assert.assertEquals(admin.listSnapshots().get().size(), 0);
+    } finally {
+      TEST_UTIL.deleteTable(tableName);
+    }
+  }
 }
\ No newline at end of file