Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 85736e5

Browse files
committed
feat: Add methods to wait for consistency with a token
This change adds support for checking consistency using a provided token, enabling distributed workflows. It also adds convenience methods to BigtableTableAdminClient for generating and waiting for a token automatically. This addresses the Consistency Tokens CUJ. Tracking Bug: b/475820272
1 parent 4a99a8c commit 85736e5

6 files changed

Lines changed: 150 additions & 3 deletions

File tree

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClient.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,49 @@ public ApiFuture<Void> awaitReplicationAsync(final String tableId) {
14601460
return stub.awaitReplicationCallable().futureCall(tableName);
14611461
}
14621462

1463+
/**
1464+
* Generates a consistency token and polls it until replication is consistent.
1465+
* Blocks until completion.
1466+
*
1467+
* @param tableId The table to check.
1468+
*/
1469+
public void waitForConsistency(String tableId) {
1470+
awaitReplication(tableId);
1471+
}
1472+
1473+
/**
1474+
* Asynchronously generates a token and polls until consistent.
1475+
*
1476+
* @param tableId The table to check.
1477+
*/
1478+
public ApiFuture<Void> waitForConsistencyAsync(String tableId) {
1479+
return awaitReplicationAsync(tableId);
1480+
}
1481+
1482+
/**
1483+
* Polls an existing consistency token until replication is consistent.
1484+
* Useful for checking consistency of a token generated in a separate process.
1485+
* Blocks until completion.
1486+
*
1487+
* @param tableId The table to check.
1488+
* @param consistencyToken The token to poll.
1489+
*/
1490+
public void waitForConsistency(String tableId, String consistencyToken) {
1491+
ApiExceptions.callAndTranslateApiException(
1492+
waitForConsistencyAsync(tableId, consistencyToken));
1493+
}
1494+
1495+
/**
1496+
* Asynchronously polls an existing consistency token.
1497+
*
1498+
* @param tableId The table to check.
1499+
* @param consistencyToken The token to poll.
1500+
*/
1501+
public ApiFuture<Void> waitForConsistencyAsync(String tableId, String consistencyToken) {
1502+
return stub.awaitConsistencyCallable().futureCall(
1503+
ConsistencyRequest.forReplication(tableId, consistencyToken));
1504+
}
1505+
14631506
/**
14641507
* Creates a new authorized view with the specified configuration.
14651508
*

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/ConsistencyRequest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.bigtable.admin.v2.TableName;
2525
import com.google.cloud.bigtable.data.v2.internal.TableAdminRequestContext;
2626
import javax.annotation.Nonnull;
27+
import javax.annotation.Nullable;
2728

2829
@AutoValue
2930
public abstract class ConsistencyRequest {
@@ -33,14 +34,33 @@ public abstract class ConsistencyRequest {
3334
@Nonnull
3435
protected abstract CheckConsistencyRequest.ModeCase getMode();
3536

37+
/**
38+
* Internal accessor for the consistency token.
39+
* Must be public to be accessible from the stub package.
40+
*/
41+
@InternalApi
42+
@Nullable
43+
public abstract String getConsistencyToken();
44+
3645
public static ConsistencyRequest forReplication(String tableId) {
3746
return new AutoValue_ConsistencyRequest(
38-
tableId, CheckConsistencyRequest.ModeCase.STANDARD_READ_REMOTE_WRITES);
47+
tableId, CheckConsistencyRequest.ModeCase.STANDARD_READ_REMOTE_WRITES, null);
48+
}
49+
50+
/**
51+
* Creates a request to check consistency using an existing token.
52+
*
53+
* @param tableId The table ID.
54+
* @param consistencyToken The token to check.
55+
*/
56+
public static ConsistencyRequest forReplication(String tableId, String consistencyToken) {
57+
return new AutoValue_ConsistencyRequest(
58+
tableId, CheckConsistencyRequest.ModeCase.STANDARD_READ_REMOTE_WRITES, consistencyToken);
3959
}
4060

4161
public static ConsistencyRequest forDataBoost(String tableId) {
4262
return new AutoValue_ConsistencyRequest(
43-
tableId, CheckConsistencyRequest.ModeCase.DATA_BOOST_READ_LOCAL_WRITES);
63+
tableId, CheckConsistencyRequest.ModeCase.DATA_BOOST_READ_LOCAL_WRITES, null);
4464
}
4565

