Skip to content
Snippets Groups Projects
Commit 3eba0a36 authored by Gavin M. Roy's avatar Gavin M. Roy
Browse files

Rework error response processing

parent a7894a9a
No related branches found
No related tags found
No related merge requests found
import contextlib
import io
import json
import os
import tempfile
import unittest
import uuid
 
from tornado_aws import client
import mock
from tornado import httpclient, httputil
from tornado_aws import client, exceptions
 
from . import utils
 
 
class BaseTestCase(unittest.TestCase):
class TestCase(unittest.TestCase):
 
CLIENT = client.AWSClient
 
def setUp(self):
super(TestCase, self).setUp()
utils.clear_environment()
self.creds = {
'default': {
'aws_access_key_id': uuid.uuid4().hex,
'aws_secret_access_key': uuid.uuid4().hex}}
def get_client(self, *args, **kwargs):
return self.CLIENT(*args, **kwargs)
 
@contextlib.contextmanager
def client_with_no_creds(self, *args, **kwargs):
handle = tempfile.NamedTemporaryFile()
handle.write(utils.build_ini({'default': {}}))
handle.flush()
handle.seek(0)
os.environ['AWS_SHARED_CREDENTIALS_FILE'] = handle.name
yield self.get_client(*args, **kwargs)
handle.close()
 
class ClientConfigTestCase(BaseTestCase):
def setUp(self):
super(ClientConfigTestCase, self).setUp()
os.environ.pop('AWS_CONFIG_FILE', None)
os.environ.pop('AWS_SHARED_CREDENTIALS_FILE', None)
os.environ.pop('AWS_DEFAULT_PROFILE', None)
@contextlib.contextmanager
def client_with_default_creds(self, *args, **kwargs):
handle = tempfile.NamedTemporaryFile()
handle.write(utils.build_ini(self.creds))
handle.flush()
handle.seek(0)
os.environ['AWS_SHARED_CREDENTIALS_FILE'] = handle.name
yield self.get_client(*args, **kwargs)
handle.close()
 
 
class ClientConfigTestCase(TestCase):
 
def test_passed_in_values(self):
region = uuid.uuid4().hex
Loading
Loading
@@ -91,7 +117,346 @@ class ClientConfigTestCase(BaseTestCase):
self.assertEqual(cfg['profile custom']['region'], obj._region)
 
 
class AsyncClientConfigTestCase(ClientConfigTestCase):
 
