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

Commit 820c751

Browse files
committed
feat: Add awaitOptimizeRestoredTable helper for Bigtable Admin
Adds `awaitOptimizeRestoredTable` to simplify waiting for the secondary "Optimize" operation after a table restore. This method automatically extracts the operation token from the restore metadata and resumes the optimization LRO. This addresses the Long Running Sub-operations CUJ. Tracking Bug: b/475820271
1 parent d36e897 commit 820c751

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
import java.util.Map;
8989
import java.util.concurrent.ExecutionException;
9090
import javax.annotation.Nonnull;
91+
import com.google.bigtable.admin.v2.OptimizeRestoredTableMetadata;
92+
import com.google.bigtable.admin.v2.RestoreTableMetadata;
93+
import com.google.common.base.Strings;
9194

9295
/**
9396
* Client for creating, configuring, and deleting Cloud Bigtable tables
@@ -1296,6 +1299,42 @@ public ApiFuture<RestoredTableResult> apply(com.google.bigtable.admin.v2.Table t
12961299
MoreExecutors.directExecutor());
12971300
}
12981301

1302+
/**
1303+
* Awaits the completion of the "Optimize Restored Table" operation.
1304+
*
1305+
* <p>This method blocks until the restore operation is complete, extracts the optimization token,
1306+
* and returns an ApiFuture for the optimization phase.
1307+
*
1308+
* @param restoreOp The OperationFuture returned by restoreTableAsync().
1309+
* @return An ApiFuture that tracks the optimization progress.
1310+
*/
1311+
public ApiFuture<Empty> awaitOptimizeRestoredTable(
1312+
OperationFuture<com.google.bigtable.admin.v2.Table, RestoreTableMetadata> restoreOp) {
1313+
// 1. Block and wait for the restore operation to complete
1314+
// (We accept blocking here per agreement that admin operations can be synchronous)
1315+
try {
1316+
restoreOp.get();
1317+
} catch (Exception e) {
1318+
throw new RuntimeException("Restore operation failed", e);
1319+
}
1320+
1321+
// 2. Extract the operation name from the metadata
1322+
String optimizeOpName;
1323+
try {
1324+
optimizeOpName = restoreOp.getMetadata().get().getOptimizeTableOperationName();
1325+
} catch (Exception e) {
1326+
throw new RuntimeException("Failed to extract optimization token from restore metadata", e);
1327+
}
1328+
1329+
if (Strings.isNullOrEmpty(optimizeOpName)) {
1330+
// If there is no optimization operation, return immediate success.
1331+
return ApiFutures.immediateFuture(Empty.getDefaultInstance());
1332+
}
1333+
1334+
// 3. Return the future for the optimization operation
1335+
return stub.awaitOptimizeRestoredTableCallable().resumeFutureCall(optimizeOpName);
1336+
}
1337+
12991338
/**
13001339
* Awaits a restored table is fully optimized.
13011340
*

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import static com.google.common.truth.Truth.assertThat;
1919

20+
import com.google.bigtable.admin.v2.OptimizeRestoredTableMetadata;
21+
import com.google.bigtable.admin.v2.RestoreTableMetadata;
2022
import com.google.api.core.ApiFuture;
2123
import com.google.api.core.ApiFutures;
2224
import com.google.api.gax.grpc.GrpcStatusCode;
@@ -285,6 +287,10 @@ public class BigtableTableAdminClientTests {
285287
com.google.iam.v1.TestIamPermissionsRequest, com.google.iam.v1.TestIamPermissionsResponse>
286288
mockTestIamPermissionsCallable;
287289

290+
@Mock
291+
private OperationCallable<Void, Empty, OptimizeRestoredTableMetadata>
292+
mockOptimizeRestoredTableCallable;
293+
288294
@Before
289295
public void setUp() {
290296
adminClient = BigtableTableAdminClient.create(PROJECT_ID, INSTANCE_ID, mockStub);
@@ -1682,6 +1688,61 @@ public void testWaitForConsistencyWithToken() {
16821688
assertThat(wasCalled.get()).isTrue();
16831689
}
16841690

1691+
@Test
1692+
public void testAwaitOptimizeRestoredTable() throws Exception {
1693+
// Setup
1694+
Mockito.when(mockStub.awaitOptimizeRestoredTableCallable())
1695+
.thenReturn(mockOptimizeRestoredTableCallable);
1696+
1697+
String optimizeToken = "my-optimization-token";
1698+
RestoreTableMetadata restoreMetadata =
1699+
RestoreTableMetadata.newBuilder().setOptimizeTableOperationName(optimizeToken).build();
1700+
1701+
// Mock the input OperationFuture (Restore Op)
1702+
OperationFuture<com.google.bigtable.admin.v2.Table, RestoreTableMetadata> mockRestoreOp =
1703+
Mockito.mock(OperationFuture.class);
1704+
Mockito.when(mockRestoreOp.get())
1705+
.thenReturn(
1706+
com.google.bigtable.admin.v2.Table.getDefaultInstance()); // Return proto, not wrapper
1707+
Mockito.when(mockRestoreOp.getMetadata())
1708+
.thenReturn(ApiFutures.immediateFuture(restoreMetadata));
1709+
1710+
// Mock the Stub's behavior (Optimize Op)
1711+
OperationFuture<Empty, OptimizeRestoredTableMetadata> mockOptimizeOp =
1712+
Mockito.mock(OperationFuture.class);
1713+
Mockito.when(mockOptimizeRestoredTableCallable.resumeFutureCall(optimizeToken))
1714+
.thenReturn(mockOptimizeOp);
1715+
1716+
// Execute
1717+
ApiFuture<Empty> result = adminClient.awaitOptimizeRestoredTable(mockRestoreOp);
1718+
1719+
// Verify
1720+
assertThat(result).isEqualTo(mockOptimizeOp);
1721+
Mockito.verify(mockOptimizeRestoredTableCallable).resumeFutureCall(optimizeToken);
1722+
}
1723+
1724+
@Test
1725+
public void testAwaitOptimizeRestoredTable_NoOp() throws Exception {
1726+
// Setup: Metadata with NO optimization token
1727+
RestoreTableMetadata restoreMetadata =
1728+
RestoreTableMetadata.newBuilder().setOptimizeTableOperationName("").build();
1729+
1730+
OperationFuture<com.google.bigtable.admin.v2.Table, RestoreTableMetadata> mockRestoreOp =
1731+
Mockito.mock(OperationFuture.class);
1732+
Mockito.when(mockRestoreOp.get())
1733+
.thenReturn(
1734+
com.google.bigtable.admin.v2.Table.getDefaultInstance()); // Return proto, not wrapper
1735+
Mockito.when(mockRestoreOp.getMetadata())
1736+
.thenReturn(ApiFutures.immediateFuture(restoreMetadata));
1737+
1738+
// Execute
1739+
ApiFuture<Empty> result = adminClient.awaitOptimizeRestoredTable(mockRestoreOp);
1740+
1741+
// Verify: Returns immediate success (Empty) without calling the stub
1742+
assertThat(result.get()).isEqualTo(Empty.getDefaultInstance());
1743+
Mockito.verifyNoInteractions(mockStub);
1744+
}
1745+
16851746
private <ReqT, RespT, MetaT> void mockOperationResult(
16861747
OperationCallable<ReqT, RespT, MetaT> callable,
16871748
ReqT request,
@@ -1706,3 +1767,4 @@ private String getResourceFilePath(String filePath) throws URISyntaxException {
17061767
return Paths.get(protoSchema.toURI()).toAbsolutePath().toString();
17071768
}
17081769
}
1770+

0 commit comments

Comments
 (0)