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:
parent
bab488ff1c
commit
14efefd01f
|
@ -36,6 +36,20 @@ CHUNKSIZE = 1024 * 64 # 64kB
|
||||||
API_VERSION = '/v1'
|
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):
|
class HTTPClient(object):
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
def __init__(self, endpoint, **kwargs):
|
||||||
|
@ -119,18 +133,6 @@ class HTTPClient(object):
|
||||||
base_url = _args[2]
|
base_url = _args[2]
|
||||||
return '%s/%s' % (base_url, url.lstrip('/'))
|
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):
|
def _http_request(self, url, method, **kwargs):
|
||||||
"""Send an http request with the specified characteristics.
|
"""Send an http request with the specified characteristics.
|
||||||
|
|
||||||
|
@ -173,7 +175,7 @@ class HTTPClient(object):
|
||||||
|
|
||||||
if 400 <= resp.status < 600:
|
if 400 <= resp.status < 600:
|
||||||
LOG.warn("Request returned failure status.")
|
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(
|
raise exceptions.from_response(
|
||||||
resp, error_json.get('faultstring'),
|
resp, error_json.get('faultstring'),
|
||||||
error_json.get('debuginfo'), method, url)
|
error_json.get('debuginfo'), method, url)
|
||||||
|
@ -303,7 +305,10 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||||
raise_exc=False, **kwargs)
|
raise_exc=False, **kwargs)
|
||||||
|
|
||||||
if 400 <= resp.status_code < 600:
|
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):
|
elif resp.status_code in (301, 302, 305):
|
||||||
# Redirected. Reissue the request to the new location.
|
# Redirected. Reissue the request to the new location.
|
||||||
location = resp.headers.get('location')
|
location = resp.headers.get('location')
|
||||||
|
|
|
@ -56,6 +56,9 @@ def from_response(response, message=None, traceback=None, method=None,
|
||||||
response.status_code = response.status
|
response.status_code = response.status
|
||||||
response.headers = {
|
response.headers = {
|
||||||
'Content-Type': response.getheader('content-type', "")}
|
'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}
|
response.json = lambda: {'error': error_body}
|
||||||
|
|
||||||
if (response.headers['Content-Type'].startswith('text/') and
|
if (response.headers['Content-Type'].startswith('text/') and
|
||||||
|
|
|
@ -22,6 +22,17 @@ from magnumclient import exceptions as exc
|
||||||
from magnumclient.tests import utils
|
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
|
HTTP_CLASS = six.moves.http_client.HTTPConnection
|
||||||
HTTPS_CLASS = http.VerifiedHTTPSConnection
|
HTTPS_CLASS = http.VerifiedHTTPSConnection
|
||||||
DEFAULT_TIMEOUT = 600
|
DEFAULT_TIMEOUT = 600
|
||||||
|
@ -49,19 +60,8 @@ class HttpClientTest(utils.BaseTestCase):
|
||||||
url = client._make_connection_url('v1/resources')
|
url = client._make_connection_url('v1/resources')
|
||||||
self.assertEqual('/v1/resources', url)
|
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):
|
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'},
|
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||||
six.StringIO(error_body),
|
six.StringIO(error_body),
|
||||||
version=1,
|
version=1,
|
||||||
|
@ -77,7 +77,7 @@ class HttpClientTest(utils.BaseTestCase):
|
||||||
|
|
||||||
def test_server_exception_msg_only(self):
|
def test_server_exception_msg_only(self):
|
||||||
error_msg = 'test error msg'
|
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'},
|
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||||
six.StringIO(error_body),
|
six.StringIO(error_body),
|
||||||
version=1,
|
version=1,
|
||||||
|
@ -95,7 +95,7 @@ class HttpClientTest(utils.BaseTestCase):
|
||||||
error_msg = 'another test error'
|
error_msg = 'another test error'
|
||||||
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
error_trace = ("\"Traceback (most recent call last):\\n\\n "
|
||||||
"File \\\"/usr/local/lib/python2.7/...")
|
"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'},
|
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||||
six.StringIO(error_body),
|
six.StringIO(error_body),
|
||||||
version=1,
|
version=1,
|
||||||
|
@ -218,7 +218,7 @@ class HttpClientTest(utils.BaseTestCase):
|
||||||
self.assertEqual(expected, params)
|
self.assertEqual(expected, params)
|
||||||
|
|
||||||
def test_401_unauthorized_exception(self):
|
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'},
|
fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
|
||||||
six.StringIO(error_body),
|
six.StringIO(error_body),
|
||||||
version=1,
|
version=1,
|
||||||
|
@ -229,3 +229,43 @@ class HttpClientTest(utils.BaseTestCase):
|
||||||
|
|
||||||
self.assertRaises(exc.Unauthorized, client.json_request,
|
self.assertRaises(exc.Unauthorized, client.json_request,
|
||||||
'GET', '/v1/resources')
|
'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))
|
||||||
|
|
|
@ -158,3 +158,23 @@ class TestCase(testtools.TestCase):
|
||||||
sys.stderr.close()
|
sys.stderr.close()
|
||||||
sys.stderr = orig_stderr
|
sys.stderr = orig_stderr
|
||||||
return (stdout, 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)
|
||||||
|
|
Loading…
Reference in New Issue