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:
@@ -12,6 +12,13 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
verbose = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
|
||||||
class BaseException(Exception):
|
class BaseException(Exception):
|
||||||
"""An error occurred."""
|
"""An error occurred."""
|
||||||
@@ -38,6 +45,30 @@ class HTTPException(BaseException):
|
|||||||
"""Base exception for all HTTP-derived exceptions."""
|
"""Base exception for all HTTP-derived exceptions."""
|
||||||
code = 'N/A'
|
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):
|
class HTTPMultipleChoices(HTTPException):
|
||||||
code = 300
|
code = 300
|
||||||
|
@@ -236,11 +236,16 @@ class HeatShell(object):
|
|||||||
|
|
||||||
httplib2.debuglevel = 1
|
httplib2.debuglevel = 1
|
||||||
|
|
||||||
|
def _setup_verbose(self, verbose):
|
||||||
|
if verbose:
|
||||||
|
exc.verbose = 1
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
# Parse args once to find version
|
# Parse args once to find version
|
||||||
parser = self.get_base_parser()
|
parser = self.get_base_parser()
|
||||||
(options, args) = parser.parse_known_args(argv)
|
(options, args) = parser.parse_known_args(argv)
|
||||||
self._setup_debugging(options.debug)
|
self._setup_debugging(options.debug)
|
||||||
|
self._setup_verbose(options.verbose)
|
||||||
|
|
||||||
# build available subcommands based on version
|
# build available subcommands based on version
|
||||||
api_version = options.heat_api_version
|
api_version = options.heat_api_version
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
from heatclient import exc
|
||||||
from heatclient.v1 import client as v1client
|
from heatclient.v1 import client as v1client
|
||||||
from keystoneclient.v2_0 import client as ksclient
|
from keystoneclient.v2_0 import client as ksclient
|
||||||
|
|
||||||
@@ -35,6 +36,34 @@ def script_heat_list():
|
|||||||
resp_dict))
|
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():
|
def fake_headers():
|
||||||
return {'X-Auth-Token': 'abcd1234',
|
return {'X-Auth-Token': 'abcd1234',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@@ -272,6 +272,108 @@ class ShellTest(TestCase):
|
|||||||
for r in required:
|
for r in required:
|
||||||
self.assertRegexpMatches(list_text, r)
|
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):
|
def test_describe(self):
|
||||||
fakes.script_keystone_client()
|
fakes.script_keystone_client()
|
||||||
resp_dict = {"stack": {
|
resp_dict = {"stack": {
|
||||||
|
Reference in New Issue
Block a user