Skip to content

Commit 5d579ad

Browse files
xgtcodeshanchunhua
andauthored
fix(auth): retry transient ARK API key fetch failures (#989)
Co-authored-by: shanchunhua <shanchunhua@bytedance.com>
1 parent 137fe13 commit 5d579ad

2 files changed

Lines changed: 138 additions & 17 deletions

File tree

tests/auth/veauth/test_ark_veauth.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ def _raw(api_key):
2727
return {"Result": {"ApiKey": api_key}}
2828

2929

30+
def _raw_error(code, message, code_number=None):
31+
error = {"Code": code, "Message": message}
32+
if code_number is not None:
33+
error["CodeN"] = code_number
34+
return {
35+
"ResponseMetadata": {
36+
"RequestId": "request-id",
37+
"Action": "GetRawApiKey",
38+
"Version": "2024-01-01",
39+
"Service": "ark",
40+
"Region": "cn-beijing",
41+
"Error": error,
42+
}
43+
}
44+
45+
3046
def _key(id_, name):
3147
return {"Id": id_, "Name": name}
3248

@@ -127,6 +143,47 @@ def test_numeric_id_is_sent_as_control_plane_integer():
127143
}
128144

129145

146+
def test_raw_api_key_retries_internal_service_timeout():
147+
with (
148+
patch("veadk.auth.veauth.ark_veauth.ve_request") as request,
149+
patch("veadk.auth.veauth.ark_veauth.time.sleep") as sleep,
150+
):
151+
request.side_effect = [
152+
_raw_error(
153+
"InternalServiceTimeout",
154+
"Internal Service is timeout. Pls Contact With Admin",
155+
code_number=100016,
156+
),
157+
_raw_error(
158+
"InternalServiceTimeout",
159+
"Internal Service is timeout. Pls Contact With Admin",
160+
code_number=100016,
161+
),
162+
_raw("sk-SELECTED"),
163+
]
164+
165+
assert get_ark_token(api_key_id="4028965") == "sk-SELECTED"
166+
167+
assert request.call_count == 3
168+
assert sleep.call_count == 2
169+
170+
171+
def test_raw_api_key_does_not_retry_non_transient_error():
172+
with (
173+
patch("veadk.auth.veauth.ark_veauth.ve_request") as request,
174+
patch("veadk.auth.veauth.ark_veauth.time.sleep") as sleep,
175+
):
176+
request.return_value = _raw_error(
177+
"AccessDenied", "No permission to call GetRawApiKey."
178+
)
179+
180+
with pytest.raises(ValueError, match="AccessDenied"):
181+
get_ark_token(api_key_id="4028965")
182+
183+
assert request.call_count == 1
184+
sleep.assert_not_called()
185+
186+
130187
def test_environment_sts_token_is_used_for_signed_requests(monkeypatch):
131188
monkeypatch.setenv("VOLCENGINE_SESSION_TOKEN", "temporary-session-token")
132189
with patch("veadk.auth.veauth.ark_veauth.ve_request") as request:

veadk/auth/veauth/ark_veauth.py

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
# limitations under the License.
1414

1515
import os
16+
import random
17+
import time
18+
19+
import requests
1620

