Do not expose exceptions from requests library

Novaclient expects the requests library to always be able to get a
response back and doesn't handle other cases where it might raise
an exception even before the request is completed (e.g.
ConnectionError).

These exceptions should be captured and properly converted to some
native novaclient exception.

Change-Id: Iec7247b715c38c29910b30e76413cead4e951a37
Co-Authored-By: Kuo-tung Kao <jelly.k@inwinstack.com>
Closes-Bug: #1406586
This commit is contained in:
Jason Dunsmore 2015-10-15 12:00:39 -05:00
parent 03f1f67ccd
commit 2961e82bfa
4 changed files with 117 additions and 11 deletions

View File

@ -84,13 +84,12 @@ class SessionClient(adapter.LegacyJsonAdapter):
# NOTE(jamielennox): The standard call raises errors from
# keystoneclient, where we need to raise the novaclient errors.
raise_exc = kwargs.pop('raise_exc', True)
with utils.record_time(self.times, self.timings, method, url):
resp, body = super(SessionClient, self).request(url,
method,
raise_exc=False,
**kwargs)
if raise_exc and resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method)
with utils.converted_exceptions():
with utils.record_time(self.times, self.timings, method, url):
resp, body = super(SessionClient, self).request(
url, method, raise_exc=False, **kwargs)
if raise_exc and resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method)
return resp, body
@ -360,10 +359,11 @@ class HTTPClient(object):
if session:
request_func = session.request
resp = request_func(
method,
url,
**kwargs)
with utils.converted_exceptions():
resp = request_func(
method,
url,
**kwargs)
self.http_log_resp(resp)

View File

@ -283,3 +283,26 @@ def from_response(response, body, url, method=None):
class ResourceNotFound(Exception):
"""Error in getting the resource."""
pass
class RequestTimeout(ClientException):
"""
HTTP 408 - Request Timeout
"""
http_status = 408
message = "Request Timeout"
class TooManyRedirects(Exception):
"""Too many redirects."""
pass
class ConnectionError(Exception):
"""A Connection error occurred."""
pass
class RequestException(Exception):
"""An ambiguous exception occurred."""
pass

View File

@ -21,6 +21,7 @@ import fixtures
from keystoneclient import adapter
import mock
import requests
import six
import novaclient.client
import novaclient.extension
@ -438,6 +439,35 @@ class ClientTest(utils.TestCase):
self.assertEqual(1, len(client.times))
self.assertEqual('GET http://no.where', client.times[0][0])
@mock.patch.object(requests, 'request')
def test_request_exception_conversions(self, m_request):
client = novaclient.client.HTTPClient(user='zqfan', password='')
exc = requests.HTTPError()
exc.response = requests.Response()
exc.response.status_code = 12345
m_request.side_effect = exc
exc = self.assertRaises(novaclient.exceptions.ClientException,
client.request, "http://www.openstack.org",
'GET')
self.assertIn("HTTP 12345", six.text_type(exc))
m_request.side_effect = requests.ConnectionError()
self.assertRaises(novaclient.exceptions.ConnectionError,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.Timeout()
self.assertRaises(novaclient.exceptions.RequestTimeout,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.TooManyRedirects()
self.assertRaises(novaclient.exceptions.TooManyRedirects,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.RequestException()
self.assertRaises(novaclient.exceptions.RequestException,
client.request, "http://www.openstack.org", 'GET')
class SessionClientTest(utils.TestCase):
@ -454,3 +484,32 @@ class SessionClientTest(utils.TestCase):
client.request("http://no.where", 'GET')
self.assertEqual(1, len(client.times))
self.assertEqual('GET http://no.where', client.times[0][0])
@mock.patch.object(adapter.LegacyJsonAdapter, 'request')
def test_request_exception_conversions(self, m_request):
client = novaclient.client.SessionClient(session=mock.MagicMock())
exc = requests.HTTPError()
exc.response = requests.Response()
exc.response.status_code = 12345
m_request.side_effect = exc
exc = self.assertRaises(novaclient.exceptions.ClientException,
client.request, "http://www.openstack.org",
'GET')
self.assertIn("HTTP 12345", six.text_type(exc))
m_request.side_effect = requests.ConnectionError()
self.assertRaises(novaclient.exceptions.ConnectionError,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.Timeout()
self.assertRaises(novaclient.exceptions.RequestTimeout,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.TooManyRedirects()
self.assertRaises(novaclient.exceptions.TooManyRedirects,
client.request, "http://www.openstack.org", 'GET')
m_request.side_effect = requests.RequestException()
self.assertRaises(novaclient.exceptions.RequestException,
client.request, "http://www.openstack.org", 'GET')

View File

@ -22,6 +22,7 @@ from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import pkg_resources
import prettytable
import requests
import six
from novaclient import exceptions
@ -376,6 +377,29 @@ def record_time(times, enabled, *args):
times.append((' '.join(args), start, end))
@contextlib.contextmanager
def converted_exceptions():
try:
yield
except requests.HTTPError as e:
status_code = e.response.status_code
raise exceptions.ClientException(status_code,
encodeutils.exception_to_unicode(e))
except requests.ConnectionError as e:
raise exceptions.ConnectionError(encodeutils.exception_to_unicode(e))
except requests.Timeout as e:
raise exceptions.RequestTimeout(encodeutils.exception_to_unicode(e))
except requests.TooManyRedirects as e:
raise exceptions.TooManyRedirects(encodeutils.exception_to_unicode(e))
except requests.RequestException as e:
raise exceptions.RequestException(encodeutils.exception_to_unicode(e))
def get_function_name(func):
if six.PY2:
if hasattr(func, "im_class"):