Skip to content

samples(bigtable): migrate all admin snippets and tests to modern V2 clients#13392

Draft
jinseopkim0 wants to merge 3 commits into
mainfrom
bigtable-v2-samples
Draft

samples(bigtable): migrate all admin snippets and tests to modern V2 clients#13392
jinseopkim0 wants to merge 3 commits into
mainfrom
bigtable-v2-samples

Conversation

@jinseopkim0
Copy link
Copy Markdown
Contributor

This PR migrates all remaining admin-related snippets and tests under java-bigtable/samples/snippets to utilize the modern BigtableTableAdminClientV2 and BigtableInstanceAdminClientV2, fully phasing out legacy V1 admin clients and .getBaseClient() references.

Migrated Samples & Tests:

  • Primary Admin Samples:
    • HelloWorld.java & HelloWorldTest.java
    • InstanceAdminExample.java & InstanceAdminExampleTest.java
    • TableAdminExample.java & TableAdminExampleTest.java
  • Secondary Admin Samples:
    • AuthorizedViewExample.java & AuthorizedViewExampleTest.java
    • SchemaBundleExample.java & SchemaBundleExampleTest.java
    • QuickstartTest.java
    • MobileTimeSeriesBaseTest.java
    • deletes/DeleteTableExample.java
    • deletes/DeleteColumnFamilyExample.java
    • deletes/DropRowRangeExample.java
    • deletes/DeletesTest.java

Key Improvements:

  1. Replaced all raw .getBaseClient() escape-hatch calls.
  2. Replaced deprecated V1 .exists(...) and exists(tableId) calls with V2 getTable / getInstance try-catch blocks.
  3. Updated nested models SubsetView and FamilySubsets to use the V2 protobuf-generated inner classes AuthorizedView.SubsetView and AuthorizedView.FamilySubsets.
  4. Correctly constructed the ProtoSchema wrapper when building and parsing schema bundles in SchemaBundleExample.java.
  5. Simplified column family presence checks by directly inspecting the keys of table.getColumnFamiliesMap(), completely eliminating legacy custom Truth correspondence helpers.

All files are formatted using spotify:fmt and compile cleanly (BUILD SUCCESS).

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +161 to +168
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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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();
        }

Comment on lines +191 to +207
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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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();
      }

Comment on lines +144 to +146
instanceAdmin.deleteCluster();
adminClient.getCluster(instanceId, CLUSTER);
adminClient.getCluster(
"projects/" + projectId + "/instances/" + instanceId + "/clusters/" + CLUSTER);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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));

Comment on lines +90 to 100
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;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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;
}
}

Comment on lines +123 to +132
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
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
    }

Comment on lines +85 to 95
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;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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;
}
}

Comment on lines +119 to +128
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
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
    }

Comment on lines 140 to 149
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant