diff options
-rw-r--r-- | heatclient/exc.py | 31 | ||||
-rw-r--r-- | heatclient/shell.py | 5 | ||||
-rw-r--r-- | heatclient/tests/fakes.py | 29 | ||||
-rw-r--r-- | heatclient/tests/test_shell.py | 102 |
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": { |