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

Commit 68ad87d

Browse files
committed
tests: system tests for autogen API
1 parent 927f026 commit 68ad87d

5 files changed

Lines changed: 522 additions & 0 deletions

File tree

google/cloud/bigtable/admin_v2/overlay/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from .types import (
3333
RestoreTableOperation,
34+
WaitForConsistencyRequest,
3435
)
3536

3637
from .services.bigtable_table_admin import (
@@ -42,4 +43,5 @@
4243
"RestoreTableOperation",
4344
"BigtableTableAdminAsyncClient",
4445
"BigtableTableAdminClient",
46+
"WaitForConsistencyRequest",
4547
)

tests/system/admin_overlay/__init__.py

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from google.cloud.client import ClientWithProject
2+
3+
import pytest
4+
5+
6+
INSTANCE_PREFIX = "admin-overlay-instance"
7+
BACKUP_PREFIX = "admin-overlay-backup"
8+
ROW_PREFIX = "test-row"
9+
10+
DEFAULT_CLUSTER_LOCATIONS = [
11+
"us-east1-b"
12+
]
13+
REPLICATION_CLUSTER_LOCATIONS = [
14+
"us-east1-b",
15+
"us-west1-b"
16+
]
17+
TEST_TABLE_NAME = "system-test-table"
18+
TEST_BACKUP_TABLE_NAME = "system-test-backup-table"
19+
TEST_COLUMMN_FAMILY_NAME = "test-column"
20+
TEST_COLUMN_NAME = "value"
21+
NUM_ROWS = 500
22+
INITIAL_CELL_VALUE = "Hello"
23+
NEW_CELL_VALUE = "World"
24+
25+
26+
@pytest.fixture(scope="session")
27+
def admin_overlay_project_id():
28+
# Instantiate a meaningless client to get project name
29+
# from environment variables.
30+
client = ClientWithProject()
31+
return client.project
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
from typing import Tuple
2+
3+
from google.api_core import operation as api_core_operation
4+
5+
from google.cloud.bigtable import admin_v2
6+
from google.cloud.bigtable.data._cross_sync import CrossSync
7+
from google.cloud.bigtable.data import mutations, read_rows_query
8+
9+
import time
10+
11+
from .conftest import (
12+
INSTANCE_PREFIX,
13+
BACKUP_PREFIX,
14+
ROW_PREFIX,
15+
DEFAULT_CLUSTER_LOCATIONS,
16+
REPLICATION_CLUSTER_LOCATIONS,
17+
TEST_TABLE_NAME,
18+
TEST_BACKUP_TABLE_NAME,
19+
TEST_COLUMMN_FAMILY_NAME,
20+
TEST_COLUMN_NAME,
21+
NUM_ROWS,
22+
INITIAL_CELL_VALUE,
23+
NEW_CELL_VALUE,
24+
)
25+
26+
from datetime import datetime, timedelta
27+
28+
import pytest
29+
30+
# TODO: Finish CrossSync integration after moving to new directory
31+
32+
33+
_TABLE_ADMIN_CLIENT = admin_v2.BigtableTableAdminClient()
34+
_INSTANCE_ADMIN_CLIENT = admin_v2.BigtableInstanceAdminClient()
35+
_DATA_CLIENT = CrossSync._Sync_Impl.DataClient()
36+
37+
38+
@pytest.fixture(scope="function")
39+
def instances_to_delete():
40+
instances_to_delete = []
41+
42+
yield instances_to_delete
43+
44+
for instance in instances_to_delete:
45+
_INSTANCE_ADMIN_CLIENT.delete_instance(name=instance.name)
46+
47+
48+
@pytest.fixture(scope="function")
49+
def backups_to_delete():
50+
backups_to_delete = []
51+
52+
yield backups_to_delete
53+
54+
for backup in backups_to_delete:
55+
_TABLE_ADMIN_CLIENT.delete_backup(name=backup.name)
56+
57+
58+
def ctime() -> int:
59+
return int(time.time())
60+
61+
62+
def create_instance(
63+
project_id,
64+
instances_to_delete,
65+
storage_type=admin_v2.StorageType.HDD,
66+
cluster_locations=DEFAULT_CLUSTER_LOCATIONS,
67+
) -> Tuple[admin_v2.Instance, admin_v2.Table]:
68+
clusters = {}
69+
70+
instance_id = f"{INSTANCE_PREFIX}-{ctime()}"
71+
72+
for idx, location in enumerate(cluster_locations):
73+
clusters[location] = admin_v2.Cluster(
74+
name=_INSTANCE_ADMIN_CLIENT.cluster_path(project_id, instance_id, f"{instance_id}-{idx}"),
75+
location=_INSTANCE_ADMIN_CLIENT.common_location_path(project_id, location),
76+
default_storage_type=storage_type
77+
)
78+
79+
create_instance_request = admin_v2.CreateInstanceRequest(
80+
parent=_INSTANCE_ADMIN_CLIENT.common_project_path(project_id),
81+
instance_id=instance_id,
82+
instance=admin_v2.Instance(
83+
display_name=instance_id[:30], # truncate to 30 characters because of character limit
84+
),
85+
clusters=clusters
86+
)
87+
operation = _INSTANCE_ADMIN_CLIENT.create_instance(create_instance_request)
88+
instance = operation.result()
89+
90+
instances_to_delete.append(instance)
91+
92+
create_table_request = admin_v2.CreateTableRequest(
93+
parent=_INSTANCE_ADMIN_CLIENT.instance_path(project_id, instance_id),
94+
table_id=TEST_TABLE_NAME,
95+
table=admin_v2.Table(
96+
column_families={
97+
TEST_COLUMMN_FAMILY_NAME: admin_v2.ColumnFamily(),
98+
}
99+
)
100+
)
101+
102+
table = _TABLE_ADMIN_CLIENT.create_table(create_table_request)
103+
104+
# Populate with dummy data
105+
populate_table(instance, table, INITIAL_CELL_VALUE)
106+
107+
return instance, table
108+
109+
110+
def populate_table(instance, table, cell_value):
111+
data_client_table = _DATA_CLIENT.get_table(
112+
_TABLE_ADMIN_CLIENT.parse_instance_path(instance.name)["instance"],
113+
_TABLE_ADMIN_CLIENT.parse_table_path(table.name)["table"],
114+
)
115+
row_mutation_entries = []
116+
for i in range(0, NUM_ROWS):
117+
row_mutation_entries.append(
118+
mutations.RowMutationEntry(
119+
row_key=f"{ROW_PREFIX}-{i}",
120+
mutations=[
121+
mutations.SetCell(
122+
family=TEST_COLUMMN_FAMILY_NAME,
123+
qualifier=TEST_COLUMN_NAME,
124+
new_value=cell_value,
125+
timestamp_micros=-1
126+
)
127+
]
128+
)
129+
)
130+
131+
data_client_table.bulk_mutate_rows(row_mutation_entries)
132+
133+
134+
def create_backup(instance, table, backups_to_delete) -> admin_v2.Backup:
135+
list_clusters_response = _INSTANCE_ADMIN_CLIENT.list_clusters(parent=instance.name)
136+
cluster_name = list_clusters_response.clusters[0].name
137+
138+
backup_id = f"{BACKUP_PREFIX}-{ctime()}"
139+
140+
operation = _TABLE_ADMIN_CLIENT.create_backup(
141+
admin_v2.CreateBackupRequest(
142+
parent=cluster_name,
143+
backup_id=backup_id,
144+
backup=admin_v2.Backup(
145+
name=f"{cluster_name}/backups/{backup_id}",
146+
source_table=table.name,
147+
expire_time=datetime.now() + timedelta(hours=7)
148+
)
149+
)
150+
)
151+
152+
backup = operation.result()
153+
backups_to_delete.append(backup)
154+
return backup
155+
156+
157+
def assert_table_cell_value_equal_to(instance, table, value):
158+
data_client_table = _DATA_CLIENT.get_table(
159+
_TABLE_ADMIN_CLIENT.parse_instance_path(instance.name)["instance"],
160+
_TABLE_ADMIN_CLIENT.parse_table_path(table.name)["table"],
161+
)
162+
163+
# Read all the rows; there shouldn't be that many of them
164+
query = read_rows_query.ReadRowsQuery(limit=NUM_ROWS)
165+
for row in data_client_table.read_rows_stream(query):
166+
latest_cell = row[TEST_COLUMMN_FAMILY_NAME, TEST_COLUMN_NAME][0]
167+
assert latest_cell.value.decode("utf-8") == value
168+
169+
170+
@pytest.mark.parametrize(
171+
"second_instance_storage_type,expect_optimize_operation",
172+
[
173+
(admin_v2.StorageType.HDD, False),
174+
(admin_v2.StorageType.SSD, True),
175+
]
176+
)
177+
def test_optimize_restored_table(
178+
admin_overlay_project_id,
179+
instances_to_delete,
180+
backups_to_delete,
181+
second_instance_storage_type,
182+
expect_optimize_operation,
183+
):
184+
# Create two instances. We backup a table from the first instance to a new table in the
185+
# second instance. This is to test whether or not different scenarios trigger an
186+
# optimize_restored_table operation
187+
instance_with_backup, table_to_backup = create_instance(
188+
admin_overlay_project_id,
189+
instances_to_delete,
190+
admin_v2.StorageType.HDD
191+
)
192+
193+
instance_to_restore, _ = create_instance(
194+
admin_overlay_project_id,
195+
instances_to_delete,
196+
second_instance_storage_type,
197+
)
198+
199+
backup = create_backup(instance_with_backup, table_to_backup, backups_to_delete)
200+
201+
# Restore to other instance
202+
restore_operation = _TABLE_ADMIN_CLIENT.restore_table(
203+
admin_v2.RestoreTableRequest(
204+
parent=instance_to_restore.name,
205+
table_id=TEST_BACKUP_TABLE_NAME,
206+
backup=backup.name,
207+
)
208+
)
209+
210+
assert isinstance(restore_operation, admin_v2.RestoreTableOperation)
211+
restored_table = restore_operation.result()
212+
213+
optimize_operation = restore_operation.optimize_restored_table_operation()
214+
if expect_optimize_operation:
215+
assert isinstance(optimize_operation, api_core_operation.Operation)
216+
optimize_operation.result()
217+
else:
218+
assert optimize_operation is None
219+
220+
# Test that the new table exists
221+
assert restored_table.name == f"{instance_to_restore.name}/tables/{TEST_BACKUP_TABLE_NAME}"
222+
assert_table_cell_value_equal_to(instance_to_restore, restored_table, INITIAL_CELL_VALUE)
223+
224+
225+
def test_wait_for_consistency(admin_overlay_project_id, instances_to_delete):
226+
instance, table = create_instance(
227+
admin_overlay_project_id,
228+
instances_to_delete,
229+
cluster_locations=REPLICATION_CLUSTER_LOCATIONS,
230+
)
231+
232+
populate_table(instance, table, NEW_CELL_VALUE)
233+
wait_for_consistency_request = admin_v2.WaitForConsistencyRequest(
234+
name=table.name,
235+
)
236+
wait_for_consistency_request.standard_read_remote_writes = admin_v2.StandardReadRemoteWrites()
237+
238+
_TABLE_ADMIN_CLIENT.wait_for_consistency(wait_for_consistency_request)
239+
assert_table_cell_value_equal_to(instance, table, NEW_CELL_VALUE)

0 commit comments

Comments
 (0)