Skip to content

Commit 02a48d7

Browse files
committed
docs: samples and tests for auto-generated createDatabase and createInstance APIs.
1 parent ec87c08 commit 02a48d7

File tree

6 files changed

+903
-0
lines changed

6 files changed

+903
-0
lines changed

samples/generated/conftest.py

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Copyright 2024 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
""" Shared pytest fixtures."""
15+
16+
import time
17+
import uuid
18+
19+
from google.api_core import exceptions
20+
21+
from google.cloud import spanner_admin_database_v1
22+
from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect
23+
from google.cloud.spanner_v1 import backup
24+
from google.cloud.spanner_v1 import client
25+
from google.cloud.spanner_v1 import database
26+
from google.cloud.spanner_v1 import instance
27+
import pytest
28+
from test_utils import retry
29+
30+
INSTANCE_CREATION_TIMEOUT = 560 # seconds
31+
32+
OPERATION_TIMEOUT_SECONDS = 120 # seconds
33+
34+
retry_429 = retry.RetryErrors(exceptions.ResourceExhausted, delay=15)
35+
36+
37+
@pytest.fixture(scope="module")
38+
def sample_name():
39+
"""Sample testcase modules must define this fixture.
40+
41+
The name is used to label the instance created by the sample, to
42+
aid in debugging leaked instances.
43+
"""
44+
raise NotImplementedError("Define 'sample_name' fixture in sample test driver")
45+
46+
47+
@pytest.fixture(scope="module")
48+
def database_dialect():
49+
"""Database dialect to be used for this sample.
50+
51+
The dialect is used to initialize the dialect for the database.
52+
It can either be GoogleStandardSql or PostgreSql.
53+
"""
54+
# By default, we consider GOOGLE_STANDARD_SQL dialect. Other specific tests
55+
# can override this if required.
56+
return DatabaseDialect.GOOGLE_STANDARD_SQL
57+
58+
59+
@pytest.fixture(scope="session")
60+
def spanner_client():
61+
"""Shared client used across all samples in a session."""
62+
return client.Client()
63+
64+
65+
def scrub_instance_ignore_not_found(to_scrub):
66+
"""Helper for func:`cleanup_old_instances`"""
67+
try:
68+
for backup_pb in to_scrub.list_backups():
69+
backup.Backup.from_pb(backup_pb, to_scrub).delete()
70+
71+
retry_429(to_scrub.delete)()
72+
except exceptions.NotFound:
73+
pass
74+
75+
76+
@pytest.fixture(scope="session")
77+
def cleanup_old_instances(spanner_client):
78+
"""Delete instances, created by samples, that are older than an hour."""
79+
cutoff = int(time.time()) - 1 * 60 * 60
80+
instance_filter = "labels.cloud_spanner_samples:true"
81+
82+
for instance_pb in spanner_client.list_instances(filter_=instance_filter):
83+
inst = instance.Instance.from_pb(instance_pb, spanner_client)
84+
85+
if "created" in inst.labels:
86+
create_time = int(inst.labels["created"])
87+
88+
if create_time <= cutoff:
89+
scrub_instance_ignore_not_found(inst)
90+
91+
92+
@pytest.fixture(scope="module")
93+
def instance_id():
94+
"""Unique id for the instance used in samples."""
95+
return f"test-instance-{uuid.uuid4().hex[:10]}"
96+
97+
98+
@pytest.fixture(scope="module")
99+
def multi_region_instance_id():
100+
"""Unique id for the multi-region instance used in samples."""
101+
return f"multi-instance-{uuid.uuid4().hex[:10]}"
102+
103+
104+
@pytest.fixture(scope="module")
105+
def instance_config(spanner_client):
106+
return "{}/instanceConfigs/{}".format(
107+
spanner_client.project_name, "regional-us-central1"
108+
)
109+
110+
111+
@pytest.fixture(scope="module")
112+
def multi_region_instance_config(spanner_client):
113+
return "{}/instanceConfigs/{}".format(spanner_client.project_name, "nam3")
114+
115+
116+
@pytest.fixture(scope="module")
117+
def sample_instance(
118+
spanner_client,
119+
cleanup_old_instances,
120+
instance_id,
121+
instance_config,
122+
sample_name,
123+
):
124+
sample_instance = spanner_client.instance(
125+
instance_id,
126+
instance_config,
127+
labels={
128+
"cloud_spanner_samples": "true",
129+
"sample_name": sample_name,
130+
"created": str(int(time.time())),
131+
},
132+
)
133+
op = retry_429(sample_instance.create)()
134+
op.result(INSTANCE_CREATION_TIMEOUT) # block until completion
135+
136+
# Eventual consistency check
137+
retry_found = retry.RetryResult(bool)
138+
retry_found(sample_instance.exists)()
139+
140+
yield sample_instance
141+
142+
for database_pb in sample_instance.list_databases():
143+
database.Database.from_pb(database_pb, sample_instance).drop()
144+
145+
for backup_pb in sample_instance.list_backups():
146+
backup.Backup.from_pb(backup_pb, sample_instance).delete()
147+
148+
sample_instance.delete()
149+
150+
151+
@pytest.fixture(scope="module")
152+
def multi_region_instance(
153+
spanner_client,
154+
cleanup_old_instances,
155+
multi_region_instance_id,
156+
multi_region_instance_config,
157+
sample_name,
158+
):
159+
multi_region_instance = spanner_client.instance(
160+
multi_region_instance_id,
161+
multi_region_instance_config,
162+
labels={
163+
"cloud_spanner_samples": "true",
164+
"sample_name": sample_name,
165+
"created": str(int(time.time())),
166+
},
167+
)
168+
op = retry_429(multi_region_instance.create)()
169+
op.result(INSTANCE_CREATION_TIMEOUT) # block until completion
170+
171+
# Eventual consistency check
172+
retry_found = retry.RetryResult(bool)
173+
retry_found(multi_region_instance.exists)()
174+
175+
yield multi_region_instance
176+
177+
for database_pb in multi_region_instance.list_databases():
178+
database.Database.from_pb(database_pb, multi_region_instance).drop()
179+
180+
for backup_pb in multi_region_instance.list_backups():
181+
backup.Backup.from_pb(backup_pb, multi_region_instance).delete()
182+
183+
multi_region_instance.delete()
184+
185+
186+
@pytest.fixture(scope="module")
187+
def database_id():
188+
"""Id for the database used in samples.
189+
190+
Sample testcase modules can override as needed.
191+
"""
192+
return "my-database-id"
193+
194+
195+
@pytest.fixture(scope="module")
196+
def bit_reverse_sequence_database_id():
197+
"""Id for the database used in bit reverse sequence samples.
198+
199+
Sample testcase modules can override as needed.
200+
"""
201+
return "sequence-database-id"
202+
203+
204+
@pytest.fixture(scope="module")
205+
def database_ddl():
206+
"""Sequence of DDL statements used to set up the database.
207+
208+
Sample testcase modules can override as needed.
209+
"""
210+
return []
211+
212+
213+
@pytest.fixture(scope="module")
214+
def sample_database(
215+
spanner_client, sample_instance, database_id, database_ddl, database_dialect
216+
):
217+
if database_dialect == DatabaseDialect.POSTGRESQL:
218+
sample_database = sample_instance.database(
219+
database_id,
220+
database_dialect=DatabaseDialect.POSTGRESQL,
221+
)
222+
223+
if not sample_database.exists():
224+
operation = sample_database.create()
225+
operation.result(OPERATION_TIMEOUT_SECONDS)
226+
227+
request = spanner_admin_database_v1.UpdateDatabaseDdlRequest(
228+
database=sample_database.name,
229+
statements=database_ddl,
230+
)
231+
232+
operation = spanner_client.database_admin_api.update_database_ddl(request)
233+
operation.result(OPERATION_TIMEOUT_SECONDS)
234+
235+
yield sample_database
236+
237+
sample_database.drop()
238+
return
239+
240+
sample_database = sample_instance.database(
241+
database_id,
242+
ddl_statements=database_ddl,
243+
)
244+
245+
if not sample_database.exists():
246+
operation = sample_database.create()
247+
operation.result(OPERATION_TIMEOUT_SECONDS)
248+
249+
yield sample_database
250+
251+
sample_database.drop()
252+
253+
254+
@pytest.fixture(scope="module")
255+
def bit_reverse_sequence_database(
256+
spanner_client, sample_instance, bit_reverse_sequence_database_id, database_dialect
257+
):
258+
if database_dialect == DatabaseDialect.POSTGRESQL:
259+
bit_reverse_sequence_database = sample_instance.database(
260+
bit_reverse_sequence_database_id,
261+
database_dialect=DatabaseDialect.POSTGRESQL,
262+
)
263+
264+
if not bit_reverse_sequence_database.exists():
265+
operation = bit_reverse_sequence_database.create()
266+
operation.result(OPERATION_TIMEOUT_SECONDS)
267+
268+
yield bit_reverse_sequence_database
269+
270+
bit_reverse_sequence_database.drop()
271+
return
272+
273+
bit_reverse_sequence_database = sample_instance.database(
274+
bit_reverse_sequence_database_id
275+
)
276+
277+
if not bit_reverse_sequence_database.exists():
278+
operation = bit_reverse_sequence_database.create()
279+
operation.result(OPERATION_TIMEOUT_SECONDS)
280+
281+
yield bit_reverse_sequence_database
282+
283+
bit_reverse_sequence_database.drop()
284+
285+
286+
@pytest.fixture(scope="module")
287+
def kms_key_name(spanner_client):
288+
return "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format(
289+
spanner_client.project,
290+
"us-central1",
291+
"spanner-test-keyring",
292+
"spanner-test-cmek",
293+
)

0 commit comments

Comments
 (0)