4666
@InternalApi
@@ -68,4 +88,4 @@ public GenerateConsistencyTokenRequest toGenerateTokenProto(
6888

6989
return builder.setName(tableName.toString()).build();
7090
}
71-
}
91+
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallable.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ static AwaitConsistencyCallable create(
9393
@Override
9494
public ApiFuture<Void> futureCall(
9595
final ConsistencyRequest consistencyRequest, final ApiCallContext apiCallContext) {
96+
97+
// If the token is already provided, skip generation and poll directly.
98+
if (consistencyRequest.getConsistencyToken() != null) {
99+
CheckConsistencyRequest request =
100+
consistencyRequest.toCheckConsistencyProto(
101+
requestContext, consistencyRequest.getConsistencyToken());
102+
return pollToken(request, apiCallContext);
103+
}
104+
96105
ApiFuture<GenerateConsistencyTokenResponse> tokenFuture =
97106
generateToken(consistencyRequest.toGenerateTokenProto(requestContext), apiCallContext);
98107

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,31 @@ public void testTestIamPermissions() {
16571657
assertThat(actualResult).containsExactly("bigtable.backups.get");
16581658
}
16591659

1660+
@Test
1661+
public void testWaitForConsistencyWithToken() {
1662+
// Setup
1663+
Mockito.when(mockStub.awaitConsistencyCallable()).thenReturn(mockAwaitConsistencyCallable);
1664+
1665+
String token = "my-token";
1666+
ConsistencyRequest expectedRequest = ConsistencyRequest.forReplication(TABLE_ID, token);
1667+
1668+
final AtomicBoolean wasCalled = new AtomicBoolean(false);
1669+
1670+
Mockito.when(mockAwaitConsistencyCallable.futureCall(expectedRequest))
1671+
.thenAnswer(
1672+
(Answer<ApiFuture<Void>>)
1673+
invocationOnMock -> {
1674+
wasCalled.set(true);
1675+
return ApiFutures.immediateFuture(null);
1676+
});
1677+
1678+
// Execute
1679+
adminClient.waitForConsistency(TABLE_ID, token);
1680+
1681+
// Verify
1682+
assertThat(wasCalled.get()).isTrue();
1683+
}
1684+
16601685
private <ReqT, RespT, MetaT> void mockOperationResult(
16611686
OperationCallable<ReqT, RespT, MetaT> callable,
16621687
ReqT request,

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/models/ConsistencyRequestTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,22 @@ public void testToGenerateTokenProto() {
7979
assertThat(generateRequest.getName())
8080
.isEqualTo(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID));
8181
}
82+
83+
@Test
84+
public void testToCheckConsistencyProtoWithToken() {
85+
ConsistencyRequest consistencyRequest =
86+
ConsistencyRequest.forReplication(TABLE_ID, CONSISTENCY_TOKEN);
87+
88+
TableAdminRequestContext requestContext =
89+
TableAdminRequestContext.create(PROJECT_ID, INSTANCE_ID);
90+
91+
CheckConsistencyRequest checkConsistencyRequest =
92+
consistencyRequest.toCheckConsistencyProto(requestContext, CONSISTENCY_TOKEN);
93+
94+
assertThat(checkConsistencyRequest.getName())
95+
.isEqualTo(NameUtil.formatTableName(PROJECT_ID, INSTANCE_ID, TABLE_ID));
96+
assertThat(checkConsistencyRequest.getConsistencyToken()).isEqualTo(CONSISTENCY_TOKEN);
97+
assertThat(checkConsistencyRequest.getModeCase())
98+
.isEqualTo(CheckConsistencyRequest.ModeCase.STANDARD_READ_REMOTE_WRITES);
99+
}
82100
}

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallableTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.junit.Test;
4343
import org.junit.runner.RunWith;
4444
import org.junit.runners.JUnit4;
45+
import org.mockito.ArgumentMatchers;
4546
import org.mockito.Mock;
4647
import org.mockito.Mockito;
4748
import org.mockito.junit.MockitoJUnit;
@@ -325,4 +326,35 @@ public void testAwaitReplicationCallablePolling() throws Exception {
325326

326327
consistentFuture.get(1, TimeUnit.SECONDS);
327328
}
329+
330+
@Test
331+
public void testWithProvidedToken() throws Exception {
332+
// 1. Setup: Request with a pre-existing token
333+
String existingToken = "existing-token";
334+
ConsistencyRequest consistencyRequest =
335+
ConsistencyRequest.forReplication(TABLE_ID, existingToken);
336+
337+
// 2. Setup: Mock the check operation to succeed immediately
338+
CheckConsistencyRequest expectedCheckRequest =
339+
CheckConsistencyRequest.newBuilder()
340+
.setName(TABLE_NAME.toString())
341+
.setConsistencyToken(existingToken)
342+
.setStandardReadRemoteWrites(StandardReadRemoteWrites.newBuilder().build())
343+
.build();
344+
CheckConsistencyResponse expectedResponse =
345+
CheckConsistencyResponse.newBuilder().setConsistent(true).build();
346+
347+
Mockito.when(mockCheckConsistencyCallable.futureCall(expectedCheckRequest, CALL_CONTEXT))
348+
.thenReturn(ApiFutures.immediateFuture(expectedResponse));
349+
350+
// 3. Execute
351+
ApiFuture<Void> future = awaitConsistencyCallable.futureCall(consistencyRequest, CALL_CONTEXT);
352+
future.get(1, TimeUnit.SECONDS);
353+
354+
// 4. Verify: Generate was NEVER called, Check WAS called
355+
Mockito.verify(mockGenerateConsistencyTokenCallable, Mockito.never())
356+
.futureCall(ArgumentMatchers.any(GenerateConsistencyTokenRequest.class), ArgumentMatchers.any(ApiCallContext.class));
357+
Mockito.verify(mockCheckConsistencyCallable, Mockito.times(1))
358+
.futureCall(expectedCheckRequest, CALL_CONTEXT);
359+
}
328360
}

0 commit comments

Comments
 (0)