Skip to content

Commit 61c2432

Browse files
authoredJul 12, 2024··
fix(metadata): enhance retry logic for metadata server access in _metadata.py (#1545)
* fix(metadata): Use exponential backoff retry logic for GCE metadata server access
1 parent 461a3f5 commit 61c2432

File tree

3 files changed

+22
-16
lines changed

3 files changed

+22
-16
lines changed
 

‎CONTRIBUTING.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A few notes on making changes to ``google-auth-library-python``.
1616
- If you've added a new feature or modified an existing feature, be sure to
1717
add or update any applicable documentation in docstrings and in the
1818
documentation (in ``docs/``). You can re-generate the reference documentation
19-
using ``nox -s docgen``.
19+
using ``nox -s docs``.
2020

2121
- The change must work fully on the following CPython versions:
2222
3.7, 3.8, 3.9, 3.10, 3.11 and 3.12 across macOS, Linux, and Windows.

‎google/auth/compute_engine/_metadata.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@
2828
from google.auth import environment_vars
2929
from google.auth import exceptions
3030
from google.auth import metrics
31+
from google.auth._exponential_backoff import ExponentialBackoff
3132

3233
_LOGGER = logging.getLogger(__name__)
3334

3435
# Environment variable GCE_METADATA_HOST is originally named
35-
# GCE_METADATA_ROOT. For compatiblity reasons, here it checks
36+
# GCE_METADATA_ROOT. For compatibility reasons, here it checks
3637
# the new variable first; if not set, the system falls back
3738
# to the old variable.
3839
_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None)
@@ -119,11 +120,12 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
119120
# could lead to false negatives in the event that we are on GCE, but
120121
# the metadata resolution was particularly slow. The latter case is
121122
# "unlikely".
122-
retries = 0
123123
headers = _METADATA_HEADERS.copy()
124124
headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping()
125125

126-
while retries < retry_count:
126+
backoff = ExponentialBackoff(total_attempts=retry_count)
127+
128+
for attempt in backoff:
127129
try:
128130
response = request(
129131
url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout
@@ -139,11 +141,10 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
139141
_LOGGER.warning(
140142
"Compute Engine Metadata server unavailable on "
141143
"attempt %s of %s. Reason: %s",
142-
retries + 1,
144+
attempt,
143145
retry_count,
144146
e,
145147
)
146-
retries += 1
147148

148149
return False
149150

@@ -179,7 +180,7 @@ def get(
179180
180181
Returns:
181182
Union[Mapping, str]: If the metadata server returns JSON, a mapping of
182-
the decoded JSON is return. Otherwise, the response content is
183+
the decoded JSON is returned. Otherwise, the response content is
183184
returned as a string.
184185
185186
Raises:
@@ -198,8 +199,9 @@ def get(
198199

199200
url = _helpers.update_query(base_url, query_params)
200201

201-
retries = 0
202-
while retries < retry_count:
202+
backoff = ExponentialBackoff(total_attempts=retry_count)
203+
204+
for attempt in backoff:
203205
try:
204206
response = request(url=url, method="GET", headers=headers_to_use)
205207
break
@@ -208,11 +210,10 @@ def get(
208210
_LOGGER.warning(
209211
"Compute Engine Metadata server unavailable on "
210212
"attempt %s of %s. Reason: %s",
211-
retries + 1,
213+
attempt,
212214
retry_count,
213215
e,
214216
)
215-
retries += 1
216217
else:
217218
raise exceptions.TransportError(
218219
"Failed to retrieve {} from the Google Compute Engine "

‎tests/compute_engine/test__metadata.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,15 @@ def test_ping_success_retry(mock_metrics_header_value):
125125
assert request.call_count == 2
126126

127127

128-
def test_ping_failure_bad_flavor():
128+
@mock.patch("time.sleep", return_value=None)
129+
def test_ping_failure_bad_flavor(mock_sleep):
129130
request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"})
130131

131132
assert not _metadata.ping(request)
132133

133134

134-
def test_ping_failure_connection_failed():
135+
@mock.patch("time.sleep", return_value=None)
136+
def test_ping_failure_connection_failed(mock_sleep):
135137
request = make_request("")
136138
request.side_effect = exceptions.TransportError()
137139

@@ -194,7 +196,8 @@ def test_get_success_json_content_type_charset():
194196
assert result[key] == value
195197

196198

197-
def test_get_success_retry():
199+
@mock.patch("time.sleep", return_value=None)
200+
def test_get_success_retry(mock_sleep):
198201
key, value = "foo", "bar"
199202

200203
data = json.dumps({key: value})
@@ -310,7 +313,8 @@ def test_get_success_custom_root_old_variable():
310313
)
311314

312315

313-
def test_get_failure():
316+
@mock.patch("time.sleep", return_value=None)
317+
def test_get_failure(mock_sleep):
314318
request = make_request("Metadata error", status=http_client.NOT_FOUND)
315319

316320
with pytest.raises(exceptions.TransportError) as excinfo:
@@ -337,7 +341,8 @@ def test_get_return_none_for_not_found_error():
337341
)
338342

339343

340-
def test_get_failure_connection_failed():
344+
@mock.patch("time.sleep", return_value=None)
345+
def test_get_failure_connection_failed(mock_sleep):
341346
request = make_request("")
342347
request.side_effect = exceptions.TransportError()
343348

0 commit comments

Comments
 (0)
Please sign in to comment.