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

Commit 810441c

Browse files
authored
chore: add Rapid x region sys test (#1765)
feat: Add system test for cross-region buckets - Adds a new system test, test_basic_wrd_x_region, to verify functionality with cross-region GCS buckets. - Also updates the Cloud Build configuration to pass the necessary _CROSS_REGION_BUCKET environment variable to the test environment This PR is a cherry-picked from #1760 because that PR is blocked by b/489420625
1 parent 4d98e32 commit 810441c

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

cloudbuild/zb-system-tests-cloudbuild.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ steps:
6868
# Execute the script on the VM via SSH.
6969
# Capture the exit code to ensure cleanup happens before the build fails.
7070
set +e
71-
gcloud compute ssh ${_VM_NAME} --zone=${_ZONE} --internal-ip --ssh-key-file=/workspace/.ssh/google_compute_engine --command="ulimit -n {_ULIMIT}; COMMIT_SHA=${COMMIT_SHA} _ZONAL_BUCKET=${_ZONAL_BUCKET} _PR_NUMBER=${_PR_NUMBER} bash run_zonal_tests.sh"
71+
gcloud compute ssh ${_VM_NAME} --zone=${_ZONE} --internal-ip --ssh-key-file=/workspace/.ssh/google_compute_engine --command="ulimit -n {_ULIMIT}; COMMIT_SHA=${COMMIT_SHA} _ZONAL_BUCKET=${_ZONAL_BUCKET} CROSS_REGION_BUCKET=${_CROSS_REGION_BUCKET} _PR_NUMBER=${_PR_NUMBER} bash run_zonal_tests.sh"
7272
EXIT_CODE=$?
7373
set -e
7474

google/cloud/storage/asyncio/async_multi_range_downloader.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ def __init__(
228228
self._read_id_to_download_ranges_id = {}
229229
self._download_ranges_id_to_pending_read_ids = {}
230230
self.persisted_size: Optional[int] = None # updated after opening the stream
231+
self._open_retries: int = 0
232+
231233

232234
async def __aenter__(self):
233235
"""Opens the underlying bidi-gRPC connection to read from the object."""
@@ -257,13 +259,18 @@ async def open(
257259
raise ValueError("Underlying bidi-gRPC stream is already open")
258260

259261
if retry_policy is None:
262+
def on_error_wrapper(exc):
263+
self._open_retries += 1
264+
self._on_open_error(exc)
265+
260266
retry_policy = AsyncRetry(
261-
predicate=_is_read_retryable, on_error=self._on_open_error
267+
predicate=_is_read_retryable, on_error=on_error_wrapper
262268
)
263269
else:
264270
original_on_error = retry_policy._on_error
265271

266272
def combined_on_error(exc):
273+
self._open_retries += 1
267274
self._on_open_error(exc)
268275
if original_on_error:
269276
original_on_error(exc)

tests/system/test_zonal.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# TODO: replace this with a fixture once zonal bucket creation / deletion
3232
# is supported in grpc client or json client client.
3333
_ZONAL_BUCKET = os.getenv("ZONAL_BUCKET")
34+
_CROSS_REGION_BUCKET = os.getenv("CROSS_REGION_BUCKET")
3435
_BYTES_TO_UPLOAD = b"dummy_bytes_to_write_read_and_delete_appendable_object"
3536

3637

@@ -82,6 +83,51 @@ def _get_equal_dist(a: int, b: int) -> tuple[int, int]:
8283
return a + step, a + 2 * step
8384

8485

86+
@pytest.mark.parametrize(
87+
"object_size",
88+
[
89+
256, # less than _chunk size
90+
10 * 1024 * 1024, # less than _MAX_BUFFER_SIZE_BYTES
91+
20 * 1024 * 1024, # greater than _MAX_BUFFER_SIZE
92+
],
93+
)
94+
def test_basic_wrd_x_region(
95+
storage_client,
96+
blobs_to_delete,
97+
object_size,
98+
event_loop,
99+
grpc_client,
100+
):
101+
object_name = f"test_basic_wrd-{str(uuid.uuid4())}"
102+
103+
async def _run():
104+
object_data = os.urandom(object_size)
105+
object_checksum = google_crc32c.value(object_data)
106+
107+
writer = AsyncAppendableObjectWriter(grpc_client, _CROSS_REGION_BUCKET, object_name)
108+
await writer.open()
109+
await writer.append(object_data)
110+
object_metadata = await writer.close(finalize_on_close=True)
111+
assert object_metadata.size == object_size
112+
assert int(object_metadata.checksums.crc32c) == object_checksum
113+
114+
buffer = BytesIO()
115+
mrd = AsyncMultiRangeDownloader(grpc_client, _CROSS_REGION_BUCKET, object_name)
116+
async with mrd:
117+
assert mrd._open_retries == 1
118+
# (0, 0) means read the whole object
119+
await mrd.download_ranges([(0, 0, buffer)])
120+
assert mrd.persisted_size == object_size
121+
122+
assert buffer.getvalue() == object_data
123+
124+
# Clean up; use json client (i.e. `storage_client` fixture) to delete.
125+
blobs_to_delete.append(storage_client.bucket(_CROSS_REGION_BUCKET).blob(object_name))
126+
del writer
127+
gc.collect()
128+
129+
event_loop.run_until_complete(_run())
130+
85131
@pytest.mark.parametrize(
86132
"object_size",
87133
[

0 commit comments

Comments
 (0)