Let CLI print exception traceback from 'debuginfo'
Response 'debuginfo' is used to carry server traceback in case of error. If 'debuginfo' is not empty, append it to exception message, so traceback will be printed together with message. Change-Id: Id7900b64ec7fc9f906253ef0e2d754c2baa76067 Closes-Bug: #1255590
This commit is contained in:
parent
8d40327e6b
commit
01c178de83
ironicclient
@ -120,15 +120,17 @@ class HTTPClient(object):
|
||||
base_url = _args[2]
|
||||
return '%s/%s' % (base_url.rstrip('/'), url.lstrip('/'))
|
||||
|
||||
def _extract_error_message(self, body):
|
||||
def _extract_error_json(self, body):
|
||||
error_json = {}
|
||||
try:
|
||||
body_json = json.loads(body)
|
||||
if 'error_message' in body_json:
|
||||
body_json = json.loads(body_json['error_message'])
|
||||
if 'faultstring' in body_json:
|
||||
return body_json['faultstring']
|
||||
raw_msg = body_json['error_message']
|
||||
error_json = json.loads(raw_msg)
|
||||
except ValueError:
|
||||
pass
|
||||
return {}
|
||||
|
||||
return error_json
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
@ -172,8 +174,10 @@ class HTTPClient(object):
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
LOG.warn("Request returned failure status.")
|
||||
err_msg = self._extract_error_message(body_str)
|
||||
raise exc.from_response(resp, err_msg)
|
||||
error_json = self._extract_error_json(body_str)
|
||||
raise exc.from_response(resp,
|
||||
error_json.get('faultstring'),
|
||||
error_json.get('debuginfo'))
|
||||
elif resp.status in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self._http_request(resp['location'], method, **kwargs)
|
||||
|
@ -42,12 +42,18 @@ class HTTPException(ClientException):
|
||||
"""Base exception for all HTTP-derived exceptions."""
|
||||
code = 'N/A'
|
||||
|
||||
def __init__(self, details=None):
|
||||
def __init__(self, details=None, server_traceback=None):
|
||||
self.details = details
|
||||
self.server_traceback = server_traceback
|
||||
|
||||
def __str__(self):
|
||||
return self.details or "%s (HTTP %s)" % (self.__class__.__name__,
|
||||
self.code)
|
||||
msg = "%s (HTTP %s)" % (self.__class__.__name__,
|
||||
self.code)
|
||||
if self.details:
|
||||
msg = self.details
|
||||
if self.server_traceback:
|
||||
msg += "\n%s" % self.server_traceback
|
||||
return msg
|
||||
|
||||
|
||||
class HTTPMultipleChoices(HTTPException):
|
||||
@ -148,10 +154,10 @@ for obj_name in dir(sys.modules[__name__]):
|
||||
_code_map[obj.code] = obj
|
||||
|
||||
|
||||
def from_response(response, error=None):
|
||||
def from_response(response, message=None, traceback=None):
|
||||
"""Return an instance of an HTTPException based on httplib response."""
|
||||
cls = _code_map.get(response.status, HTTPException)
|
||||
return cls(error)
|
||||
return cls(message, traceback)
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
|
@ -13,7 +13,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import six
|
||||
|
||||
from ironicclient.common import http
|
||||
from ironicclient import exc
|
||||
from ironicclient.tests import utils
|
||||
|
||||
|
||||
@ -38,3 +42,63 @@ class HttpClientTest(utils.BaseTestCase):
|
||||
client = http.HTTPClient('http://localhost')
|
||||
url = client._make_connection_url('v1/resources')
|
||||
self.assertEqual(url, '/v1/resources')
|
||||
|
||||
@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()
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = \
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp)
|
||||
|
||||
error = self.assertRaises(exc.HTTPInternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual('HTTPInternalServerError (HTTP 500)', str(error))
|
||||
|
||||
def test_server_exception_msg_only(self):
|
||||
error_msg = 'test error msg'
|
||||
error_body = self._get_error_body(error_msg)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = \
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp)
|
||||
|
||||
error = self.assertRaises(exc.HTTPInternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual(error_msg, str(error))
|
||||
|
||||
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 = self._get_error_body(error_msg, error_trace)
|
||||
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
|
||||
six.StringIO(error_body),
|
||||
version=1,
|
||||
status=500)
|
||||
client = http.HTTPClient('http://localhost/')
|
||||
client.get_connection = \
|
||||
lambda *a, **kw: utils.FakeConnection(fake_resp)
|
||||
|
||||
error = self.assertRaises(exc.HTTPInternalServerError,
|
||||
client.json_request,
|
||||
'GET', '/v1/resources')
|
||||
self.assertEqual(error_msg + "\n" + error_trace, str(error))
|
||||
|
@ -49,13 +49,32 @@ class FakeAPI(object):
|
||||
return FakeResponse(response[0]), response[1]
|
||||
|
||||
|
||||
class FakeConnection(object):
|
||||
def __init__(self, response=None):
|
||||
self._response = response
|
||||
self._last_request = None
|
||||
|
||||
def request(self, method, conn_url, **kwargs):
|
||||
self._last_request = (method, conn_url, kwargs)
|
||||
|
||||
def setresponse(self, response):
|
||||
self._response = response
|
||||
|
||||
def getresponse(self):
|
||||
return self._response
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, headers, body=None, version=None):
|
||||
def __init__(self, headers, body=None, version=None, status=None,
|
||||
reason=None):
|
||||
""":param headers: dict representing HTTP response headers
|
||||
:param body: file-like object
|
||||
"""
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
self.version = version
|
||||
self.status = status
|
||||
self.reason = reason
|
||||
|
||||
def getheaders(self):
|
||||
return copy.deepcopy(self.headers).items()
|
||||
|
Loading…
x
Reference in New Issue
Block a user