Skip to content

Commit 87cc707

Browse files
test(spanner): handle AlreadyExists idempotently on restore test retries (#17174)
When a restore database test times out on the client-side, Spanner continues the restore operation in the background. When the test runner retries the test, the call would fail with AlreadyExists. This change catches the AlreadyExists exception, retrieves the database metadata, and asserts that it is restoring from the correct source backup and achieves a READY or READY_OPTIMIZING state. This allows the test to execute idempotently without namespace collisions. 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) Fixes #<issue_number_goes_here> 🦕
1 parent ac594a0 commit 87cc707

1 file changed

Lines changed: 91 additions & 25 deletions

File tree

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

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
import uuid
1515

16-
from google.api_core.exceptions import DeadlineExceeded
16+
from google.api_core.exceptions import AlreadyExists, DeadlineExceeded
1717
import pytest
1818
from test_utils.retry import RetryErrors
1919

@@ -35,6 +35,49 @@ def unique_backup_id():
3535
return f"test-backup-{uuid.uuid4().hex[:10]}"
3636

3737

38+
def _verify_restored_database(
39+
spanner_client,
40+
instance_id,
41+
database_id,
42+
backup_id,
43+
kms_key_name=None,
44+
kms_key_names=None,
45+
):
46+
from google.cloud.spanner_admin_database_v1.types import spanner_database_admin
47+
import time
48+
49+
database_admin_api = spanner_client.database_admin_api
50+
db_path = database_admin_api.database_path(
51+
spanner_client.project, instance_id, database_id
52+
)
53+
db = database_admin_api.get_database(name=db_path)
54+
55+
# Verify it was restored from the correct backup
56+
restore_info = db.restore_info
57+
assert restore_info is not None
58+
assert backup_id in restore_info.backup_info.backup
59+
60+
if kms_key_name:
61+
assert kms_key_name in db.encryption_config.kms_key_name
62+
if kms_key_names:
63+
assert kms_key_names[0] in db.encryption_config.kms_key_names[0]
64+
65+
# Verify/Wait until the state is either READY or READY_OPTIMIZING
66+
for _ in range(40): # Wait up to 20 minutes
67+
if db.state in (
68+
spanner_database_admin.Database.State.READY,
69+
spanner_database_admin.Database.State.READY_OPTIMIZING,
70+
):
71+
break
72+
time.sleep(30)
73+
db = database_admin_api.get_database(name=db_path)
74+
75+
assert db.state in (
76+
spanner_database_admin.Database.State.READY,
77+
spanner_database_admin.Database.State.READY_OPTIMIZING,
78+
)
79+
80+
3881
RESTORE_DB_ID = unique_database_id()
3982
BACKUP_ID = unique_backup_id()
4083
CMEK_RESTORE_DB_ID = unique_database_id()
@@ -136,12 +179,15 @@ def test_copy_backup_with_multiple_kms_keys(
136179

137180
@pytest.mark.dependency(depends=["create_backup"])
138181
@RetryErrors(exception=(DeadlineExceeded, TimeoutError), max_tries=2)
139-
def test_restore_database(capsys, instance_id, sample_database):
140-
backup_sample.restore_database(instance_id, RESTORE_DB_ID, BACKUP_ID)
141-
out, _ = capsys.readouterr()
142-
assert (sample_database.database_id + " restored to ") in out
143-
assert (RESTORE_DB_ID + " from backup ") in out
144-
assert BACKUP_ID in out
182+
def test_restore_database(capsys, instance_id, sample_database, spanner_client):
183+
try:
184+
backup_sample.restore_database(instance_id, RESTORE_DB_ID, BACKUP_ID)
185+
out, _ = capsys.readouterr()
186+
assert (sample_database.database_id + " restored to ") in out
187+
assert (RESTORE_DB_ID + " from backup ") in out
188+
assert BACKUP_ID in out
189+
except AlreadyExists:
190+
_verify_restored_database(spanner_client, instance_id, RESTORE_DB_ID, BACKUP_ID)
145191

146192

147193
@pytest.mark.dependency(depends=["create_backup_with_encryption_key"])
@@ -151,15 +197,25 @@ def test_restore_database_with_encryption_key(
151197
instance_id,
152198
sample_database,
153199
kms_key_name,
200+
spanner_client,
154201
):
155-
backup_sample.restore_database_with_encryption_key(
156-
instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_name
157-
)
158-
out, _ = capsys.readouterr()
159-
assert (sample_database.database_id + " restored to ") in out
160-
assert (CMEK_RESTORE_DB_ID + " from backup ") in out
161-
assert CMEK_BACKUP_ID in out
162-
assert kms_key_name in out
202+
try:
203+
backup_sample.restore_database_with_encryption_key(
204+
instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_name
205+
)
206+
out, _ = capsys.readouterr()
207+
assert (sample_database.database_id + " restored to ") in out
208+
assert (CMEK_RESTORE_DB_ID + " from backup ") in out
209+
assert CMEK_BACKUP_ID in out
210+
assert kms_key_name in out
211+
except AlreadyExists:
212+
_verify_restored_database(
213+
spanner_client,
214+
instance_id,
215+
CMEK_RESTORE_DB_ID,
216+
CMEK_BACKUP_ID,
217+
kms_key_name=kms_key_name,
218+
)
163219

164220

165221
@pytest.mark.skip(reason="skipped since the KMS keys are not added on test project")
@@ -170,17 +226,27 @@ def test_restore_database_with_multiple_kms_keys(
170226
multi_region_instance_id,
171227
sample_multi_region_database,
172228
kms_key_names,
229+
spanner_client,
173230
):
174-
backup_sample.restore_database_with_multiple_kms_keys(
175-
multi_region_instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_names
176-
)
177-
out, _ = capsys.readouterr()
178-
assert (sample_multi_region_database.database_id + " restored to ") in out
179-
assert (CMEK_RESTORE_DB_ID + " from backup ") in out
180-
assert CMEK_BACKUP_ID in out
181-
assert kms_key_names[0] in out
182-
assert kms_key_names[1] in out
183-
assert kms_key_names[2] in out
231+
try:
232+
backup_sample.restore_database_with_multiple_kms_keys(
233+
multi_region_instance_id, CMEK_RESTORE_DB_ID, CMEK_BACKUP_ID, kms_key_names
234+
)
235+
out, _ = capsys.readouterr()
236+
assert (sample_multi_region_database.database_id + " restored to ") in out
237+
assert (CMEK_RESTORE_DB_ID + " from backup ") in out
238+
assert CMEK_BACKUP_ID in out
239+
assert kms_key_names[0] in out
240+
assert kms_key_names[1] in out
241+
assert kms_key_names[2] in out
242+
except AlreadyExists:
243+
_verify_restored_database(
244+
spanner_client,
245+
multi_region_instance_id,
246+
CMEK_RESTORE_DB_ID,
247+
CMEK_BACKUP_ID,
248+
kms_key_names=kms_key_names,
249+
)
184250

185251

186252
@pytest.mark.dependency(depends=["create_backup", "copy_backup"])

0 commit comments

Comments
 (0)