samples(bigtable): migrate all admin snippets and tests to modern V2 clients#13392
samples(bigtable): migrate all admin snippets and tests to modern V2 clients#13392jinseopkim0 wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request migrates Bigtable samples and tests to use the newer BigtableTableAdminClientV2 and BigtableInstanceAdminClientV2 clients along with direct protobuf-generated request and model classes. The review feedback identifies several important issues: potential resource leaks due to unclosed InputStream instances in SchemaBundleExample.java, an inverted assertion logic in InstanceAdminExampleTest.java that should use assertThrows to verify cluster deletion, and opportunities to optimize table existence checks across multiple files by requesting a NAME_ONLY view. Additionally, it is recommended to simplify column family checks in TableAdminExampleTest.java using containsKey.
| InputStream in = getClass().getClassLoader().getResourceAsStream(PROTO_FILE_PATH); | ||
| CreateSchemaBundleRequest request = | ||
| CreateSchemaBundleRequest.of(tableId, schemaBundleId) | ||
| .setProtoSchema(ByteString.readFrom(in)); | ||
| SchemaBundle schemaBundle = adminClient.createSchemaBundle(request); | ||
| System.out.printf("Schema bundle: %s created successfully%n", schemaBundle.getId()); | ||
| } catch (NotFoundException e) { | ||
| System.err.println( | ||
| "Failed to create a schema bundle from a non-existent table: " + e.getMessage()); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| com.google.bigtable.admin.v2.SchemaBundle schemaBundleObj = | ||
| com.google.bigtable.admin.v2.SchemaBundle.newBuilder() | ||
| .setProtoSchema( | ||
| com.google.bigtable.admin.v2.ProtoSchema.newBuilder() | ||
| .setProtoDescriptors(ByteString.readFrom(in)) | ||
| .build()) | ||
| .build(); |
There was a problem hiding this comment.
The InputStream opened from resources is never closed, which causes a resource leak. Wrap it in a try-with-resources block to ensure it is closed properly. Additionally, add a null check to handle cases where the resource is not found.
com.google.bigtable.admin.v2.SchemaBundle schemaBundleObj;
try (InputStream in = getClass().getClassLoader().getResourceAsStream(PROTO_FILE_PATH)) {
if (in == null) {
throw new java.io.FileNotFoundException("Resource not found: " + PROTO_FILE_PATH);
}
schemaBundleObj =
com.google.bigtable.admin.v2.SchemaBundle.newBuilder()
.setProtoSchema(
com.google.bigtable.admin.v2.ProtoSchema.newBuilder()
.setProtoDescriptors(ByteString.readFrom(in))
.build())
.build();
}| InputStream in = getClass().getClassLoader().getResourceAsStream(PROTO_FILE_PATH); | ||
| UpdateSchemaBundleRequest request = | ||
| UpdateSchemaBundleRequest.of(tableId, schemaBundleId) | ||
| .setProtoSchema(ByteString.readFrom(in)); | ||
| SchemaBundle schemaBundle = adminClient.updateSchemaBundle(request); | ||
| System.out.printf("Schema bundle: %s updated successfully%n", schemaBundle.getId()); | ||
| } catch (NotFoundException e) { | ||
| System.err.println("Failed to modify a non-existent schema bundle: " + e.getMessage()); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| com.google.bigtable.admin.v2.SchemaBundle schemaBundleObj = | ||
| com.google.bigtable.admin.v2.SchemaBundle.newBuilder() | ||
| .setName( | ||
| "projects/" | ||
| + projectId | ||
| + "/instances/" | ||
| + instanceId | ||
| + "/tables/" | ||
| + tableId | ||
| + "/schemaBundles/" | ||
| + schemaBundleId) | ||
| .setProtoSchema( | ||
| com.google.bigtable.admin.v2.ProtoSchema.newBuilder() | ||
| .setProtoDescriptors(ByteString.readFrom(in)) | ||
| .build()) | ||
| .build(); |
There was a problem hiding this comment.
The InputStream opened from resources is never closed, which causes a resource leak. Wrap it in a try-with-resources block to ensure it is closed properly. Additionally, add a null check to handle cases where the resource is not found.
com.google.bigtable.admin.v2.SchemaBundle schemaBundleObj;
try (InputStream in = getClass().getClassLoader().getResourceAsStream(PROTO_FILE_PATH)) {
if (in == null) {
throw new java.io.FileNotFoundException("Resource not found: " + PROTO_FILE_PATH);
}
schemaBundleObj =
com.google.bigtable.admin.v2.SchemaBundle.newBuilder()
.setName(
"projects/"
+ projectId
+ "/instances/"
+ instanceId
+ "/tables/"
+ tableId
+ "/schemaBundles/"
+ schemaBundleId)
.setProtoSchema(
com.google.bigtable.admin.v2.ProtoSchema.newBuilder()
.setProtoDescriptors(ByteString.readFrom(in))
.build())
.build();
}| instanceAdmin.deleteCluster(); | ||
| adminClient.getCluster(instanceId, CLUSTER); | ||
| adminClient.getCluster( | ||
| "projects/" + projectId + "/instances/" + instanceId + "/clusters/" + CLUSTER); |
There was a problem hiding this comment.
Calling getCluster directly after deleteCluster will throw a NotFoundException if the deletion succeeds, which will cause the test to fail. If the deletion fails, it will return the cluster and the test will pass. This inverts the test logic. Use assertThrows to verify that the cluster was successfully deleted.
instanceAdmin.deleteCluster();
org.junit.Assert.assertThrows(
NotFoundException.class,
() ->
adminClient.getCluster(
"projects/" + projectId + "/instances/" + instanceId + "/clusters/" + CLUSTER));| private boolean exists(String tableId) { | ||
| try { | ||
| adminClient.getTable( | ||
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | ||
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | ||
| .build()); | ||
| return true; | ||
| } catch (com.google.api.gax.rpc.NotFoundException e) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
When checking for table existence using getTable, retrieving the entire table schema can be inefficient and slow. You can optimize this by setting the view to NAME_ONLY on the GetTableRequest, which avoids retrieving unnecessary metadata like column families and GC rules.
| private boolean exists(String tableId) { | |
| try { | |
| adminClient.getTable( | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .build()); | |
| return true; | |
| } catch (com.google.api.gax.rpc.NotFoundException e) { | |
| return false; | |
| } | |
| } | |
| private boolean exists(String tableId) { | |
| try { | |
| adminClient.getTable( | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .setView(com.google.bigtable.admin.v2.Table.View.NAME_ONLY) | |
| .build()); | |
| return true; | |
| } catch (com.google.api.gax.rpc.NotFoundException e) { | |
| return false; | |
| } | |
| } |
| boolean exists = false; | ||
| try { | ||
| adminClient.getTable( | ||
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | ||
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | ||
| .build()); | ||
| exists = true; | ||
| } catch (NotFoundException e) { | ||
| // ignore | ||
| } |
There was a problem hiding this comment.
When checking for table existence using getTable, retrieving the entire table schema can be inefficient and slow. You can optimize this by setting the view to NAME_ONLY on the GetTableRequest, which avoids retrieving unnecessary metadata like column families and GC rules.
boolean exists = false;
try {
adminClient.getTable(
com.google.bigtable.admin.v2.GetTableRequest.newBuilder()
.setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId)
.setView(com.google.bigtable.admin.v2.Table.View.NAME_ONLY)
.build());
exists = true;
} catch (NotFoundException e) {
// ignore
}| private boolean exists(String tableId) { | ||
| try { | ||
| adminClient.getTable( | ||
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | ||
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | ||
| .build()); | ||
| return true; | ||
| } catch (com.google.api.gax.rpc.NotFoundException e) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
When checking for table existence using getTable, retrieving the entire table schema can be inefficient and slow. You can optimize this by setting the view to NAME_ONLY on the GetTableRequest, which avoids retrieving unnecessary metadata like column families and GC rules.
| private boolean exists(String tableId) { | |
| try { | |
| adminClient.getTable( | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .build()); | |
| return true; | |
| } catch (com.google.api.gax.rpc.NotFoundException e) { | |
| return false; | |
| } | |
| } | |
| private boolean exists(String tableId) { | |
| try { | |
| adminClient.getTable( | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .setView(com.google.bigtable.admin.v2.Table.View.NAME_ONLY) | |
| .build()); | |
| return true; | |
| } catch (com.google.api.gax.rpc.NotFoundException e) { | |
| return false; | |
| } | |
| } |
| boolean exists = false; | ||
| try { | ||
| adminClient.getTable( | ||
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | ||
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | ||
| .build()); | ||
| exists = true; | ||
| } catch (NotFoundException e) { | ||
| // ignore | ||
| } |
There was a problem hiding this comment.
When checking for table existence using getTable, retrieving the entire table schema can be inefficient and slow. You can optimize this by setting the view to NAME_ONLY on the GetTableRequest, which avoids retrieving unnecessary metadata like column families and GC rules.
boolean exists = false;
try {
adminClient.getTable(
com.google.bigtable.admin.v2.GetTableRequest.newBuilder()
.setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId)
.setView(com.google.bigtable.admin.v2.Table.View.NAME_ONLY)
.build());
exists = true;
} catch (NotFoundException e) {
// ignore
}| boolean found = true; | ||
| List<ColumnFamily> columnFamilies = adminClient.getTable(tableId).getColumnFamilies(); | ||
| for (ColumnFamily columnFamily : columnFamilies) { | ||
| if (columnFamily.equals("cf2")) { | ||
| com.google.bigtable.admin.v2.GetTableRequest request = | ||
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | ||
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | ||
| .build(); | ||
| for (String familyName : adminClient.getTable(request).getColumnFamiliesMap().keySet()) { | ||
| if (familyName.equals("cf2")) { | ||
| found = false; | ||
| break; | ||
| } |
There was a problem hiding this comment.
Instead of looping through the key set of column families to check if cf2 is deleted, you can use containsKey directly on the map. This is cleaner, more readable, and more efficient.
| boolean found = true; | |
| List<ColumnFamily> columnFamilies = adminClient.getTable(tableId).getColumnFamilies(); | |
| for (ColumnFamily columnFamily : columnFamilies) { | |
| if (columnFamily.equals("cf2")) { | |
| com.google.bigtable.admin.v2.GetTableRequest request = | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .build(); | |
| for (String familyName : adminClient.getTable(request).getColumnFamiliesMap().keySet()) { | |
| if (familyName.equals("cf2")) { | |
| found = false; | |
| break; | |
| } | |
| com.google.bigtable.admin.v2.GetTableRequest request = | |
| com.google.bigtable.admin.v2.GetTableRequest.newBuilder() | |
| .setName("projects/" + projectId + "/instances/" + instanceId + "/tables/" + tableId) | |
| .build(); | |
| boolean found = !adminClient.getTable(request).getColumnFamiliesMap().containsKey("cf2"); |
This PR migrates all remaining admin-related snippets and tests under
java-bigtable/samples/snippetsto utilize the modernBigtableTableAdminClientV2andBigtableInstanceAdminClientV2, fully phasing out legacy V1 admin clients and.getBaseClient()references.Migrated Samples & Tests:
HelloWorld.java&HelloWorldTest.javaInstanceAdminExample.java&InstanceAdminExampleTest.javaTableAdminExample.java&TableAdminExampleTest.javaAuthorizedViewExample.java&AuthorizedViewExampleTest.javaSchemaBundleExample.java&SchemaBundleExampleTest.javaQuickstartTest.javaMobileTimeSeriesBaseTest.javadeletes/DeleteTableExample.javadeletes/DeleteColumnFamilyExample.javadeletes/DropRowRangeExample.javadeletes/DeletesTest.javaKey Improvements:
.getBaseClient()escape-hatch calls..exists(...)andexists(tableId)calls with V2getTable/getInstancetry-catch blocks.SubsetViewandFamilySubsetsto use the V2 protobuf-generated inner classesAuthorizedView.SubsetViewandAuthorizedView.FamilySubsets.ProtoSchemawrapper when building and parsing schema bundles inSchemaBundleExample.java.table.getColumnFamiliesMap(), completely eliminating legacy custom Truth correspondence helpers.All files are formatted using
spotify:fmtand compile cleanly (BUILD SUCCESS).