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

Commit 1a54a69

Browse files
committed
capture status for unary failed attempts
1 parent e239d56 commit 1a54a69

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

google/cloud/bigtable/data/_async/metrics_interceptor.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import time
1919
from functools import wraps
20+
from grpc import StatusCode
21+
2022
from google.cloud.bigtable.data._metrics.data_model import (
2123
OPERATION_INTERCEPTOR_METADATA_KEY,
2224
)
@@ -147,18 +149,23 @@ def on_operation_cancelled(self, op):
147149
async def intercept_unary_unary(
148150
self, operation, continuation, client_call_details, request
149151
):
150-
encountered_exc: Exception | None = None
152+
encountered_status: Exception | StatusCode | None = None
151153
metadata = None
152154
try:
153155
call = await continuation(client_call_details, request)
154156
metadata = await _get_metadata(call)
157+
if CrossSync.is_async:
158+
encountered_status = await call.code()
159+
elif isinstance(call, Exception):
160+
# sync unary calls return exception objects without raising
161+
encountered_status = call
155162
return call
156163
except Exception as rpc_error:
157164
metadata = await _get_metadata(rpc_error)
158-
encountered_exc = rpc_error
165+
encountered_status = rpc_error
159166
raise rpc_error
160167
finally:
161-
_end_attempt(operation, encountered_exc, metadata)
168+
_end_attempt(operation, encountered_status, metadata)
162169

163170
@CrossSync.convert
164171
@_with_operation_from_metadata

tests/system/data/test_metrics_async.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,45 @@ async def test_mutate_row_failure_unauthorized(
15611561
and attempt.gfe_latency_ns < operation.duration_ns
15621562
)
15631563

1564+
@CrossSync.pytest
1565+
async def test_mutate_row_failure_unauthorized_with_retries(
1566+
self, handler, authorized_view, cluster_config
1567+
):
1568+
"""
1569+
retry unauthorized request multiple times before timing out
1570+
"""
1571+
from google.cloud.bigtable.data.mutations import SetCell
1572+
1573+
row_key = b"row_key_1"
1574+
mutation = SetCell("unauthorized", b"q", b"v")
1575+
1576+
with pytest.raises(GoogleAPICallError) as e:
1577+
await authorized_view.mutate_row(row_key, [mutation], retryable_errors=[PermissionDenied], operation_timeout=30)
1578+
assert e.value.grpc_status_code.name == "DEADLINE_EXCEEDED"
1579+
# validate counts
1580+
assert len(handler.completed_operations) == 1
1581+
assert len(handler.completed_attempts) > 1
1582+
assert len(handler.cancelled_operations) == 0
1583+
# validate operation
1584+
operation = handler.completed_operations[0]
1585+
assert isinstance(operation, CompletedOperationMetric)
1586+
assert operation.final_status.name == "DEADLINE_EXCEEDED"
1587+
assert operation.op_type.value == "MutateRow"
1588+
assert operation.is_streaming is False
1589+
assert len(operation.completed_attempts) > 1
1590+
assert operation.cluster_id == next(iter(cluster_config.keys()))
1591+
assert (
1592+
operation.zone
1593+
== cluster_config[operation.cluster_id].location.split("/")[-1]
1594+
)
1595+
# validate attempts
1596+
for attempt in handler.completed_attempts:
1597+
assert attempt.end_status.name == "PERMISSION_DENIED"
1598+
assert (
1599+
attempt.gfe_latency_ns >= 0
1600+
and attempt.gfe_latency_ns < operation.duration_ns
1601+
)
1602+
15641603
@CrossSync.pytest
15651604
async def test_sample_row_keys(self, table, temp_rows, handler, cluster_config):
15661605
await table.sample_row_keys()

0 commit comments

Comments
 (0)