Skip to content

Commit 6f9cf7a

Browse files
authored
feat: add force_direct_connectivity behavior (#770)
* feat: add force_direct_connectivity behavior * chore: add tests, readme and fix formatting * style: fix formatting with black and isort
1 parent c4b75eb commit 6f9cf7a

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ curl -H "x-retry-test-id: 1d05c20627844214a9ff7cbcf696317d" "http://localhost:91
276276
| stall-for-Ts-after-YK | [HTTP] Testbench will stall for T second after reading YKiB of downloaded/uploaded data, e.g. stall-for-10s-after-12K stalls after reading/writing 12KiB of data <br> [GRPC] Not supported
277277
| redirect-send-token-T | [HTTP] Unsupported [GRPC] Testbench will fail the RPC with `ABORTED` and include appropriate redirection error details.
278278
| redirect-send-handle-and-token-T | [HTTP] Unsupported [GRPC] Testbench will fail the RPC with `ABORTED` and include appropriate redirection error details.
279+
| return-X-if-dp-enforced | [HTTP] Unsupported [GRPC] Testbench will fail with the equivalent gRPC error to the HTTP code provided for X, but only if DirectPath is enforced.
279280

280281
## Developing for the testbench
281282

testbench/common.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
r"redirect-send-handle-and-token-([a-z\-]+)$"
5454
)
5555
retry_expect_redirection_token = re.compile(r"redirect-expect-token-([a-z\-]+)$")
56+
retry_return_error_if_dp_enforced = re.compile(r"return-([0-9]+)-if-dp-enforced$")
5657

5758
# A retry instruction to return a specific set of unreachable buckets.
5859
# For example, if the instruction is:
@@ -784,6 +785,23 @@ def grpc_handle_retry_test_instruction(database, request, context, method):
784785
):
785786
return __get_default_response_fn
786787
next_instruction = database.peek_next_instruction(test_id, method)
788+
enforced_match = retry_return_error_if_dp_enforced.match(next_instruction)
789+
if enforced_match:
790+
database.dequeue_next_instruction(test_id, method)
791+
params = get_context_request_params(context)
792+
if params and "force_direct_connectivity=ENFORCED" in params:
793+
rest_code = enforced_match.group(1)
794+
grpc_code = _grpc_forced_failure_from_http_instruction(rest_code)
795+
msg = {
796+
"error": {
797+
"message": "DirectPath is enforced but failed to establish connection: {}".format(
798+
grpc_code
799+
)
800+
}
801+
}
802+
testbench.error.inject_error(context, rest_code, grpc_code, msg=msg)
803+
return __get_default_response_fn
804+
787805
error_code_matches = testbench.common.retry_return_error_code.match(
788806
next_instruction
789807
)

testbench/database.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ def __validate_injected_failure_description(self, failure):
662662
testbench.common.retry_return_handle_and_redirection_token,
663663
testbench.common.retry_expect_redirection_token,
664664
testbench.common.retry_return_unreachable_buckets,
665+
testbench.common.retry_return_error_if_dp_enforced,
665666
]:
666667
if expr.match(failure) is not None:
667668
return

tests/test_testbench_retry.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,56 @@ def test_grpc_bidiread_open_redirect(self):
14421442
# Verify the early break occurred during first message only.
14431443
self.assertEqual(len(responses), 0)
14441444

1445+
def test_grpc_retry_return_error_if_dp_enforced(self):
1446+
# Setup a retry test with 'return-503-if-dp-enforced'
1447+
response = self.rest_client.post(
1448+
"/retry_test",
1449+
data=json.dumps(
1450+
{
1451+
"instructions": {
1452+
"storage.buckets.get": ["return-503-if-dp-enforced"]
1453+
},
1454+
"transport": "GRPC",
1455+
},
1456+
),
1457+
)
1458+
self.assertEqual(response.status_code, 200)
1459+
test_id = json.loads(response.data).get("id")
1460+
1461+
# Call WITH force_direct_connectivity=ENFORCED, expect 503 (UNAVAILABLE)
1462+
context = unittest.mock.Mock()
1463+
context.invocation_metadata.return_value = (
1464+
("x-retry-test-id", test_id),
1465+
("x-goog-request-params", "force_direct_connectivity=ENFORCED"),
1466+
)
1467+
self.grpc.GetBucket(
1468+
storage_pb2.GetBucketRequest(name="projects/_/buckets/bucket-name"), context
1469+
)
1470+
context.abort.assert_called_once_with(StatusCode.UNAVAILABLE, unittest.mock.ANY)
1471+
1472+
# Re-setup to test non-enforced behavior
1473+
response = self.rest_client.post(
1474+
"/retry_test",
1475+
data=json.dumps(
1476+
{
1477+
"instructions": {
1478+
"storage.buckets.get": ["return-503-if-dp-enforced"]
1479+
},
1480+
"transport": "GRPC",
1481+
}
1482+
),
1483+
)
1484+
test_id = json.loads(response.data).get("id")
1485+
1486+
# Call WITHOUT enforcement, expect SUCCESS (instruction should still be consumed)
1487+
context = unittest.mock.Mock()
1488+
context.invocation_metadata.return_value = (("x-retry-test-id", test_id),)
1489+
self.grpc.GetBucket(
1490+
storage_pb2.GetBucketRequest(name="projects/_/buckets/bucket-name"), context
1491+
)
1492+
self.assertEqual(context.abort.call_count, 0)
1493+
self.assertIsNone(self.db.peek_next_instruction(test_id, "storage.buckets.get"))
1494+
14451495
class _StatusAsCall:
14461496
"""_StatusAsCall wraps a status and pretends it is a client-side call"""
14471497

0 commit comments

Comments
 (0)