Parse error object (in json format) returned by heat-api

With this fix, heatclient will display a clear error message when
encounter an server-side error. Additionally, the corresponding
traceback will be displayed when "--verbose" is set.

Change-Id: I99b828465f61478a3c63fcf549d044a62523be1f
This commit is contained in:
Jianing YANG 2013-07-22 15:15:57 +08:00
parent a49cf4c148
commit 2b83260feb
4 changed files with 167 additions and 0 deletions

View File

@ -12,6 +12,13 @@
import sys
verbose = 0
try:
import json
except ImportError:
import simplejson as json
class BaseException(Exception):
"""An error occurred."""
@ -38,6 +45,30 @@ class HTTPException(BaseException):
"""Base exception for all HTTP-derived exceptions."""
code = 'N/A'
def __init__(self, message=None):
super(HTTPException, self).__init__(message)
try:
self.error = json.loads(message)
if 'error' not in self.error:
raise KeyError('Key "error" not exists')
except KeyError:
# NOTE(jianingy): If key 'error' happens not exist,
# self.message becomes no sense. In this case, we
# return doc of current exception class instead.
self.error = {'error':
{'message': self.__class__.__doc__}}
except Exception:
self.error = {'error':
{'message': self.message or self.__class__.__doc__}}
def __str__(self):
message = self.error['error'].get('message', 'Internal Error')
if verbose:
traceback = self.error['error'].get('traceback', '')
return 'ERROR: %s\n%s' % (message, traceback)
else:
return 'ERROR: %s' % message
class HTTPMultipleChoices(HTTPException):
code = 300

View File

@ -236,11 +236,16 @@ class HeatShell(object):
httplib2.debuglevel = 1
def _setup_verbose(self, verbose):
if verbose:
exc.verbose = 1
def main(self, argv):
# Parse args once to find version
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
self._setup_debugging(options.debug)
self._setup_verbose(options.verbose)
# build available subcommands based on version
api_version = options.heat_api_version

View File

@ -1,5 +1,6 @@
import json
from heatclient import exc
from heatclient.v1 import client as v1client
from keystoneclient.v2_0 import client as ksclient
@ -35,6 +36,34 @@ def script_heat_list():
resp_dict))
def script_heat_normal_error():
resp_dict = {
"explanation": "The resource could not be found.",
"code": 404,
"error": {
"message": "The Stack (bad) could not be found.",
"type": "StackNotFound",
"traceback": "",
},
"title": "Not Found"
}
resp = FakeHTTPResponse(400,
'The resource could not be found',
{'content-type': 'application/json'},
json.dumps(resp_dict))
v1client.Client.json_request('GET', '/stacks/bad').AndRaise(
exc.from_response(resp, json.dumps(resp_dict)))
def script_heat_error(resp_string):
resp = FakeHTTPResponse(400,
'The resource could not be found',
{'content-type': 'application/json'},
resp_string)
v1client.Client.json_request('GET', '/stacks/bad').AndRaise(
exc.from_response(resp, resp_string))
def fake_headers():
return {'X-Auth-Token': 'abcd1234',
'Content-Type': 'application/json',

View File

@ -272,6 +272,108 @@ class ShellTest(TestCase):
for r in required:
self.assertRegexpMatches(list_text, r)
def test_parsable_error(self):
message = "The Stack (bad) could not be found."
resp_dict = {
"explanation": "The resource could not be found.",
"code": 404,
"error": {
"message": message,
"type": "StackNotFound",
"traceback": "",
},
"title": "Not Found"
}
fakes.script_keystone_client()
fakes.script_heat_error(json.dumps(resp_dict))
self.m.ReplayAll()
try:
self.shell("stack-show bad")
except exc.HTTPException as e:
self.assertEqual(str(e), "ERROR: " + message)
def test_parsable_verbose(self):
message = "The Stack (bad) could not be found."
resp_dict = {
"explanation": "The resource could not be found.",
"code": 404,
"error": {
"message": message,
"type": "StackNotFound",
"traceback": "<TRACEBACK>",
},
"title": "Not Found"
}
fakes.script_keystone_client()
fakes.script_heat_error(json.dumps(resp_dict))
self.m.ReplayAll()
try:
exc.verbose = 1
self.shell("stack-show bad")
except exc.HTTPException as e:
expect = 'ERROR: The Stack (bad) could not be found.\n<TRACEBACK>'
self.assertEqual(str(e), expect)
def test_parsable_malformed_error(self):
invalid_json = "ERROR: {Invalid JSON Error."
fakes.script_keystone_client()
fakes.script_heat_error(invalid_json)
self.m.ReplayAll()
try:
self.shell("stack-show bad")
except exc.HTTPException as e:
self.assertEqual(str(e), "ERROR: " + invalid_json)
def test_parsable_malformed_error_missing_message(self):
missing_message = {
"explanation": "The resource could not be found.",
"code": 404,
"error": {
"type": "StackNotFound",
"traceback": "",
},
"title": "Not Found"
}
fakes.script_keystone_client()
fakes.script_heat_error(json.dumps(missing_message))
self.m.ReplayAll()
try:
self.shell("stack-show bad")
except exc.HTTPException as e:
self.assertEqual(str(e), "ERROR: Internal Error")
def test_parsable_malformed_error_missing_traceback(self):
message = "The Stack (bad) could not be found."
resp_dict = {
"explanation": "The resource could not be found.",
"code": 404,
"error": {
"message": message,
"type": "StackNotFound",
},
"title": "Not Found"
}
fakes.script_keystone_client()
fakes.script_heat_error(json.dumps(resp_dict))
self.m.ReplayAll()
try:
exc.verbose = 1
self.shell("stack-show bad")
except exc.HTTPException as e:
self.assertEqual(str(e),
"ERROR: The Stack (bad) could not be found.\n")
def test_describe(self):
fakes.script_keystone_client()
resp_dict = {"stack": {