CLIENT = client.AsyncAWSClient
class AMZErrorTestCase(TestCase):
def test_awz_error(self):
content = b'{"__type": "MissingAuthenticationTokenException", ' \
b'"message": "Missing Authentication Token"}'
expectation = {'__type': 'MissingAuthenticationTokenException',
'message': 'Missing Authentication Token'}
with self.client_with_default_creds('s3') as obj:
self.assertDictEqual(obj._parse_awz_error(content), expectation)
def test_unsupported_payload(self):
content = b'{"message": "Missing Authentication Token"}'
with self.client_with_default_creds('invalid') as obj:
self.assertIsNone(obj._parse_awz_error(content))
class XMLErrorTestCase(TestCase):
def test_ec2_error(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>' \
b'<Response><Errors><Error><Code>InvalidAction</Code>' \
b'<Message>The action CreateVolume is not valid for t' \
b'his web service.</Message></Error></Errors><Request' \
b'ID>cb159b66-84a7-43bf-8a02-c839cc50b164</RequestID>' \
b'</Response>'
expectation = {
'Code': 'InvalidAction',
'Message': 'The action CreateVolume is not valid for this '
'web service.',
'RequestId': 'cb159b66-84a7-43bf-8a02-c839cc50b164'}
with self.client_with_default_creds('s3') as obj:
self.assertDictEqual(obj._parse_xml_error(content), expectation)
def test_advertising_error(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>\n<Errors>' \
b'<Error><Code>AWS.InvalidAccount</Code><Message>T' \
b'he provided token has expired.</Message></Error></Errors>'
expectation = {'Code': 'AWS.InvalidAccount',
'Message': 'The provided token has expired.'}
with self.client_with_default_creds('s3') as obj:
self.assertDictEqual(obj._parse_xml_error(content), expectation)
def test_s3_error(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>\n<Error>' \
b'<Code>ExpiredToken</Code><Message>The provided ' \
b'token has expired.</Message><RequestId>DA1C5F5' \
b'26B1A0EF2</RequestId><HostId>381a948d-a988-4e7' \
b'a-aefe-e27e82555bed</HostId></Error>'
expectation = {'Code': 'ExpiredToken',
'HostId': '381a948d-a988-4e7a-aefe-e27e82555bed',
'Message': 'The provided token has expired.',
'RequestId': 'DA1C5F526B1A0EF2'}
with self.client_with_default_creds('s3') as obj:
self.assertDictEqual(obj._parse_xml_error(content), expectation)
def test_simpledb_error(self):
content = b'<MissingAuthenticationTokenException><Message>' \
b'Missing Authentication Token</Message></Missin' \
b'gAuthenticationTokenException>'
expectation = {'Code': 'MissingAuthenticationTokenException',
'Message': 'Missing Authentication Token'}
with self.client_with_default_creds('simpledb') as obj:
self.assertDictEqual(obj._parse_xml_error(content), expectation)
def test_unparsable_error(self):
content = b'error'
with self.client_with_default_creds('invalid') as obj:
with self.assertRaises(ValueError):
obj._parse_xml_error(content)
def test_unsupported_error(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>\n<Errs>' \
b'<Err><Code>AWS.InvalidAccount</Code><Message>T' \
b'he provided token has expired.</Message></Err></Errs>'
with self.client_with_default_creds('invalid') as obj:
with self.assertRaises(ValueError):
obj._parse_xml_error(content)
class ProcessErrorTestCase(TestCase):
def test_599_bypasses_processing(self):
error = httpclient.HTTPError(599, 'Timeout')
with self.client_with_default_creds('dynamodb') as obj:
self.assertEqual(obj._process_error(error), (False, None))
def test_process_error_awz_creds(self):
content = b'{"__type": "MissingAuthenticationTokenException", ' \
b'"message": "Missing Authentication Token"}'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/test')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/x-amz-json-1.1',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
error = httpclient.HTTPError(400, 'Bad Request', response)
with self.client_with_default_creds('dynamodb') as obj:
result = obj._process_error(error)
self.assertTrue(result[0])
def test_process_dynamodb_error_creds(self):
content = b'{"__type": "com.amazonaws.dynamodb.v20120810#MissingAut' \
b'henticationTokenException", "message": "Missing Authen' \
b'tication Token"}'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/test')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/x-amz-json-1.0',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
error = httpclient.HTTPError(400, 'Bad Request', response)
with self.client_with_default_creds('dynamodb') as obj:
result = obj._process_error(error)
self.assertTrue(result[0])
self.assertIsInstance(result[1], exceptions.AWSError)
def test_process_error_s3(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>\n<Error>' \
b'<Code>ExpiredToken</Code><Message>The provided ' \
b'token has expired.</Message><RequestId>DA1C5F5' \
b'26B1A0EF2</RequestId><HostId>381a948d-a988-4e7' \
b'a-aefe-e27e82555bed</HostId></Error>'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/xml',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
error = httpclient.HTTPError(400, 'Bad Request', response)
with self.client_with_default_creds('s3') as obj:
result = obj._process_error(error)
self.assertTrue(result[0])
self.assertIsInstance(result[1], exceptions.AWSError)
def test_process_error_ec2_non_auth(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>' \
b'<Response><Errors><Error><Code>InvalidAction</Code>' \
b'<Message>The action CreateVolume is not valid for t' \
b'his web service.</Message></Error></Errors><Request' \
b'ID>cb159b66-84a7-43bf-8a02-c839cc50b164</RequestID>' \
b'</Response>'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/xml',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
error = httpclient.HTTPError(400, 'Bad Request', response)
with self.client_with_default_creds('dynamodb') as obj:
result = obj._process_error(error)
self.assertFalse(result[0])
self.assertIsInstance(result[1], exceptions.AWSError)
def test_process_simpledb_non_auth(self):
content = b'<?xml version="1.0" encoding="UTF-8"?>' \
b'<Response><Errors><Error><Code>InvalidAction</Code>' \
b'<Message>The action CreateVolume is not valid for t' \
b'his web service.</Message></Error></Errors><Request' \
b'ID>cb159b66-84a7-43bf-8a02-c839cc50b164</RequestID>' \
b'</Response>'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'x-amzn-RequestId': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
error = httpclient.HTTPError(400, 'Bad Request', response)
with self.client_with_default_creds('dynamodb') as obj:
result = obj._process_error(error)
self.assertFalse(result[0])
self.assertIsInstance(result[1], exceptions.AWSError)
def test_process_bogus_response(self):
content = b'Slow Down'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'x-amzn-RequestId': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 503, headers, stream)
error = httpclient.HTTPError(503, 'Bad Request', response)
with self.client_with_default_creds('s3') as obj:
self.assertEqual(obj._process_error(error), (False, None))
class ClientFetchTestCase(TestCase):
@staticmethod
def mock_ok_response():
content = b'{"foo": "bar"}'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'x-amzn-RequestId': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Content-Type': 'application/x-amz-json-1.0',
'Server': 'Server',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
return httpclient.HTTPResponse(request, 200, headers, stream)
@staticmethod
def mock_auth_exception():
content = b'<?xml version="1.0" encoding="UTF-8"?>\n<Error>' \
b'<Code>ExpiredToken</Code><Message>The provided ' \
b'token has expired.</Message><RequestId>DA1C5F5' \
b'26B1A0EF2</RequestId><HostId>381a948d-a988-4e7' \
b'a-aefe-e27e82555bed</HostId></Error>'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/xml',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
return httpclient.HTTPError(400, 'Bad Request', response)
@staticmethod
def mock_error_exception():
content = b'<?xml version="1.0" encoding="UTF-8"?>' \
b'<Response><Errors><Error><Code>InvalidAction</Code>' \
b'<Message>The action CreateVolume is not valid for t' \
b'his web service.</Message></Error></Errors><Request' \
b'ID>cb159b66-84a7-43bf-8a02-c839cc50b164</RequestID>' \
b'</Response>'
stream = io.BytesIO(content)
request = httpclient.HTTPRequest('/')
headers = httputil.HTTPHeaders(
{'Content-Type': 'application/xml',
'Server': 'Server',
'X-Amz-Request-Id': '3840c615-0503-4a53-a2f6-07afa795a5d6',
'Date': 'Tue, 06 Jun 2017 18:31:47 GMT'})
response = httpclient.HTTPResponse(request, 400, headers, stream)
return httpclient.HTTPError(400, 'Bad Request', response)
def test_fetch_success(self):
with self.client_with_default_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
fetch.return_value=self.mock_ok_response()
body = json.dumps({'foo': 'bar'}).encode('utf-8')
result = obj.fetch(
'POST', '/', body=body,
headers={'x-amz-target': 'DynamoDB_20120810.CreateTable',
'Content-Type': 'application/x-amz-json-1.0'})
self.assertEqual(result.code, 200)
fetch.assert_called_once()
# Get the first argument of the call into fetch
request = fetch.call_args_list[0][0][0]
self.assertEqual(request.method, 'POST')
self.assertEqual(request.headers['Content-Type'],
'application/x-amz-json-1.0')
def test_fetch_no_headers(self):
with self.client_with_default_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
fetch.return_value=self.mock_ok_response()
body = json.dumps({'foo': 'bar'})
result = obj.fetch('POST', '/', body=body)
self.assertEqual(result.code, 200)
fetch.assert_called_once()
# Get the first argument of the call into fetch
request = fetch.call_args_list[0][0][0]
self.assertEqual(request.method, 'POST')
def test_fetch_needs_credentials(self):
with self.client_with_default_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
with mock.patch.object(obj._auth_config, 'refresh') as refresh:
obj._auth_config._local_credentials = False
obj._auth_config._access_key = None
fetch.return_value = self.mock_ok_response()
body = json.dumps({'foo': 'bar'}).encode('utf-8')
result = obj.fetch(
'POST', '/', body=body,
headers={
'x-amz-target': 'DynamoDB_20120810.CreateTable',
'Content-Type': 'application/x-amz-json-1.0'})
self.assertEqual(result.code, 200)
fetch.assert_called_once()
refresh.assert_called_once()
# Get the first argument of the call into fetch
request = fetch.call_args_list[0][0][0]
self.assertEqual(request.method, 'POST')
self.assertEqual(request.headers['Content-Type'],
'application/x-amz-json-1.0')
def test_fetch_when_client_raises_error(self):
with self.client_with_default_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
with self.assertRaises(exceptions.AWSError):
fetch.side_effect = self.mock_error_exception()
obj.fetch(
'GET', '/',
query_args={'list_type': '2', 'delimiter': '/'},
headers={'Host': 'bucket.s3.amazonaws.com'})
def test_fetch_when_client_needs_credentials(self):
with self.client_with_no_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
with mock.patch.object(obj._auth_config, 'refresh') as refresh:
with mock.patch.object(obj._auth_config, 'reset') as reset:
fetch.side_effect = [self.mock_auth_exception(),
self.mock_ok_response()]
result = obj.fetch(
'GET', '/',
query_args={'list_type': '2', 'delimiter': '/'},
headers={'Host': 'bucket.s3.amazonaws.com'})
self.assertEqual(result.code, 200)
reset.assert_called_once()
self.assertEqual(fetch.call_count, 2)
self.assertEqual(refresh.call_count, 2)
def test_fetch_when_client_fails_credentials(self):
with self.client_with_no_creds('s3') as obj:
with mock.patch.object(obj._client, 'fetch') as fetch:
with mock.patch.object(obj._auth_config, 'refresh') as refresh:
with mock.patch.object(obj._auth_config, 'reset') as reset:
fetch.side_effect = [self.mock_auth_exception(),
self.mock_auth_exception()]
with self.assertRaises(exceptions.AWSError):
result = obj.fetch(
'GET', '/',
query_args={'list_type': '2', 'delimiter': '/'},
headers={'Host': 'bucket.s3.amazonaws.com'})
self.assertEqual(result.code, 200)
self.assertEqual(reset.call_count, 2)
self.assertEqual(fetch.call_count, 2)
self.assertEqual(refresh.call_count, 2)
Loading
Loading
@@ -13,28 +13,48 @@ import logging
import os
try:
from urllib import parse as _urlparse
except ImportError:
except ImportError: # pragma: nocover
import urlparse as _urlparse
 
from tornado import concurrent, httpclient, ioloop
 
try:
from tornado import curl_httpclient
except ImportError:
except ImportError: # pragma: nocover
curl_httpclient = None
 
from tornado_aws import config, exceptions
from tornado_aws import config, exceptions, xml
 
LOGGER = logging.getLogger(__name__)
 
MIME_AWZ_JSON = 'application/x-amz-json-1.0'
MIME_AWZ_JSON = 'application/x-amz-json-1.1'
_AWZ_CONTENT_TYPES = [
'application/json',
'application/x-amz-json-1.0',
'application/x-amz-json-1.1'
]
 
_REFRESH_EXCEPTIONS = [
'com.amazon.coral.service#InvalidSignatureException',
'com.amazon.coral.service#UnrecognizedClientException',
'com.amazon.coral.service#ExpiredTokenException'
'AuthFailure',
'AuthMissingFailure',
'AWS.InvalidAccount',
'ExpiredTokenException',
'InvalidSignatureException',
'MissingAuthenticationTokenException',
'UnrecognizedClientException'
]
_REFRESH_XML_EXCEPTIONS = [
'AuthFailure',
'ExpiredToken',
'InvalidClientTokenId',
'InvalidSecurity',
'MissingAuthenticationToken',
'SignatureDoesNotMatch'
]
 
_HEADER_FORMAT = '{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}'
 
 
Loading
Loading
@@ -114,7 +134,7 @@ class AWSClient(object):
self._host = self._hostname(self._endpoint_url)
 
def fetch(self, method, path='/', query_args=None, headers=None, body=b'',
_recursed=False):
recursed=False):
"""Executes a request, returning an
:py:class:`HTTPResponse <tornado.httpclient.HTTPResponse>`.
 
Loading
Loading
@@ -127,6 +147,7 @@ class AWSClient(object):
:param dict query_args: Request query arguments
:param dict headers: Request headers
:param bytes body: The request body
:param bool recursed: Internally invoked if it's a recursive fetch
:rtype: :class:`~tornado.httpclient.HTTPResponse`
:raises: :class:`~tornado.httpclient.HTTPError`
:raises: :class:`~tornado_aws.exceptions.NoCredentialsError`
Loading
Loading
@@ -142,20 +163,107 @@ class AWSClient(object):
result = self._client.fetch(request, raise_error=True)
return result
except httpclient.HTTPError as error:
awz_error = self._awz_error(error)
if awz_error:
if self._credentials_error(awz_error):
if not self._auth_config.local_credentials:
if not _recursed:
self._auth_config.refresh()
return self.fetch(method, path, query_args,
headers, body, True)
else:
self._auth_config.reset()
raise exceptions.AWSError(type=awz_error['__type'],
message=awz_error['message'])
raise
need_credentials, aws_error = self._process_error(error)
LOGGER.debug('err: %r, %r, %r', need_credentials, aws_error, self._auth_config.local_credentials)
if need_credentials and not self._auth_config.local_credentials:
self._auth_config.reset()
if not recursed:
return self.fetch(method, path, query_args,
headers, body, True)
raise aws_error if aws_error else error
def _process_error(self, error):
"""Attempt to process the error coming from AWS. Returns ``True``
if the client should attempt to fetch credentials and the AWSError
exception to raise if the client did not have an authentication error.
:param tornado.httpclient.HTTPError error: The HTTP error
:rtype: (tuple, tornado_aws.exceptions.AWSError)
"""
LOGGER.error('Error: %r', error)
if error.code == 599:
return False, None
elif error.code == 400 and self._awz_response(error.response):
awz_error = self._parse_awz_error(error.response.body)
return ((awz_error and
awz_error['__type'] in _REFRESH_EXCEPTIONS),
exceptions.AWSError(
type=awz_error['__type'],
message=awz_error['message']))
try:
xml_error = self._parse_xml_error(error.response.body)
except ValueError:
LOGGER.debug('Could not fallback to XML: %r', error)
return False, None
return ((xml_error and
xml_error['Code'] in _REFRESH_XML_EXCEPTIONS),
self._aws_error_from_xml(xml_error))
@staticmethod
def _aws_error_from_xml(error):
"""Return an AWSError exception for an XML error response, given the
variation in field names.
:param dict error: The parsed XML error
:rtype: tornado_aws.exceptions.AWSError
"""
return exceptions.AWSError(
type=error['Code'], message=error['Message'],
request_id=error.get('RequestId', error.get('x-amzn-RequestId')),
resource=error.get('Resource'))
@staticmethod
def _awz_response(response):
"""Returns ``True`` if the HTTPResponse headers indicate it is
an AWS style response
:param tornado.httpclient.HTTPResponse: The HTTP response
:rtype: bool
"""
return response.headers.get('Content-Type') in _AWZ_CONTENT_TYPES
@staticmethod
def _parse_awz_error(content):
"""Returns the AWZ error parsed out of the HTTPError that was raised.
:param bytes content: The response error content
:rtype: dict|None
"""
payload = json.loads(content.decode('utf-8'))
if isinstance(payload, dict) and '__type' in payload:
if '#' in payload['__type']:
payload['__type'] = \
payload['__type'][payload['__type'].index('#') + 1:]
return payload
@staticmethod
def _parse_xml_error(content):
"""Returns the XML error parsed out of the HTTPError that was raised.
:param bytes content: The response error content
:rtype: dict
:raises: ValueError
"""
payload = xml.loads(content.decode('utf-8'))
if 'Error' in payload:
return payload['Error']
elif 'Errors' in payload and 'Error' in payload['Errors']:
return payload['Errors']['Error']
elif 'Response' in payload and 'Errors' in payload['Response'] \
and 'Error' in payload['Response']['Errors']:
payload['Response']['Errors']['Error']['RequestId'] = \
payload['Response'].get('RequestID')
return payload['Response']['Errors']['Error']
key = tuple(payload.keys())[0]
if 'Message' in payload[key]:
return {'Code': key, 'Message': payload[key]['Message']}
raise ValueError
 
def _auth_header(self, amz_date, date_stamp, request_hash, signed_headers):
"""Return the Authorization string header value
Loading
Loading
@@ -173,27 +281,6 @@ class AWSClient(object):
scope,
signed_headers, signature)
 
@staticmethod
def _awz_error(error):
"""Returns the AWZ error parsed out of the HTTPError that was raised.
:param tornado.httpclient.HTTPError error: The error that was raised
:rtype: dict|None
"""
if not isinstance(error, httpclient.HTTPError):
return
if error.code >= 400 and error.response is not None:
try:
payload = json.loads(error.response.body.decode('utf-8'))
except (AttributeError, json.JSONDecodeError) as err:
LOGGER.error('Error decoding response as JSON (%s): %r %r',
err, error.response,
getattr(error.response, 'body', None))
return
if isinstance(payload, dict) and '__type' in payload:
return payload
def _create_request(self, method, path='/', query_args=None, headers=None,
body=b''):
"""Create the HTTPRequest instance that will be used to make the AWS
Loading
Loading
@@ -259,19 +346,6 @@ class AWSClient(object):
"""
return _urlparse.quote(value, safe='').replace('%7E', '~')
 
def _credentials_error(self, error):
"""Returns ``True`` if the the error is related to authentication
errors that could be remedied by loading from ECS metadata and user
data API.
:param dict error: The AWZ error response
:rtype: bool
"""
if self._auth_config.local_credentials:
return False
return error['__type'] in _REFRESH_EXCEPTIONS
@staticmethod
def _sign(key, msg):
"""Sign the msg with the key
Loading
Loading
@@ -440,7 +514,6 @@ class AsyncAWSClient(AWSClient):
:param str secret_key: The secret access key
:param str endpoint: Override the base endpoint URL
:param int max_clients: Max simultaneous HTTP requests (Default: ``100``)
:param bool use_curl: Use Tornado's CurlAsyncHTTPClient
:param tornado.ioloop.IOLoop io_loop: Specify the IOLoop to use
:raises: :exc:`tornado_aws.exceptions.ConfigNotFound`
:raises: :exc:`tornado_aws.exceptions.ConfigParserError`
Loading
Loading
@@ -476,7 +549,7 @@ class AsyncAWSClient(AWSClient):
force_instance=True)
 
def fetch(self, method, path='/', query_args=None, headers=None, body=None,
_recursed=False):
recursed=False):
"""Executes a request, returning an
:py:class:`HTTPResponse <tornado.httpclient.HTTPResponse>`.
 
Loading
Loading
@@ -489,6 +562,7 @@ class AsyncAWSClient(AWSClient):
:param dict query_args: Request query arguments
:param dict headers: Request headers
:param bytes body: The request body
:param bool recursed: Internal use only
:rtype: :class:`~tornado.httpclient.HTTPResponse`
:raises: :class:`~tornado.httpclient.HTTPError`
:raises: :class:`~tornado_aws.exceptions.AWSError`
Loading
Loading
@@ -500,29 +574,20 @@ class AsyncAWSClient(AWSClient):
def on_response(response):
exception = response.exception()
if exception:
awz_error = self._awz_error(exception)
if awz_error:
if self._credentials_error(awz_error):
self._auth_config.reset()
if not self._auth_config.local_credentials:
if not _recursed:
def on_retry(retry):
if not self._future_exception(retry,
future):
future.set_result(retry.result())
request = self.fetch(method, path, query_args,
headers, body, True)
self._ioloop.add_future(request, on_retry)
return
msg = awz_error.get('message', awz_error.get('Message'))
if not msg:
LOGGER.debug('awz_error without message: %r',
awz_error)
exception = exceptions.AWSError(
type=awz_error['__type'], message=msg)
future.set_exception(exception)
need_credentials, aws_error = self._process_error(exception)
if need_credentials and \
not self._auth_config.local_credentials:
self._auth_config.reset()
if not recursed:
def on_retry(retry):
if not self._future_exception(retry, future):
future.set_result(retry.result())
request = self.fetch(method, path, query_args,
headers, body, True)
self._ioloop.add_future(request, on_retry)
return
future.set_exception(aws_error if aws_error else exception)
else:
future.set_result(response.result())
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment