Repository: GoogleCloudPlatform/cloud-bigtable-client
Pull request #1778 (2018-04-04 05:39:03)

Implemented Admin async TruncateTable
This Pull Request is created for Truncate Async Table operations and also implemented earlier review comments.


Patch:
diff --git a/bigtable-client-core-parent/bigtable-client-core/pom.xml b/bigtable-client-core-parent/bigtable-client-core/pom.xml
index c291876a9..453a5a40b 100644
--- a/bigtable-client-core-parent/bigtable-client-core/pom.xml
+++ b/bigtable-client-core-parent/bigtable-client-core/pom.xml
@@ -107,11 +107,6 @@ limitations under the License.
             <artifactId>guava</artifactId>
             <version>${guava.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.threeten</groupId>
-            <artifactId>threetenbp</artifactId>
-            <version>${threetenbp.version}</version>
-        </dependency>
 
         <!-- Auth -->
         <dependency>
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCache.java b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCache.java
index 92db299d6..4a0e91018 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCache.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCache.java
@@ -33,7 +33,6 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import javax.annotation.concurrent.GuardedBy;
-import org.threeten.bp.Duration;
 
 /**
  * This class caches calls to {@link OAuth2Credentials#refreshAccessToken()}.  It asynchronously refreshes
@@ -182,9 +181,9 @@ public class OAuthCredentialsCache {
     return headerCache;
   }
 
-  HeaderToken getHeader(Duration timeout) {
+  HeaderToken getHeader(long timeout, TimeUnit timeUnit) {
     try {
-      return getHeaderUnsafe(timeout).getToken();
+      return getHeaderUnsafe(timeout, timeUnit).getToken();
     } catch (Exception e) {
       LOG.warn("Got an unexpected exception while trying to refresh google credentials.", e);
       Status status = Status.UNAUTHENTICATED
@@ -198,7 +197,7 @@ public class OAuthCredentialsCache {
    * Get the http credential header we need from a new oauth2 AccessToken.
    */
   @VisibleForTesting
-  HeaderCacheElement getHeaderUnsafe(Duration timeout) {
+  HeaderCacheElement getHeaderUnsafe(long timeout, TimeUnit timeUnit) {
     // Optimize for the common case: do a volatile read to peek for a Good cache value
     HeaderCacheElement headerCacheUnsync = this.headerCache;
 
@@ -212,7 +211,7 @@ public class OAuthCredentialsCache {
       case Expired:
       case Exception:
         // defer the future resolution (asyncRefresh will spin up a thread that will try to acquire the lock)
-        return syncRefresh(timeout);
+        return syncRefresh(timeout, timeUnit);
       default:
         String message = "Could not process state: " + headerCacheUnsync.getCacheState();
         LOG.warn(message);
@@ -227,9 +226,9 @@ public class OAuthCredentialsCache {
    * been refreshed.
    * This method should not be called while holding the refresh lock
    */
-  private HeaderCacheElement syncRefresh(Duration timeout) {
+  private HeaderCacheElement syncRefresh(long timeout, TimeUnit timeUnit) {
     try (Closeable ss = Tracing.getTracer().spanBuilder("CredentialsRefresh").startScopedSpan()) {
-      return asyncRefresh().get(timeout.toMillis(), TimeUnit.MILLISECONDS);
+      return asyncRefresh().get(timeout, timeUnit);
     } catch (InterruptedException e) {
       LOG.warn("Interrupted while trying to refresh google credentials.", e);
       Thread.currentThread().interrupt();
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptor.java b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptor.java
index b2b7d66b0..1af726cf5 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptor.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/main/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptor.java
@@ -33,7 +33,6 @@ import io.grpc.Status;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
-import org.threeten.bp.Duration;
 
 /**
  * Client interceptor that authenticates all calls by binding header data provided by a credential.
@@ -55,7 +54,8 @@ import org.threeten.bp.Duration;
 public class RefreshingOAuth2CredentialsInterceptor implements ClientInterceptor {
   private static final Logger LOG = new Logger(RefreshingOAuth2CredentialsInterceptor.class);
 
-  public static Duration TIMEOUT = Duration.ofSeconds(15);
+  @VisibleForTesting
+  static long TIMEOUT_MILLISECONDS = TimeUnit.SECONDS.toMillis(15);
 
   @VisibleForTesting
   static final Metadata.Key<String> AUTHORIZATION_HEADER_KEY = Metadata.Key.of(
@@ -121,13 +121,13 @@ public class RefreshingOAuth2CredentialsInterceptor implements ClientInterceptor
 
       @Override
       public void start(Listener<RespT> responseListener, Metadata headers) {
-        final Duration timeout;
+        final long timeout;
         if (callOptions.getDeadline() != null) {
-          timeout = Duration.ofMillis(callOptions.getDeadline().timeRemaining(TimeUnit.MILLISECONDS));
+          timeout = callOptions.getDeadline().timeRemaining(TimeUnit.MILLISECONDS);
         } else {
-          timeout = TIMEOUT;
+          timeout = TIMEOUT_MILLISECONDS;
         }
-        HeaderToken token = store.getHeader(timeout);
+        HeaderToken token = store.getHeader(timeout, TimeUnit.MILLISECONDS);
 
         if (!token.getStatus().isOk()) {
           unauthorized = true;
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
index 8846dbecd..f3e5dcb08 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-shaded/pom.xml
@@ -189,10 +189,6 @@ limitations under the License.
                   <pattern>org.json</pattern>
                   <shadedPattern>com.google.bigtable.repackaged.org.json</shadedPattern>
                 </relocation>
-                <relocation>
-                  <pattern>org.threeten</pattern>
-                  <shadedPattern>com.google.bigtable.repackaged.org.threeten</shadedPattern>
-                </relocation>
                 <relocation>
                   <pattern>com.lmax</pattern>
                   <shadedPattern>com.google.bigtable.repackaged.com.lmax</shadedPattern>
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAdmin.java b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAdmin.java
index 33fe3e55c..7952aa150 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAdmin.java
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAdmin.java
@@ -369,12 +369,20 @@ public class BigtableAdmin extends AbstractBigtableAdmin {
     throw new UnsupportedOperationException("modifyTableAsync");
   }
 
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.hbase.client.Admin#truncateTableAsync(org.apache.hadoop.hbase.TableName, boolean)
+   */
   @Override
-  public Future<Void> truncateTableAsync(TableName arg0, boolean arg1) throws IOException {
-    // TODO - implementable with async hbase2
-    throw new UnsupportedOperationException("truncateTableAsync");
+  public Future<Void> truncateTableAsync(TableName tableName, boolean preserveSplits) throws IOException {
+   if (!preserveSplits) {
+      LOG.info("truncate will preserveSplits. The passed in variable is ignored.");
+   }
+   DropRowRangeRequest.Builder deleteRequest = DropRowRangeRequest.newBuilder().setDeleteAllDataFromTable(true);
+   return FutureUtils.toCompletableFuture(
+        bigtableTableAdminClient
+          .dropRowRangeAsync(deleteRequest.setName(toBigtableName(tableName)).build()))
+        .thenApply(r -> null);
   }
-
   /* ******* Unsupported methods *********** */
 
   @Override
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAsyncAdmin.java b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAsyncAdmin.java
index b2186f0c3..e2cc8744b 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAsyncAdmin.java
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x/src/main/java/com/google/cloud/bigtable/hbase2_x/BigtableAsyncAdmin.java
@@ -29,9 +29,25 @@ import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import com.google.common.util.concurrent.ListenableFuture;
-import org.apache.hadoop.hbase.*;
-import org.apache.hadoop.hbase.client.*;
+import org.apache.hadoop.hbase.CacheEvictionStats;
+import org.apache.hadoop.hbase.ClusterMetrics;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.RegionMetrics;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.TableNotDisabledException;
+import org.apache.hadoop.hbase.TableNotEnabledException;
+import org.apache.hadoop.hbase.TableNotFoundException;
+import org.apache.hadoop.hbase.client.AbstractBigtableAdmin;
+import org.apache.hadoop.hbase.client.AsyncAdmin;
+import org.apache.hadoop.hbase.client.BigtableAsyncConnection;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.client.CompactType;
+import org.apache.hadoop.hbase.client.CompactionState;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.ServiceCaller;
+import org.apache.hadoop.hbase.client.SnapshotDescription;
+import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
@@ -43,6 +59,7 @@ import org.apache.hadoop.hbase.shaded.com.google.protobuf.RpcChannel;
 import com.google.bigtable.admin.v2.CreateTableRequest;
 import com.google.bigtable.admin.v2.DeleteTableRequest;
 import com.google.bigtable.admin.v2.DeleteTableRequest.Builder;
+import com.google.bigtable.admin.v2.DropRowRangeRequest;
 import com.google.bigtable.admin.v2.GetTableRequest;
 import com.google.bigtable.admin.v2.ListTablesRequest;
 import com.google.bigtable.admin.v2.Table;
@@ -52,6 +69,7 @@ import com.google.cloud.bigtable.grpc.BigtableInstanceName;
 import com.google.cloud.bigtable.grpc.BigtableTableAdminClient;
 import com.google.cloud.bigtable.hbase.adapters.admin.ColumnDescriptorAdapter;
 import com.google.cloud.bigtable.hbase2_x.adapters.admin.TableAdapter2x;
+import com.google.common.util.concurrent.ListenableFuture;
 
 import io.grpc.Status;
 
@@ -307,9 +325,18 @@ public class BigtableAsyncAdmin implements AsyncAdmin {
     throw new UnsupportedOperationException("restoreSnapshot"); // TODO
   }
 
+  /* (non-Javadoc)
+   * @see org.apache.hadoop.hbase.client.AsyncAdmin#truncateTable(org.apache.hadoop.hbase.TableName, boolean)
+   */
   @Override
-  public CompletableFuture<Void> truncateTable(TableName arg0, boolean arg1) {
-    throw new UnsupportedOperationException("truncateTable"); // TODO
+  public CompletableFuture<Void> truncateTable(TableName tableName, boolean preserveSplits) {
+  	if (!preserveSplits) {
+      LOG.info("truncate will preserveSplits. The passed in variable is ignored.");
+  	}
+  	DropRowRangeRequest.Builder deleteRequest = DropRowRangeRequest.newBuilder().setDeleteAllDataFromTable(true);
+    return FutureUtils.toCompletableFuture(
+         bigtableTableAdminClient.dropRowRangeAsync(deleteRequest.setName(bigtableInstanceName.toTableNameStr(tableName.getNameAsString())).build()))
+         .thenApply(r -> null);
   }
 
   @Override
diff --git a/bigtable-hbase-parent/bigtable-hbase-1.x-shaded/pom.xml b/bigtable-hbase-parent/bigtable-hbase-1.x-shaded/pom.xml
index 43acefd68..048208cce 100644
--- a/bigtable-hbase-parent/bigtable-hbase-1.x-shaded/pom.xml
+++ b/bigtable-hbase-parent/bigtable-hbase-1.x-shaded/pom.xml
@@ -172,10 +172,6 @@ limitations under the License.
                   <pattern>org.json</pattern>
                   <shadedPattern>com.google.bigtable.repackaged.org.json</shadedPattern>
                 </relocation>
-                <relocation>
-                  <pattern>org.threeten</pattern>
-                  <shadedPattern>com.google.bigtable.repackaged.org.threeten</shadedPattern>
-                </relocation>
                 <relocation>
                   <pattern>com.lmax</pattern>
                   <shadedPattern>com.google.bigtable.repackaged.com.lmax</shadedPattern>
diff --git a/pom.xml b/pom.xml
index e12df5bea..42b0834b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,6 @@ limitations under the License.
         <guava.version>19.0</guava.version>
         <google.auth.library.version>0.9.0</google.auth.library.version>
         <dropwizard.metrics.version>3.1.2</dropwizard.metrics.version>
-        <threetenbp.version>1.3.3</threetenbp.version>
 
 
         <beam.version>2.3.0</beam.version>


Test patch:
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCacheTest.java b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCacheTest.java
index 1dc5c3ee8..0d8dd6e01 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCacheTest.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/OAuthCredentialsCacheTest.java
@@ -34,7 +34,6 @@ import org.mockito.stubbing.Answer;
 import java.io.IOException;
 import java.util.Date;
 import java.util.concurrent.*;
-import org.threeten.bp.Duration;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.mockito.Matchers.any;
@@ -46,7 +45,7 @@ import static org.mockito.Mockito.*;
  */
 @RunWith(JUnit4.class)
 public class OAuthCredentialsCacheTest {
-  private static Duration TIMEOUT = Duration.ofSeconds(5);
+  private static long TIMEOUT_SECONDS = 5;
 
   private static ExecutorService executorService;
 
@@ -65,21 +64,10 @@ public class OAuthCredentialsCacheTest {
   @Mock
   private OAuth2Credentials mockCredentials;
 
-  @Mock
-  private Channel mockChannel;
-
-  @Mock
-  private ClientCall mockClientCall;
-
-  @Mock
-  private ClientCall.Listener mockListener;
-
   @Before
   public void setupMocks() {
     MockitoAnnotations.initMocks(this);
     setTimeInMillieconds(0L);
-    when(mockChannel.newCall(any(MethodDescriptor.class), any(CallOptions.class)))
-        .thenReturn(mockClientCall);
   }
 
   private void setTimeInMillieconds(final long timeMs) {
@@ -103,7 +91,7 @@ public class OAuthCredentialsCacheTest {
     ExecutorService mockExecutor = Mockito.mock(ExecutorService.class);
     when(mockExecutor.submit(any(Callable.class))).thenThrow(new RuntimeException(""));
     underTest = new OAuthCredentialsCache(mockExecutor, mockCredentials);
-    Assert.assertFalse(underTest.getHeader(TIMEOUT).getStatus().isOk());
+    Assert.assertFalse(underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS).getStatus().isOk());
   }
 
   @Test
@@ -138,7 +126,8 @@ public class OAuthCredentialsCacheTest {
   public void testRefreshAfterFailure() throws Exception {
     underTest = new OAuthCredentialsCache(executorService, mockCredentials);
 
-    final AccessToken accessToken = new AccessToken("hi", new Date(HeaderCacheElement.TOKEN_STALENESS_MS + 1));
+    final AccessToken accessToken =
+        new AccessToken("hi", new Date(HeaderCacheElement.TOKEN_STALENESS_MS + 1));
 
     //noinspection unchecked
     Mockito.when(mockCredentials.refreshAccessToken())
@@ -148,11 +137,13 @@ public class OAuthCredentialsCacheTest {
         .thenReturn(accessToken);
 
     // First call
-    OAuthCredentialsCache.HeaderToken token1 = underTest.getHeader(TIMEOUT);
+    OAuthCredentialsCache.HeaderToken token1 =
+        underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertFalse(token1.getStatus().isOk());
 
     // Now the second token should be available
-    OAuthCredentialsCache.HeaderToken token2 = underTest.getHeader(TIMEOUT);
+    OAuthCredentialsCache.HeaderToken token2 =
+        underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertTrue(token2.getStatus().isOk());
     Assert.assertThat(token2.getHeader(), containsString("hi"));
     // Make sure that the token was only requested twice: once for the first failure & second time for background recovery
@@ -176,7 +167,8 @@ public class OAuthCredentialsCacheTest {
         .thenReturn(goodToken);
 
     // First call - setup
-    OAuthCredentialsCache.HeaderToken firstResult = underTest.getHeader(TIMEOUT);
+    OAuthCredentialsCache.HeaderToken firstResult =
+        underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertEquals(CacheState.Good, underTest.getHeaderCache().getCacheState());
     Assert.assertThat(firstResult.getHeader(), containsString("stale"));
 
@@ -184,7 +176,7 @@ public class OAuthCredentialsCacheTest {
     setTimeInMillieconds(10);
 
     // Second call - return stale token, but schedule refresh
-    HeaderCacheElement secondResult = underTest.getHeaderUnsafe(TIMEOUT);
+    HeaderCacheElement secondResult = underTest.getHeaderUnsafe(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertEquals(CacheState.Stale, secondResult.getCacheState());
     Assert.assertThat(secondResult.getToken().getHeader(), containsString("stale"));
 
@@ -192,7 +184,8 @@ public class OAuthCredentialsCacheTest {
     setTimeInMillieconds(expires);
 
     // Third call - now returns good token
-    OAuthCredentialsCache.HeaderToken thirdResult = underTest.getHeader(TIMEOUT);
+    OAuthCredentialsCache.HeaderToken thirdResult =
+        underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertEquals(CacheState.Good, underTest.getHeaderCache().getCacheState());
     Assert.assertThat(thirdResult.getHeader(), containsString("good"));
 
@@ -224,13 +217,13 @@ public class OAuthCredentialsCacheTest {
     underTest.revokeUnauthToken(underTest.getHeaderCache().getToken());
     Assert.assertEquals(CacheState.Expired, underTest.getHeaderCache().getCacheState());
 
-    underTest.getHeader(TIMEOUT);
+    underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     Assert.assertEquals(CacheState.Good, underTest.getHeaderCache().getCacheState());
 
     underTest.revokeUnauthToken(underTest.getHeaderCache().getToken());
     // Ensure that the revoke interceptor does not run so quickly after the previous revoke.
     // The cache state should stay good.
-    Assert.assertTrue(underTest.getHeader(TIMEOUT).getStatus().isOk());
+    Assert.assertTrue(underTest.getHeader(TIMEOUT_SECONDS, TimeUnit.SECONDS).getStatus().isOk());
   }
 
   private void testHanging()
diff --git a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptorTest.java b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptorTest.java
index 3b35aff7f..bfe9de997 100644
--- a/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptorTest.java
+++ b/bigtable-client-core-parent/bigtable-client-core/src/test/java/com/google/cloud/bigtable/grpc/io/RefreshingOAuth2CredentialsInterceptorTest.java
@@ -37,10 +37,8 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.threeten.bp.Duration;
 
 
 /**
@@ -105,7 +103,7 @@ public class RefreshingOAuth2CredentialsInterceptorTest {
     public void testDefaultDeadline() throws IOException {
         initializeOk();
         sendRequest(CallOptions.DEFAULT);
-        assertEquals(RefreshingOAuth2CredentialsInterceptor.TIMEOUT.toMillis(), getDeadlineMs());
+        assertEquals(RefreshingOAuth2CredentialsInterceptor.TIMEOUT_MILLISECONDS, getDeadlineMs());
     }
 
     @Test
@@ -129,9 +127,10 @@ public class RefreshingOAuth2CredentialsInterceptorTest {
     }
 
     private long getDeadlineMs() {
-        ArgumentCaptor<Duration> timeoutCaptor = ArgumentCaptor.forClass(Duration.class);
-        verify(mockCache, times(1)).getHeader(timeoutCaptor.capture());
-        return timeoutCaptor.getValue().toMillis();
+        ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<TimeUnit> timeunitCaptor = ArgumentCaptor.forClass(TimeUnit.class);
+        verify(mockCache, times(1)).getHeader(timeoutCaptor.capture(), timeunitCaptor.capture());
+        return timeunitCaptor.getValue().toMillis(timeoutCaptor.getValue());
     }
 
     @Test
@@ -142,7 +141,7 @@ public class RefreshingOAuth2CredentialsInterceptorTest {
         Status grpcStatus = Status.UNAUTHENTICATED;
 
         // Something bad happened, and authentication could not be established
-        when(mockCache.getHeader(any(Duration.class)))
+        when(mockCache.getHeader(any(Long.class), any(TimeUnit.class)))
                 .thenReturn(new OAuthCredentialsCache.HeaderToken(grpcStatus, null));
 
         ClientCall<ReadRowsRequest, ReadRowsResponse> call = underTest.interceptCall(
@@ -219,7 +218,7 @@ public class RefreshingOAuth2CredentialsInterceptorTest {
     }
 
     private void initializeOk() {
-        when(mockCache.getHeader(any(Duration.class)))
+        when(mockCache.getHeader(any(Long.class), any(TimeUnit.class)))
                 .thenReturn(new OAuthCredentialsCache.HeaderToken(Status.OK, HEADER));
     }
 
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/IntegrationTests.java b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/IntegrationTests.java
index 8e66dabca..b1e858a42 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/IntegrationTests.java
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/IntegrationTests.java
@@ -64,7 +64,8 @@ import org.junit.runners.Suite;
     TestAsyncCreateTable.class,
     TestAsyncConnection.class,
     TestAsyncScan.class,
-    TestBasicAsyncOps.class
+    TestBasicAsyncOps.class,
+    TestAsyncTruncateTable.class
 })
 public class IntegrationTests {
 
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
index 63724734b..099424b16 100644
--- a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
@@ -38,4 +38,12 @@ public class TestTruncateTable extends AbstractTestTruncateTable {
     }
   }
 
+  @Override
+  protected void doTruncate(TableName tableName) throws Exception {
+    TableName newTestTableName = sharedTestEnv.newTestTableName();
+    sharedTestEnv.createTable(newTestTableName);
+    try(Admin admin = getConnection().getAdmin()) {
+      admin.truncateTableAsync(newTestTableName, true).get();
+    }
+  }
 }
diff --git a/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/async/TestAsyncTruncateTable.java b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/async/TestAsyncTruncateTable.java
new file mode 100644
index 000000000..69ac80c44
--- /dev/null
+++ b/bigtable-hbase-2.x-parent/bigtable-hbase-2.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/async/TestAsyncTruncateTable.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 Google LLC All Rights Reserved.
+ *
+ * Licensed 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 com.google.cloud.bigtable.hbase.async;
+
+import static com.google.cloud.bigtable.hbase.test_env.SharedTestEnvRule.COLUMN_FAMILY;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.AsyncAdmin;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import com.google.cloud.bigtable.hbase.AbstractTestTruncateTable;
+
+@RunWith(JUnit4.class)
+public class TestAsyncTruncateTable extends AbstractTestTruncateTable {
+
+  private AsyncAdmin getAsyncAdmin() throws InterruptedException, ExecutionException {
+    return AbstractAsyncTest.getAsyncConnection().getAdmin();
+  }
+	
+  private TableDescriptor createDescriptor(TableName tableName) {
+    return TableDescriptorBuilder.newBuilder(tableName)
+        .addColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(COLUMN_FAMILY).build())
+        .build();
+  }
+
+  @Override
+  protected void createTable(TableName tableName, byte[][] ranges) throws IOException {
+    try{
+	  getAsyncAdmin().createTable(createDescriptor(tableName), ranges).get();
+	} catch (InterruptedException | ExecutionException e) {
+	  e.printStackTrace();
+	}
+   }
+
+  @Override
+  protected void doTruncate(TableName tableName) throws Exception {
+    getAsyncAdmin().createTable(createDescriptor(tableName)).get();
+    getAsyncAdmin().truncateTable(tableName, true).get();
+  }
+}
diff --git a/bigtable-hbase-parent/bigtable-hbase-1.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java b/bigtable-hbase-parent/bigtable-hbase-1.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
index 63724734b..ff5e9f8c3 100644
--- a/bigtable-hbase-parent/bigtable-hbase-1.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
+++ b/bigtable-hbase-parent/bigtable-hbase-1.x-integration-tests/src/test/java/com/google/cloud/bigtable/hbase/TestTruncateTable.java
@@ -38,4 +38,11 @@ public class TestTruncateTable extends AbstractTestTruncateTable {
     }
   }
 
+  @Override
+  protected void doTruncate(TableName tableName) throws Exception {
+    try(Admin admin = getConnection().getAdmin()) {
+      admin.truncateTable(tableName,true);
+    }
+  }
+
 }
diff --git a/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestFilters.java b/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestFilters.java
index 4450a36f7..3225f401b 100644
--- a/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestFilters.java
+++ b/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestFilters.java
@@ -1965,7 +1965,7 @@ public abstract class AbstractTestFilters extends AbstractTest {
     Pair<byte[], byte[]> fuzzyData = Pair
         .newPair(
             createKey(1, 0, 0, 4),
-            createKey(0, 1, 1, 0));
+            createFuzzyMask(0, 1, 1, 0));
 
     Scan scan = new Scan().setFilter(new FuzzyRowFilter(ImmutableList.of(fuzzyData)));
 
@@ -1977,6 +1977,51 @@ public abstract class AbstractTestFilters extends AbstractTest {
     }
   }
 
+  @Test
+  public void testFuzzyWithIntKeys() throws Exception {
+    Table table = getDefaultTable();
+    List<byte[]> keys = Collections.unmodifiableList(
+        Arrays.asList(
+            createKey(5, 10, 126, 5),
+            createKey(5, 10, 127, 5),
+            createKey(5, 10, 128, 5),
+            createKey(5, 10, 129, 5),
+            createKey(5, 11, 126, 7),
+            createKey(5, 11, 127, 7),
+            createKey(5, 11, 128, 7),
+            createKey(5, 11, 129, 7)));
+
+    List<Put> puts = new ArrayList<>();
+    for(byte[] key : keys) {
+      puts.add(new Put(key).addColumn(SharedTestEnvRule.COLUMN_FAMILY,
+          Bytes.toBytes(0), Bytes.toBytes(0)));
+    }
+
+    table.put(puts);
+
+    // match keys with 5 in the first position and 126/127/128/129 in the 3rd position
+    FuzzyRowFilter filter = new FuzzyRowFilter(ImmutableList.of(
+        Pair.newPair(createKey(5, 0, 126, 0), createFuzzyMask(0, 1, 0, 1)),
+        Pair.newPair(createKey(5, 0, 127, 0), createFuzzyMask(0, 1, 0, 1)),
+        Pair.newPair(createKey(5, 0, 128, 0), createFuzzyMask(0, 1, 0, 1)),
+        Pair.newPair(createKey(5, 0, 129, 0), createFuzzyMask(0, 1, 0, 1))));
+
+    Scan scan = new Scan().setFilter(filter);
+
+    // all 8 keys should be matched
+    try (ResultScanner scanner = table.getScanner(scan)) {
+      assertMatchingRow(scanner.next(), keys.get(0));
+      assertMatchingRow(scanner.next(), keys.get(1));
+      assertMatchingRow(scanner.next(), keys.get(2));
+      assertMatchingRow(scanner.next(), keys.get(3));
+      assertMatchingRow(scanner.next(), keys.get(4));
+      assertMatchingRow(scanner.next(), keys.get(5));
+      assertMatchingRow(scanner.next(), keys.get(6));
+      assertMatchingRow(scanner.next(), keys.get(7));
+      assertNull(scanner.next());
+    }
+  }
+
   protected final void assertMatchingRow(Result result, byte[] key) {
     assertNotNull(String.format("Got null result for key %s", toFuzzyKeyString(key)), result);
     byte[] actualKey = CellUtil.cloneRow(result.rawCells()[0]);
@@ -1989,17 +2034,26 @@ public abstract class AbstractTestFilters extends AbstractTest {
   private final String toFuzzyKeyString(byte[] bytes) {
     StringBuilder sb = new StringBuilder("[");
     String prefix = "";
-    for (byte b : bytes) {
-      sb.append(prefix).append((int) b);
+    int nValues = bytes.length / 4;
+    for (int i = 0; i < nValues; i++) {
+      sb.append(prefix).append(Bytes.toInt(bytes, 4 * i));
       prefix = ", ";
     }
     return sb.append("]").toString();
   }
 
   private static byte[] createKey(int... values) {
-    byte[] bytes = new byte[values.length];
+    byte[] bytes = new byte[4 * values.length];
+    for (int i = 0; i < values.length; i++) {
+      System.arraycopy(Bytes.toBytes(values[i]), 0, bytes, 4 * i, 4);
+    }
+    return bytes;
+  }
+
+  private static byte[] createFuzzyMask(int... values) {
+    byte[] bytes = new byte[4 * values.length];
     for (int i = 0; i < values.length; i++) {
-      bytes[i] = (byte) values[i];
+      Arrays.fill(bytes, i * 4, (i + 1) * 4, (byte) (values[i] == 0 ? 0 : 1));
     }
     return bytes;
   }
@@ -2034,4 +2088,4 @@ public abstract class AbstractTestFilters extends AbstractTest {
   protected abstract void scanAddVersion(Scan scan, int version);
 
 }
-  
\ No newline at end of file
+  
diff --git a/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestTruncateTable.java b/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestTruncateTable.java
index fcbbb924a..32822a9ec 100644
--- a/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestTruncateTable.java
+++ b/bigtable-hbase-parent/bigtable-hbase-integration-tests-common/src/main/java/com/google/cloud/bigtable/hbase/AbstractTestTruncateTable.java
@@ -15,27 +15,31 @@
  */
 package com.google.cloud.bigtable.hbase;
 
+import static com.google.cloud.bigtable.hbase.test_env.SharedTestEnvRule.COLUMN_FAMILY;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.client.*;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionLocator;
+import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.IOException;
-import java.util.List;
-
-import static com.google.cloud.bigtable.hbase.test_env.SharedTestEnvRule.COLUMN_FAMILY;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 @RunWith(JUnit4.class)
 public abstract class AbstractTestTruncateTable extends AbstractTest {
 
   @Test
-  public void testTruncate() throws IOException {
+  public void testTruncate() throws Exception {
     TableName newTestTableName = sharedTestEnv.newTestTableName();
     sharedTestEnv.createTable(newTestTableName);
     try {
@@ -111,4 +115,6 @@ public abstract class AbstractTestTruncateTable extends AbstractTest {
     Assert.assertEquals(splits.length + 1, regions.size());
     AbstractTestCreateTable.assertSplitsAndRegionsMatch(splits, regions);
   }
+  
+  protected abstract void doTruncate(TableName tableName) throws Exception;
 }