summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJianing YANG <jianingy@unitedstack.com>2013-07-22 15:15:57 +0800
committerJianing YANG <jianingy@unitedstack.com>2013-07-26 20:52:04 +0800
commit2b83260feb910a72bceed93db2f4ebd59c692022 (patch)
treea64f545d461dcd4a49bf756ec662cf90749f7cee
parenta49cf4c148bfcb4cf878afc13325f2ffcf219992 (diff)
downloadpython-heatclient-2b83260feb910a72bceed93db2f4ebd59c692022.tar.gz
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
-rw-r--r--heatclient/exc.py31
-rw-r--r--heatclient/shell.py5
-rw-r--r--heatclient/tests/fakes.py29
-rw-r--r--heatclient/tests/test_shell.py102
4 files changed, 167 insertions, 0 deletions
diff --git a/heatclient/exc.py b/heatclient/exc.py
index c90b0fc..6996bf2 100644
--- a/heatclient/exc.py
+++ b/heatclient/exc.py
@@ -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
diff --git a/heatclient/shell.py b/heatclient/shell.py
index 27e6a74..feaa329 100644
--- a/heatclient/shell.py
+++ b/heatclient/shell.py
@@ -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
diff --git a/heatclient/tests/fakes.py b/heatclient/tests/fakes.py
index 19ae437..657ad80 100644
--- a/heatclient/tests/fakes.py
+++ b/heatclient/tests/fakes.py
@@ -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',
diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py
index 3043f96..af3c69d 100644
--- a/heatclient/tests/test_shell.py
+++ b/heatclient/tests/test_shell.py
@@ -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": {