diff --git a/CHANGELOG.md b/CHANGELOG.md index 9762d0c03..70032efcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.10.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.0...v1.10.1) (2020-01-10) + + +### Bug Fixes + +* **google.auth.compute_engine.metadata:** add retry to google.auth.compute_engine._metadata.get() ([#398](https://www.github.com/googleapis/google-auth-library-python/issues/398)) ([af29c1a](https://www.github.com/googleapis/google-auth-library-python/commit/af29c1a9fd9282b38867961e4053f74f018a3815)), closes [#211](https://www.github.com/googleapis/google-auth-library-python/issues/211) [#323](https://www.github.com/googleapis/google-auth-library-python/issues/323) [#323](https://www.github.com/googleapis/google-auth-library-python/issues/323) [#211](https://www.github.com/googleapis/google-auth-library-python/issues/211) +* always pass body of type bytes to `google.auth.transport.Request` ([#421](https://www.github.com/googleapis/google-auth-library-python/issues/421)) ([a57a770](https://www.github.com/googleapis/google-auth-library-python/commit/a57a7708cfea635b5030f8c7ba10c967715f9a87)), closes [#318](https://www.github.com/googleapis/google-auth-library-python/issues/318) + ## [1.10.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.9.0...v1.10.0) (2019-12-18) diff --git a/google/auth/compute_engine/_metadata.py b/google/auth/compute_engine/_metadata.py index f4fae7298..30cd3d43b 100644 --- a/google/auth/compute_engine/_metadata.py +++ b/google/auth/compute_engine/_metadata.py @@ -99,7 +99,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): return False -def get(request, path, root=_METADATA_ROOT, recursive=False): +def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): """Fetch a resource from the metadata server. Args: @@ -111,6 +111,8 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more details. + retry_count (int): How many times to attempt connecting to metadata + server using above timeout. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -129,7 +131,24 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): url = _helpers.update_query(base_url, query_params) - response = request(url=url, method="GET", headers=_METADATA_HEADERS) + retries = 0 + while retries < retry_count: + try: + response = request(url=url, method="GET", headers=_METADATA_HEADERS) + break + + except exceptions.TransportError: + _LOGGER.info( + "Compute Engine Metadata server unavailable on" "attempt %s of %s", + retries + 1, + retry_count, + ) + retries += 1 + else: + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine" + "metadata service. Compute Engine Metadata server unavailable".format(url) + ) if response.status == http_client.OK: content = _helpers.from_bytes(response.data) diff --git a/google/auth/iam.py b/google/auth/iam.py index a43872658..0ab5b5549 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -70,7 +70,9 @@ def _make_signing_request(self, message): method = "POST" url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} - body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) + body = json.dumps( + {"bytesToSign": base64.b64encode(message).decode("utf-8")} + ).encode("utf-8") self._credentials.before_request(self._request, method, url, headers) response = self._request(url=url, method=method, body=body, headers=headers) diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index 70fa5dc9c..bc7031e78 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -84,7 +84,7 @@ def _make_iam_token_request(request, principal, headers, body): """ iam_endpoint = _IAM_ENDPOINT.format(principal) - body = json.dumps(body) + body = json.dumps(body).encode("utf-8") response = request(url=iam_endpoint, method="POST", headers=headers, body=body) diff --git a/google/oauth2/_client.py b/google/oauth2/_client.py index 4cf7a7fe9..4ba31a87a 100644 --- a/google/oauth2/_client.py +++ b/google/oauth2/_client.py @@ -95,7 +95,7 @@ def _token_endpoint_request(request, token_uri, body): google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - body = urllib.parse.urlencode(body) + body = urllib.parse.urlencode(body).encode("utf-8") headers = {"content-type": _URLENCODED_CONTENT_TYPE} retry = 0 diff --git a/setup.py b/setup.py index 954ce1763..1b5eac4bc 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.10.0" +version = "1.10.1" setup( name="google-auth", diff --git a/tests/compute_engine/test__metadata.py b/tests/compute_engine/test__metadata.py index bd06b7402..0898e1f4e 100644 --- a/tests/compute_engine/test__metadata.py +++ b/tests/compute_engine/test__metadata.py @@ -30,14 +30,17 @@ PATH = "instance/service-accounts/default" -def make_request(data, status=http_client.OK, headers=None): +def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = _helpers.to_bytes(data) response.headers = headers or {} request = mock.create_autospec(transport.Request) - request.return_value = response + if retry: + request.side_effect = [exceptions.TransportError(), response] + else: + request.return_value = response return request @@ -55,6 +58,20 @@ def test_ping_success(): ) +def test_ping_success_retry(): + request = make_request("", headers=_metadata._METADATA_HEADERS, retry=True) + + assert _metadata.ping(request) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_IP_ROOT, + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) + assert request.call_count == 2 + + def test_ping_failure_bad_flavor(): request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) @@ -105,6 +122,25 @@ def test_get_success_json(): assert result[key] == value +def test_get_success_retry(): + key, value = "foo", "bar" + + data = json.dumps({key: value}) + request = make_request( + data, headers={"content-type": "application/json"}, retry=True + ) + + result = _metadata.get(request, PATH) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 2 + assert result[key] == value + + def test_get_success_text(): data = "foobar" request = make_request(data, headers={"content-type": "text/plain"}) @@ -154,6 +190,23 @@ def test_get_failure(): ) +def test_get_failure_connection_failed(): + request = make_request("") + request.side_effect = exceptions.TransportError() + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request, PATH) + + assert excinfo.match(r"Compute Engine Metadata server unavailable") + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 5 + + def test_get_failure_bad_json(): request = make_request("{", headers={"content-type": "application/json"}) diff --git a/tests/oauth2/test__client.py b/tests/oauth2/test__client.py index 9cf59eb98..052390aa8 100644 --- a/tests/oauth2/test__client.py +++ b/tests/oauth2/test__client.py @@ -96,7 +96,7 @@ def test__token_endpoint_request(): method="POST", url="http://example.com", headers={"content-type": "application/x-www-form-urlencoded"}, - body="test=params", + body="test=params".encode("utf-8"), ) # Check result @@ -131,7 +131,7 @@ def test__token_endpoint_request_internal_failure_error(): def verify_request_params(request, params): - request_body = request.call_args[1]["body"] + request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) for key, value in six.iteritems(params):