Handle faultstring when using SessionClient

When the magnum-api returns an error code 400-600, SessionClient
faultstring did not return to users. It should return a detailed
error message.

This commit was ported from Ironic:
https://review.openstack.org/#/c/142021/

Change-Id: I8bc15c0ddab10f0e117fb6acb2f3995929fa65e2
Closes-Bug: #1520363
This commit is contained in:
Hongbin Lu 2015-11-26 16:39:32 -05:00
parent bab488ff1c
commit 14efefd01f
4 changed files with 97 additions and 29 deletions

View File

@ -36,6 +36,20 @@ CHUNKSIZE = 1024 * 64 # 64kB
API_VERSION = '/v1'
def _extract_error_json(body):
"""Return error_message from the HTTP response body."""
error_json = {}
try:
body_json = json.loads(body)
if 'error_message' in body_json:
raw_msg = body_json['error_message']
error_json = json.loads(raw_msg)
except ValueError:
return {}
return error_json
class HTTPClient(object):
def __init__(self, endpoint, **kwargs):
@ -119,18 +133,6 @@ class HTTPClient(object):
base_url = _args[2]
return '%s/%s' % (base_url, url.lstrip('/'))
def _extract_error_json(self, body):
error_json = {}
try:
body_json = json.loads(body)
if 'error_message' in body_json:
raw_msg = body_json['error_message']
error_json = json.loads(raw_msg)
except ValueError:
return {}
return error_json
def _http_request(self, url, method, **kwargs):
"""Send an http request with the specified characteristics.
@ -173,7 +175,7 @@ class HTTPClient(object):
if 400 <= resp.status < 600:
LOG.warn("Request returned failure status.")
error_json = self._extract_error_json(body_str)
error_json = _extract_error_json(body_str)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
@ -303,7 +305,10 @@ class SessionClient(adapter.LegacyJsonAdapter):
raise_exc=False, **kwargs)
if 400 <= resp.status_code < 600:
raise exceptions.from_response(resp)
error_json = _extract_error_json(resp.content)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (301, 302, 305):
# Redirected. Reissue the request to the new location.
location = resp.headers.get('location')

View File

@ -56,6 +56,9 @@ def from_response(response, message=None, traceback=None, method=None,
response.status_code = response.status
response.headers = {
'Content-Type': response.getheader('content-type', "")}
if hasattr(response, 'status_code'):
# NOTE(hongbin): This allows SessionClient to handle faultstring.
response.json = lambda: {'error': error_body}
if (response.headers['Content-Type'].startswith('text/') and

View File

@ -22,6 +22,17 @@ from magnumclient import exceptions as exc
from magnumclient.tests import utils
def _get_error_body(faultstring=None, debuginfo=None):
error_body = {
'faultstring': faultstring,
'debuginfo': debuginfo
}
raw_error_body = json.dumps(error_body)
body = {'error_message': raw_error_body}
raw_body = json.dumps(body)
return raw_body
HTTP_CLASS = six.moves.http_client.HTTPConnection
HTTPS_CLASS = http.VerifiedHTTPSConnection
DEFAULT_TIMEOUT = 600
@ -49,19 +60,8 @@ class HttpClientTest(utils.BaseTestCase):
url = client._make_connection_url('v1/resources')
self.assertEqual('/v1/resources', url)
@staticmethod
def _get_error_body(faultstring=None, debuginfo=None):
error_body = {
'faultstring': faultstring,
'debuginfo': debuginfo
}
raw_error_body = json.dumps(error_body)
body = {'error_message': raw_error_body}
raw_body = json.dumps(body)
return raw_body
def test_server_exception_empty_body(self):
error_body = self._get_error_body()
error_body = _get_error_body()
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
@ -77,7 +77,7 @@ class HttpClientTest(utils.BaseTestCase):
def test_server_exception_msg_only(self):
error_msg = 'test error msg'
error_body = self._get_error_body(error_msg)
error_body = _get_error_body(error_msg)
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
@ -95,7 +95,7 @@ class HttpClientTest(utils.BaseTestCase):
error_msg = 'another test error'
error_trace = ("\"Traceback (most recent call last):\\n\\n "
"File \\\"/usr/local/lib/python2.7/...")
error_body = self._get_error_body(error_msg, error_trace)
error_body = _get_error_body(error_msg, error_trace)
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
@ -218,7 +218,7 @@ class HttpClientTest(utils.BaseTestCase):
self.assertEqual(expected, params)
def test_401_unauthorized_exception(self):
error_body = self._get_error_body()
error_body = _get_error_body()
fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
six.StringIO(error_body),
version=1,
@ -229,3 +229,43 @@ class HttpClientTest(utils.BaseTestCase):
self.assertRaises(exc.Unauthorized, client.json_request,
'GET', '/v1/resources')
class SessionClientTest(utils.BaseTestCase):
def test_server_exception_msg_and_traceback(self):
error_msg = 'another test error'
error_trace = ("\"Traceback (most recent call last):\\n\\n "
"File \\\"/usr/local/lib/python2.7/...")
error_body = _get_error_body(error_msg, error_trace)
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
error_body,
500)
client = http.SessionClient(session=fake_session)
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual(
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
'trace': error_trace},
"%(error)s\n%(details)s" % {'error': str(error),
'details': str(error.details)})
def test_server_exception_empty_body(self):
error_body = _get_error_body()
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
error_body,
500)
client = http.SessionClient(session=fake_session)
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual('Internal Server Error (HTTP 500)', str(error))

View File

@ -158,3 +158,23 @@ class TestCase(testtools.TestCase):
sys.stderr.close()
sys.stderr = orig_stderr
return (stdout, stderr)
class FakeSessionResponse(object):
def __init__(self, headers, content=None, status_code=None):
self.headers = headers
self.content = content
self.status_code = status_code
class FakeSession(object):
def __init__(self, headers, content=None, status_code=None):
self.headers = headers
self.content = content
self.status_code = status_code
def request(self, url, method, **kwargs):
return FakeSessionResponse(self.headers, self.content,
self.status_code)