1721
from veadk.auth.veauth.utils import get_credential_from_vefaas_iam
1822
from veadk.utils.logger import get_logger
@@ -25,6 +29,40 @@
2529
# name or exhaust the list.
2630
_ARK_PROJECT_NAME = "default"
2731
_ARK_PAGE_SIZE = 100
32+
_RAW_API_KEY_MAX_ATTEMPTS = 3
33+
_RAW_API_KEY_RETRY_DELAYS_SECONDS = (0.2, 0.5)
34+
_RAW_API_KEY_RETRYABLE_ERROR_CODES = {
35+
"InternalError",
36+
"InternalServiceTimeout",
37+
"RequestTimeout",
38+
"ServiceUnavailable",
39+
"Throttling",
40+
"TooManyRequests",
41+
}
42+
_RAW_API_KEY_RETRYABLE_ERROR_CODE_NUMBERS = {100016}
43+
44+
45+
def _response_error(res: dict) -> dict:
46+
metadata = res.get("ResponseMetadata", {})
47+
if not isinstance(metadata, dict):
48+
return {}
49+
error = metadata.get("Error", {})
50+
return error if isinstance(error, dict) else {}
51+
52+
53+
def _is_retryable_raw_api_key_response(res: dict) -> bool:
54+
error = _response_error(res)
55+
return (
56+
error.get("Code") in _RAW_API_KEY_RETRYABLE_ERROR_CODES
57+
or error.get("CodeN") in _RAW_API_KEY_RETRYABLE_ERROR_CODE_NUMBERS
58+
)
59+
60+
61+
def _sleep_before_raw_api_key_retry(attempt: int) -> None:
62+
delay = _RAW_API_KEY_RETRY_DELAYS_SECONDS[
63+
min(attempt - 1, len(_RAW_API_KEY_RETRY_DELAYS_SECONDS) - 1)
64+
]
65+
time.sleep(delay + random.uniform(0, delay / 5))
2866

2967

3068
def get_ark_token(
@@ -142,20 +180,46 @@ def _list_api_keys(page_number: int) -> dict:
142180
if isinstance(target_id, str) and target_id.isdigit()
143181
else target_id
144182
)
145-
res = ve_request(
146-
request_body={"Id": request_key_id, "ProjectName": _ARK_PROJECT_NAME},
147-
header={"X-Security-Token": session_token},
148-
action="GetRawApiKey",
149-
ak=access_key,
150-
sk=secret_key,
151-
service="ark",
152-
version="2024-01-01",
153-
region=region,
154-
host=host,
155-
)
156-
try:
157-
api_key = res["Result"]["ApiKey"]
158-
logger.info("Successfully fetched ARK API Key.")
159-
return api_key
160-
except KeyError as error:
161-
raise ValueError("Failed to get ARK API key.") from error
183+
request_body = {"Id": request_key_id, "ProjectName": _ARK_PROJECT_NAME}
184+
for attempt in range(1, _RAW_API_KEY_MAX_ATTEMPTS + 1):
185+
try:
186+
res = ve_request(
187+
request_body=request_body,
188+
header={"X-Security-Token": session_token},
189+
action="GetRawApiKey",
190+
ak=access_key,
191+
sk=secret_key,
192+
service="ark",
193+
version="2024-01-01",
194+
region=region,
195+
host=host,
196+
)
197+
except requests.exceptions.RequestException as error:
198+
if attempt >= _RAW_API_KEY_MAX_ATTEMPTS:
199+
raise ValueError("Failed to get ARK api key.") from error
200+
logger.warning(
201+
"GetRawApiKey request failed; retrying "
202+
f"({attempt}/{_RAW_API_KEY_MAX_ATTEMPTS}): {error}"
203+
)
204+
_sleep_before_raw_api_key_retry(attempt)
205+
continue
206+
207+
try:
208+
api_key = res["Result"]["ApiKey"]
209+
logger.info("Successfully fetched ARK API Key.")
210+
return api_key
211+
except KeyError as error:
212+
if (
213+
attempt < _RAW_API_KEY_MAX_ATTEMPTS
214+
and _is_retryable_raw_api_key_response(res)
215+
):
216+
logger.warning(
217+
"GetRawApiKey returned a retryable error; retrying "
218+
f"({attempt}/{_RAW_API_KEY_MAX_ATTEMPTS}): "
219+
f"{_response_error(res)}"
220+
)
221+
_sleep_before_raw_api_key_retry(attempt)
222+
continue
223+
raise ValueError(f"Failed to get ARK api key: {res}") from error
224+
225+
raise ValueError("Failed to get ARK api key.")

0 commit comments

Comments
 (0)