Skip to content

Commit 2670eab

Browse files
test(spanner): make samples instance and backup cleanup robust against FailedPrecondition and quota exhaustion (#17141)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-cloud-python/issues) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) ### Root Cause During Kokoro continuous test runs for Cloud Spanner samples, tests restore databases from backups. The restored databases hold active backend references to the backups (`backup.referencing_databases`) while optimizing in the background. During module teardown, `conftest.py` calls `database.drop()` and immediately iterates to `backup.delete()`. Because `database.drop()` is asynchronous on the Spanner backend, `backup.delete()` frequently fails with a `FailedPrecondition` error (backup in use). This unhandled exception crashes the fixture teardown generator, skipping `instance.delete()` and leaking instances and backups. As these leaked resources accumulate across frequent Kokoro runs in the shared test project, subsequent runs fail due to `ResourceExhausted` (quota exceeded) errors. ### Solution - Created a robust retry helper (`retry_cleanup`) that retries on both `ResourceExhausted` and `FailedPrecondition`. - Isolated individual database drops and backup deletions within `try...except exceptions.GoogleAPICallError` blocks so that an error on a single resource never aborts the scrubbing generator. - Unified this robust scrubbing logic across `cleanup_old_instances`, `sample_instance`, and `multi_region_instance` teardown fixtures.
1 parent f93911c commit 2670eab

1 file changed

Lines changed: 18 additions & 17 deletions

File tree

  • packages/google-cloud-spanner/samples/samples

packages/google-cloud-spanner/samples/samples/conftest.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
OPERATION_TIMEOUT_SECONDS = 120 # seconds
3030

3131
retry_429 = retry.RetryErrors(exceptions.ResourceExhausted, delay=15)
32+
retry_cleanup = retry.RetryErrors(
33+
(exceptions.ResourceExhausted, exceptions.FailedPrecondition), max_tries=6, delay=15
34+
)
3235

3336

3437
@pytest.fixture(scope="module")
@@ -60,12 +63,22 @@ def spanner_client():
6063

6164

6265
def scrub_instance_ignore_not_found(to_scrub):
63-
"""Helper for func:`cleanup_old_instances`"""
66+
"""Robustly delete an instance, its databases, and backups."""
6467
try:
68+
for database_pb in to_scrub.list_databases():
69+
try:
70+
database.Database.from_pb(database_pb, to_scrub).drop()
71+
except exceptions.GoogleAPICallError:
72+
pass
73+
6574
for backup_pb in to_scrub.list_backups():
66-
backup.Backup.from_pb(backup_pb, to_scrub).delete()
75+
try:
76+
b = backup.Backup.from_pb(backup_pb, to_scrub)
77+
retry_cleanup(b.delete)()
78+
except exceptions.GoogleAPICallError:
79+
pass
6780

68-
retry_429(to_scrub.delete)()
81+
retry_cleanup(to_scrub.delete)()
6982
except exceptions.NotFound:
7083
pass
7184

@@ -154,13 +167,7 @@ def sample_instance(
154167

155168
yield sample_instance
156169

157-
for database_pb in sample_instance.list_databases():
158-
database.Database.from_pb(database_pb, sample_instance).drop()
159-
160-
for backup_pb in sample_instance.list_backups():
161-
backup.Backup.from_pb(backup_pb, sample_instance).delete()
162-
163-
sample_instance.delete()
170+
scrub_instance_ignore_not_found(sample_instance)
164171

165172

166173
@pytest.fixture(scope="module")
@@ -189,13 +196,7 @@ def multi_region_instance(
189196

190197
yield multi_region_instance
191198

192-
for database_pb in multi_region_instance.list_databases():
193-
database.Database.from_pb(database_pb, multi_region_instance).drop()
194-
195-
for backup_pb in multi_region_instance.list_backups():
196-
backup.Backup.from_pb(backup_pb, multi_region_instance).delete()
197-
198-
multi_region_instance.delete()
199+
scrub_instance_ignore_not_found(multi_region_instance)
199200

200201

201202
@pytest.fixture(scope="module")

0 commit comments

Comments
 (0)