diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/__init__.py b/packages/google-cloud-bigtable/samples/admin_client_async/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/__init__.py b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/instanceadmin_async.py b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/instanceadmin_async.py new file mode 100644 index 000000000000..daf96c8c5bd9 --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/instanceadmin_async.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# 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. + +"""Demonstrates how to run instance admin operations using BigtableInstanceAdminAsyncClient. + +Prerequisites: +- Create a Cloud Bigtable project. +- Set your Google Application Default Credentials. +""" + +import argparse +import asyncio + + +async def run_instance_operations(project_id, instance_id, cluster_id): + # [START bigtable_async_check_instance_exists] + from google.api_core.exceptions import NotFound + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + + try: + instance = await client.get_instance(name=instance_path) + print(f"Instance {instance_id} already exists.") + except NotFound: + print(f"Instance {instance_id} does not exist.") + # [END bigtable_async_check_instance_exists] + + # [START bigtable_async_create_prod_instance] + from google.api_core.exceptions import NotFound + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + project_path = client.common_project_path(project_id) + instance_path = client.instance_path(project_id, instance_id) + + cluster = bigtable_admin_v2.Cluster( + location=client.common_location_path(project_id, "us-central1-f"), + serve_nodes=1, + default_storage_type=bigtable_admin_v2.StorageType.SSD, + ) + instance_obj = bigtable_admin_v2.Instance( + display_name=instance_id, + labels={"prod-label": "prod-label"}, + type_=bigtable_admin_v2.Instance.Type.PRODUCTION, + ) + + try: + await client.get_instance(name=instance_path) + except NotFound: + print("\nCreating an instance") + # Create instance with given options + operation = await client.create_instance( + parent=project_path, + instance_id=instance_id, + instance=instance_obj, + clusters={cluster_id: cluster}, + ) + # Ensure the operation completes. + await operation.result(timeout=480) + print(f"\nCreated instance: {instance_id}") + # [END bigtable_async_create_prod_instance] + + # [START bigtable_async_list_instances] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + project_path = client.common_project_path(project_id) + + print("\nListing instances:") + instances_response = await client.list_instances(parent=project_path) + for instance in instances_response.instances: + print(instance.name.split("/")[-1]) + # [END bigtable_async_list_instances] + + # [START bigtable_async_get_instance] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + + inst = await client.get_instance(name=instance_path) + print(f"\nName of instance: {inst.display_name}\nLabels: {dict(inst.labels)}") + # [END bigtable_async_get_instance] + + # [START bigtable_async_get_clusters] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + + print("\nListing clusters...") + clusters_response = await client.list_clusters(parent=instance_path) + for cluster in clusters_response.clusters: + print(cluster.name.split("/")[-1]) + # [END bigtable_async_get_clusters] + + +async def delete_instance(project_id, instance_id): + # [START bigtable_async_delete_instance] + from google.api_core.exceptions import NotFound + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + + print("\nDeleting instance") + try: + await client.delete_instance(name=instance_path) + print(f"Deleted instance: {instance_id}") + except NotFound: + print(f"Instance {instance_id} does not exist.") + # [END bigtable_async_delete_instance] + + +async def add_cluster(project_id, instance_id, cluster_id): + # [START bigtable_async_create_cluster] + from google.api_core.exceptions import NotFound + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + cluster_path = client.cluster_path(project_id, instance_id, cluster_id) + + print("\nListing clusters...") + clusters_response = await client.list_clusters(parent=instance_path) + for cluster in clusters_response.clusters: + print(cluster.name.split("/")[-1]) + + new_cluster = bigtable_admin_v2.Cluster( + location=client.common_location_path(project_id, "us-central1-a"), + serve_nodes=1, + default_storage_type=bigtable_admin_v2.StorageType.SSD, + ) + try: + await client.get_cluster(name=cluster_path) + print(f"\nCluster not created, as {cluster_id} already exists.") + except NotFound: + operation = await client.create_cluster( + parent=instance_path, cluster_id=cluster_id, cluster=new_cluster + ) + # Ensure the operation completes. + await operation.result(timeout=480) + print(f"\nCluster created: {cluster_id}") + # [END bigtable_async_create_cluster] + + +async def delete_cluster(project_id, instance_id, cluster_id): + """Delete the cluster + + :type project_id: str + :param project_id: Project id of the client. + + :type instance_id: str + :param instance_id: Instance of the client. + + :type cluster_id: str + :param cluster_id: Cluster id. + """ + # [START bigtable_async_delete_cluster] + from google.api_core.exceptions import NotFound + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableInstanceAdminAsyncClient() as client: + cluster_path = client.cluster_path(project_id, instance_id, cluster_id) + + print("\nDeleting cluster") + try: + await client.delete_cluster(name=cluster_path) + print(f"Cluster deleted: {cluster_id}") + except NotFound: + print(f"\nCluster {cluster_id} does not exist.") + # [END bigtable_async_delete_cluster] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "command", + help="run, del-instance, add-cluster or del-cluster.", + ) + parser.add_argument("project_id", help="Your Cloud Platform project ID.") + parser.add_argument("instance_id", help="ID of the Cloud Bigtable instance.") + parser.add_argument("cluster_id", help="ID of the Cloud Bigtable cluster.") + + args = parser.parse_args() + + if args.command.lower() == "run": + asyncio.run( + run_instance_operations(args.project_id, args.instance_id, args.cluster_id) + ) + elif args.command.lower() == "del-instance": + asyncio.run(delete_instance(args.project_id, args.instance_id)) + elif args.command.lower() == "add-cluster": + asyncio.run(add_cluster(args.project_id, args.instance_id, args.cluster_id)) + elif args.command.lower() == "del-cluster": + asyncio.run(delete_cluster(args.project_id, args.instance_id, args.cluster_id)) diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/requirements.txt b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/requirements.txt new file mode 100644 index 000000000000..b1ff3f53c77e --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/requirements.txt @@ -0,0 +1,2 @@ +google-cloud-bigtable>=2.35.0 +backoff==2.2.1 diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/test_instanceadmin_async.py b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/test_instanceadmin_async.py new file mode 100644 index 000000000000..3ec71138d30d --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/instanceadmin/test_instanceadmin_async.py @@ -0,0 +1,56 @@ +# Copyright 2026 Google LLC +# 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. + +import os +import uuid + +import backoff +import pytest +from google.api_core import exceptions + +from .instanceadmin_async import ( + add_cluster, + delete_cluster, + delete_instance, + run_instance_operations, +) + +PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] +INSTANCE_ID = f"instance-admin-async-{str(uuid.uuid4())[:8]}" +CLUSTER_ID = f"cluster-admin-async-{str(uuid.uuid4())[:8]}" +NEW_CLUSTER_ID = f"cluster-add-async-{str(uuid.uuid4())[:8]}" + + +@pytest.mark.asyncio +async def test_instance_operations(capsys): + try: + await run_instance_operations(PROJECT, INSTANCE_ID, CLUSTER_ID) + out, _ = capsys.readouterr() + assert f"Created instance: {INSTANCE_ID}" in out + assert "Listing instances:" in out + assert "Listing clusters..." in out + + backoff_503 = backoff.on_exception(backoff.expo, exceptions.ServiceUnavailable) + await backoff_503(add_cluster)(PROJECT, INSTANCE_ID, NEW_CLUSTER_ID) + out, _ = capsys.readouterr() + assert f"Cluster created: {NEW_CLUSTER_ID}" in out + + await delete_cluster(PROJECT, INSTANCE_ID, NEW_CLUSTER_ID) + out, _ = capsys.readouterr() + assert f"Cluster deleted: {NEW_CLUSTER_ID}" in out + + await delete_instance(PROJECT, INSTANCE_ID) + out, _ = capsys.readouterr() + assert f"Deleted instance: {INSTANCE_ID}" in out + finally: + await delete_instance(PROJECT, INSTANCE_ID) diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/__init__.py b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/requirements.txt b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/requirements.txt new file mode 100644 index 000000000000..74e55ca10648 --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/requirements.txt @@ -0,0 +1 @@ +google-cloud-bigtable>=2.35.0 diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async.py b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async.py new file mode 100644 index 000000000000..632c88f82b54 --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# 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. + +"""Demonstrates how to connect to Cloud Bigtable and run table admin operations using BigtableTableAdminAsyncClient. + +Prerequisites: +- Create a Cloud Bigtable instance. +- Set your Google Application Default Credentials. +""" + +import argparse +import asyncio + +from ...utils import create_table_cm + + +async def run_table_operations(project_id, instance_id, table_id): + with create_table_cm(project_id, instance_id, table_id, verbose=False): + # [START bigtable_async_list_tables] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + + print("Listing tables in current project...") + tables = [ + tbl async for tbl in await client.list_tables(parent=instance_path) + ] + if tables: + for tbl in tables: + print(tbl.name.split("/")[-1]) + else: + print("No table exists in current project...") + # [END bigtable_async_list_tables] + + # [START bigtable_async_create_family_gc_max_age] + from google.protobuf import duration_pb2 + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Creating column family cf1 with MaxAge GC Rule...") + # Create a column family with GC policy : maximum age + # where age = current time minus cell timestamp + + # Define the GC rule to retain data with max age of 5 days + max_age_rule = bigtable_admin_v2.GcRule( + max_age=duration_pb2.Duration(seconds=5 * 86400) + ) + cf1 = bigtable_admin_v2.ColumnFamily(gc_rule=max_age_rule) + mod1 = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf1", create=cf1 + ) + await client.modify_column_families(name=table_path, modifications=[mod1]) + print("Created column family cf1 with MaxAge GC Rule.") + # [END bigtable_async_create_family_gc_max_age] + + # [START bigtable_async_create_family_gc_max_versions] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Creating column family cf2 with max versions GC rule...") + # Create a column family with GC policy : most recent N versions + # where 1 = most recent version + + # Define the GC policy to retain only the most recent 2 versions + max_versions_rule = bigtable_admin_v2.GcRule(max_num_versions=2) + cf2 = bigtable_admin_v2.ColumnFamily(gc_rule=max_versions_rule) + mod2 = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf2", create=cf2 + ) + await client.modify_column_families(name=table_path, modifications=[mod2]) + print("Created column family cf2 with Max Versions GC Rule.") + # [END bigtable_async_create_family_gc_max_versions] + + # [START bigtable_async_create_family_gc_union] + from google.protobuf import duration_pb2 + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Creating column family cf3 with union GC rule...") + # Create a column family with GC policy to drop data that matches + # at least one condition. + # Define a GC rule to drop cells older than 5 days or not the + # most recent version + union_rule = bigtable_admin_v2.GcRule( + union=bigtable_admin_v2.GcRule.Union( + rules=[ + bigtable_admin_v2.GcRule( + max_age=duration_pb2.Duration(seconds=5 * 86400) + ), + bigtable_admin_v2.GcRule(max_num_versions=2), + ] + ) + ) + cf3 = bigtable_admin_v2.ColumnFamily(gc_rule=union_rule) + mod3 = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf3", create=cf3 + ) + await client.modify_column_families(name=table_path, modifications=[mod3]) + print("Created column family cf3 with Union GC rule") + # [END bigtable_async_create_family_gc_union] + + # [START bigtable_async_create_family_gc_intersection] + from google.protobuf import duration_pb2 + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Creating column family cf4 with Intersection GC rule...") + # Create a column family with GC policy to drop data that matches + # all conditions + # GC rule: Drop cells older than 5 days AND older than the most + # recent 2 versions + intersection_rule = bigtable_admin_v2.GcRule( + intersection=bigtable_admin_v2.GcRule.Intersection( + rules=[ + bigtable_admin_v2.GcRule( + max_age=duration_pb2.Duration(seconds=5 * 86400) + ), + bigtable_admin_v2.GcRule(max_num_versions=2), + ] + ) + ) + cf4 = bigtable_admin_v2.ColumnFamily(gc_rule=intersection_rule) + mod4 = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf4", create=cf4 + ) + await client.modify_column_families(name=table_path, modifications=[mod4]) + print("Created column family cf4 with Intersection GC rule.") + # [END bigtable_async_create_family_gc_intersection] + + # [START bigtable_async_create_family_gc_nested] + from google.protobuf import duration_pb2 + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Creating column family cf5 with a Nested GC rule...") + # Create a column family with nested GC policies. + # Create a nested GC rule: + # Drop cells that are either older than the 10 recent versions + # OR + # Drop cells that are older than a month AND older than the + # 2 recent versions + rule1 = bigtable_admin_v2.GcRule(max_num_versions=10) + rule2 = bigtable_admin_v2.GcRule( + intersection=bigtable_admin_v2.GcRule.Intersection( + rules=[ + bigtable_admin_v2.GcRule( + max_age=duration_pb2.Duration(seconds=30 * 86400) + ), + bigtable_admin_v2.GcRule(max_num_versions=2), + ] + ) + ) + nested_rule = bigtable_admin_v2.GcRule( + union=bigtable_admin_v2.GcRule.Union(rules=[rule1, rule2]) + ) + cf5 = bigtable_admin_v2.ColumnFamily(gc_rule=nested_rule) + mod5 = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf5", create=cf5 + ) + await client.modify_column_families(name=table_path, modifications=[mod5]) + print("Created column family cf5 with a Nested GC rule.") + # [END bigtable_async_create_family_gc_nested] + + # [START bigtable_async_list_column_families] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Printing Column Family and GC Rule for all column families...") + table_obj = await client.get_table(name=table_path) + for column_family_name, cf_obj in sorted(table_obj.column_families.items()): + print("Column Family:", column_family_name) + print("GC Rule:") + print(cf_obj.gc_rule) + # Sample output: + # Column Family: cf4 + # GC Rule: + # intersection { + # rules { + # max_age { + # seconds: 432000 + # } + # } + # rules { + # max_num_versions: 2 + # } + # } + # [END bigtable_async_list_column_families] + + # [START bigtable_async_update_gc_rule] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Updating column family cf1 GC rule...") + # Update the column family cf1 to update the GC rule + updated_cf1 = bigtable_admin_v2.ColumnFamily( + gc_rule=bigtable_admin_v2.GcRule(max_num_versions=1) + ) + mod_update = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf1", update=updated_cf1 + ) + await client.modify_column_families( + name=table_path, modifications=[mod_update] + ) + print("Updated column family cf1 GC rule\n") + # [END bigtable_async_update_gc_rule] + + # [START bigtable_async_delete_family] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + print("Delete a column family cf2...") + # Delete a column family + mod_delete = bigtable_admin_v2.ModifyColumnFamiliesRequest.Modification( + id="cf2", drop=True + ) + await client.modify_column_families( + name=table_path, modifications=[mod_delete] + ) + print("Column family cf2 deleted successfully.") + # [END bigtable_async_delete_family] + + print( + 'execute command "python tableadmin_async.py delete [project_id] \ + [instance_id] --table [tableName]" to delete the table.' + ) + + +async def delete_table(project_id, instance_id, table_id): + """Delete bigtable. + + :type project_id: str + :param project_id: Project id of the client. + + :type instance_id: str + :param instance_id: Instance of the client. + + :type table_id: str + :param table_id: Table id to create table. + """ + # [START bigtable_async_delete_table] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + # Delete the entire table + print("Deleting {} table.".format(table_id)) + await client.delete_table(name=table_path) + print("Deleted {} table.".format(table_id)) + # [END bigtable_async_delete_table] + + +async def wait_for_consistency(project_id, instance_id, table_id): + """Block until the table's recent writes have been replicated. + + :type project_id: str + :param project_id: Project id of the client. + + :type instance_id: str + :param instance_id: Instance of the client. + + :type table_id: str + :param table_id: Table id to wait on. + """ + # [START bigtable_async_wait_for_consistency] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + table_path = client.table_path(project_id, instance_id, table_id) + + # Blocks until every mutation made to the table before this call has + # been replicated to all of the instance's clusters. + print("Waiting for {} to be consistent...".format(table_id)) + await client.wait_for_consistency(name=table_path) + print("Table {} is consistent.".format(table_id)) + # [END bigtable_async_wait_for_consistency] + + +async def create_backup(project_id, instance_id, cluster_id, table_id, backup_id): + """Create a backup of a table. + + :type project_id: str + :param project_id: Project id of the client. + + :type instance_id: str + :param instance_id: Instance of the client. + + :type cluster_id: str + :param cluster_id: Cluster that will store the backup. + + :type table_id: str + :param table_id: Table to back up. + + :type backup_id: str + :param backup_id: Id for the new backup. + """ + # [START bigtable_async_create_backup] + import datetime + + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + cluster_path = client.cluster_path(project_id, instance_id, cluster_id) + table_path = client.table_path(project_id, instance_id, table_id) + + # Backups must expire between 6 hours and 90 days after they are created + expire_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( + hours=8 + ) + print("Creating backup {} of table {}...".format(backup_id, table_id)) + operation = await client.create_backup( + parent=cluster_path, + backup_id=backup_id, + backup=bigtable_admin_v2.Backup( + source_table=table_path, expire_time=expire_time + ), + ) + backup = await operation.result() + print("Created backup {}.".format(backup.name.split("/")[-1])) + # [END bigtable_async_create_backup] + + +async def restore_table( + project_id, instance_id, cluster_id, backup_id, restored_table_id +): + """Restore a backup into a new table. + + :type project_id: str + :param project_id: Project id of the client. + + :type instance_id: str + :param instance_id: Instance of the client. + + :type cluster_id: str + :param cluster_id: Cluster that stores the backup. + + :type backup_id: str + :param backup_id: Backup to restore from. + + :type restored_table_id: str + :param restored_table_id: Id for the new table. + """ + # [START bigtable_async_restore_table] + from google.cloud import bigtable_admin_v2 + + async with bigtable_admin_v2.BigtableTableAdminAsyncClient() as client: + instance_path = client.instance_path(project_id, instance_id) + backup_path = client.backup_path(project_id, instance_id, cluster_id, backup_id) + + print( + "Restoring backup {} into table {}...".format(backup_id, restored_table_id) + ) + operation = await client.restore_table( + request={ + "parent": instance_path, + "table_id": restored_table_id, + "backup": backup_path, + } + ) + table = await operation.result() + print("Restored table {}.".format(table.name.split("/")[-1])) + + # Restoring can kick off a second operation that optimizes the new + # table in the background. The table is usable before it finishes, so + # waiting for it is optional. + optimize_operation = await operation.optimize_restored_table_operation() + if optimize_operation is not None: + print("Waiting for table {} to be optimized...".format(restored_table_id)) + await optimize_operation.result() + print("Table {} is optimized.".format(restored_table_id)) + # [END bigtable_async_restore_table] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument("command", help="run, delete, wait, backup or restore.") + parser.add_argument( + "--table", help="Cloud Bigtable Table name.", default="Hello-Bigtable" + ) + parser.add_argument( + "--cluster", help="Cluster that stores the backup (backup/restore)." + ) + parser.add_argument( + "--backup", help="Backup id (backup/restore).", default="Hello-Bigtable-backup" + ) + parser.add_argument("project_id", help="Your Cloud Platform project ID.") + parser.add_argument( + "instance_id", help="ID of the Cloud Bigtable instance to connect to." + ) + + args = parser.parse_args() + + if args.command.lower() == "run": + asyncio.run(run_table_operations(args.project_id, args.instance_id, args.table)) + elif args.command.lower() == "delete": + asyncio.run(delete_table(args.project_id, args.instance_id, args.table)) + elif args.command.lower() == "wait": + asyncio.run(wait_for_consistency(args.project_id, args.instance_id, args.table)) + elif args.command.lower() == "backup": + asyncio.run( + create_backup( + args.project_id, args.instance_id, args.cluster, args.table, args.backup + ) + ) + elif args.command.lower() == "restore": + asyncio.run( + restore_table( + args.project_id, args.instance_id, args.cluster, args.backup, args.table + ) + ) + else: + print( + "Command should be one of run, delete, wait, backup or restore.\n" + "Use argument -h, --help to show help and exit." + ) diff --git a/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async_test.py b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async_test.py new file mode 100644 index 000000000000..7bf5ea1dfa1d --- /dev/null +++ b/packages/google-cloud-bigtable/samples/admin_client_async/tableadmin/tableadmin_async_test.py @@ -0,0 +1,113 @@ +# Copyright 2026 Google LLC +# 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. + +import os +import uuid + +import pytest +from google.api_core import exceptions + +from google.cloud import bigtable_admin_v2 + +from ...utils import create_table_cm +from .tableadmin_async import ( + create_backup, + delete_table, + restore_table, + run_table_operations, + wait_for_consistency, +) + +PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] +BIGTABLE_INSTANCE = os.environ["BIGTABLE_INSTANCE"] +TABLE_ID = f"table-admin-async-{str(uuid.uuid4())[:16]}" + + +@pytest.mark.asyncio +async def test_run_table_operations(capsys): + await run_table_operations(PROJECT, BIGTABLE_INSTANCE, TABLE_ID) + out, _ = capsys.readouterr() + assert "Listing tables in current project..." in out + assert "Created column family cf1 with MaxAge GC Rule." in out + assert "Created column family cf2 with Max Versions GC Rule." in out + assert "Created column family cf3 with Union GC rule" in out + assert "Created column family cf4 with Intersection GC rule." in out + assert "Created column family cf5 with a Nested GC rule." in out + assert "Printing Column Family and GC Rule for all column families..." in out + assert "Updated column family cf1 GC rule" in out + assert "Column family cf2 deleted successfully." in out + + +@pytest.mark.asyncio +async def test_delete_table(capsys): + table_id = f"table-admin-async-to-delete-{str(uuid.uuid4())[:16]}" + with create_table_cm(PROJECT, BIGTABLE_INSTANCE, table_id, verbose=False): + await delete_table(PROJECT, BIGTABLE_INSTANCE, table_id) + out, _ = capsys.readouterr() + assert f"Deleting {table_id} table." in out + assert f"Deleted {table_id} table." in out + + +@pytest.mark.asyncio +async def test_wait_for_consistency(capsys): + table_id = f"table-admin-async-consistency-{str(uuid.uuid4())[:16]}" + with create_table_cm(PROJECT, BIGTABLE_INSTANCE, table_id, verbose=False): + await wait_for_consistency(PROJECT, BIGTABLE_INSTANCE, table_id) + out, _ = capsys.readouterr() + assert f"Waiting for {table_id} to be consistent..." in out + assert f"Table {table_id} is consistent." in out + + +@pytest.mark.asyncio +async def test_backup_and_restore(capsys): + table_id = f"table-admin-async-backup-{str(uuid.uuid4())[:16]}" + backup_id = f"backup-async-{str(uuid.uuid4())[:16]}" + restored_table_id = f"table-admin-async-restored-{str(uuid.uuid4())[:16]}" + + instance_client = bigtable_admin_v2.BigtableInstanceAdminClient() + instance_path = instance_client.instance_path(PROJECT, BIGTABLE_INSTANCE) + cluster_id = ( + instance_client.list_clusters(parent=instance_path) + .clusters[0] + .name.split("/")[-1] + ) + + table_client = bigtable_admin_v2.BigtableTableAdminClient() + backup_path = table_client.backup_path( + PROJECT, BIGTABLE_INSTANCE, cluster_id, backup_id + ) + restored_table_path = table_client.table_path( + PROJECT, BIGTABLE_INSTANCE, restored_table_id + ) + try: + with create_table_cm(PROJECT, BIGTABLE_INSTANCE, table_id, verbose=False): + await create_backup( + PROJECT, BIGTABLE_INSTANCE, cluster_id, table_id, backup_id + ) + await restore_table( + PROJECT, BIGTABLE_INSTANCE, cluster_id, backup_id, restored_table_id + ) + assert table_client.get_table(name=restored_table_path) is not None + finally: + for name, delete in ( + (restored_table_path, table_client.delete_table), + (backup_path, table_client.delete_backup), + ): + try: + delete(name=name) + except exceptions.NotFound: + pass + + out, _ = capsys.readouterr() + assert f"Created backup {backup_id}." in out + assert f"Restored table {restored_table_id}." in out