Updated Branches: refs/heads/trunk 741746124 -> 85fcfb1ae
AMBARI-3244. Ambari Client improve errors handling. (Andrew Onischuk via mahadev) Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/85fcfb1a Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/85fcfb1a Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/85fcfb1a Branch: refs/heads/trunk Commit: 85fcfb1aeb2c6ce9cea30f6e2be25fb7858bcd18 Parents: 7417461 Author: Mahadev Konar <[email protected]> Authored: Tue Sep 17 13:14:14 2013 -0700 Committer: Mahadev Konar <[email protected]> Committed: Tue Sep 17 13:14:14 2013 -0700 ---------------------------------------------------------------------- .../main/python/ambari_client/core/errors.py | 88 +++++++++++++------- .../python/ambari_client/core/rest_resource.py | 23 +++-- .../src/main/python/ambari_client/model/host.py | 2 +- .../main/python/ambari_client/model/status.py | 7 +- .../main/python/ambari_client/model/utils.py | 30 +++++-- .../src/test/python/TestAmbariClient.py | 26 +++++- 6 files changed, 121 insertions(+), 55 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/main/python/ambari_client/core/errors.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/main/python/ambari_client/core/errors.py b/ambari-client/src/main/python/ambari_client/core/errors.py index 65515ed..c691241 100755 --- a/ambari-client/src/main/python/ambari_client/core/errors.py +++ b/ambari-client/src/main/python/ambari_client/core/errors.py @@ -17,36 +17,62 @@ class ResourceError(Exception): - def __init__(self, msg=None, http_code=None, response=None): - self.msg = msg or '' - self.status_code = http_code - self.response = response - Exception.__init__(self) - - def _get_message(self): - return self.msg - def _set_message(self, msg): - self.msg = msg or '' - message = property(_get_message, _set_message) - - def __str__(self): - if self.msg: - return self.msg - try: - return self._fmt % self.__dict__ - except (NameError, ValueError, KeyError), e: - return 'exception %s: %s' \ - % (self.__class__.__name__, str(e)) - -class ResourceNotFound(ResourceError): - """Exception raised when no resource was found. + def __init__(self, response, resource_root=None): """ + Create new exception based on not successful server response + @param response: StatusModel response + @param resource_root: The resource which sent an error response + """ + self.response = response + self.resource_root = resource_root + Exception.__init__(self) + + def get_message(self): + """ Get an error message """ + return self.response.get_message() + + def get_status_code(self): + """ Get a status(error) code from the server response """ + return self.response.status + + def get_reponse(self): + """ StatusModel object """ + return self.reponse + + def get_root_resource(self): + """ AmbariClient object """ + return self.resource_root + + def __str__(self): + if self.get_message(): + return "exception: %s. %s" % (self.response.status, self.get_message()) + try: + return self._fmt % self.__dict__ + except (NameError, ValueError, KeyError), e: + return 'exception %s: %s' \ + % (self.__class__.__name__, str(e)) -class RequestError(Exception): - """Exception for incorrect request """ - -class Unauthorized(ResourceError): - """Exception when an authorization is required """ - -class RequestFailed(ResourceError): - """Exception for unexpected HTTP error """ \ No newline at end of file +class ResourceConflict(ResourceError): + """ 409 status code """ +class ResourceNotFound(ResourceError): + """ 404 status code """ +class BadRequest(ResourceError): + """ 400 status code """ +class AuthorizationError(ResourceError): + """ 401 status code """ +class ForbiddenError(ResourceError): + """ 403 status code """ +class InternalServerError(ResourceError): + """ 500 status code """ +class MethodNotAllowed(ResourceError): + """ 405 status code """ +class UnknownServerError(ResourceError): + """ Received other response code """ + +_exceptions_to_codes = { 409:ResourceConflict, + 404:ResourceNotFound, + 400:BadRequest, + 401:AuthorizationError, + 403:ForbiddenError, + 500:InternalServerError, + 405:MethodNotAllowed } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/main/python/ambari_client/core/rest_resource.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/main/python/ambari_client/core/rest_resource.py b/ambari-client/src/main/python/ambari_client/core/rest_resource.py index f7e2e23..532fc53 100755 --- a/ambari-client/src/main/python/ambari_client/core/rest_resource.py +++ b/ambari-client/src/main/python/ambari_client/core/rest_resource.py @@ -64,22 +64,19 @@ class RestResource(object): LOG.debug ("RESPONSE from the REST request >>>>>>> \n" + str(resp)) LOG.debug ("\n===========================================================") #take care of REST calls with no response - if not resp and (code != 200 and code != 201): - LOG.error("Command '%s %s' failed with error %s" % (http_method, path, code)) - return {"status":code , "message":"Command '%s %s' failed with error %s" % (http_method, path, code)} - #raise Exception("Command '%s %s' failed with error %s" % (http_method, path, code)) - if resp and (code == 404 or code == 405 or code == 500): - LOG.error("Command '%s %s' failed with error %s" % (http_method, path, code)) - return {"status":code , "message":"Command '%s %s' failed with error %s" % (http_method, path, code)} - #raise Exception("Command '%s %s' failed with error %s" % (http_method, path, code)) + try: - if (code == 200 or code == 201) and not resp: - return {"status":code} + isOK = (code == 200 or code == 201 or code == 202) + + if isOK and not resp: + json_dict = {"status":code} + else: json_dict = json.loads(resp) - return json_dict + + return json_dict except Exception, ex: - LOG.error('JSON decode error: %s' % (resp,)) - raise ex + LOG.error("Command '%s %s' failed with error %s\n%s" % (http_method, path, code ,resp)) + return {"status":code , "message":"Command '%s %s' failed with error %s" % (http_method, path, code)} def get(self, path=None): http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/main/python/ambari_client/model/host.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/main/python/ambari_client/model/host.py b/ambari-client/src/main/python/ambari_client/model/host.py index 2a0e101..c5e94cb 100755 --- a/ambari-client/src/main/python/ambari_client/model/host.py +++ b/ambari-client/src/main/python/ambari_client/model/host.py @@ -79,7 +79,7 @@ def _create_host(root_resource, host_name, ip, rack_info=None): @param rack_info: Rack id. Default None @return: An HostModel object """ - host_list = ModelList([HostModel(host_name, ip, rack_info)]) + host_list = ModelList([HostModel(root_resource, host_name, ip, rack_info)]) return _create_hosts(root_resource, host_list) def _add_hosts(root_resource, cluster_name , host_list): http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/main/python/ambari_client/model/status.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/main/python/ambari_client/model/status.py b/ambari-client/src/main/python/ambari_client/model/status.py index 1eba35d..331a701 100755 --- a/ambari-client/src/main/python/ambari_client/model/status.py +++ b/ambari-client/src/main/python/ambari_client/model/status.py @@ -35,7 +35,7 @@ class StatusModel(BaseModel): utils.retain_self_helper(BaseModel, **locals()) def __str__(self): - return "<<StatusModel>> status = %s ; requestId = %s ;message = %s" % (self.status, self._get_id() , self._get_message()) + return "<<StatusModel>> status = %s ; requestId = %s ;message = %s" % (self.status, self._get_id() , self.get_message()) def get_bootstrap_path(self): return paths.BOOTSTRAP_PATH + '/' + self.requestId @@ -43,11 +43,14 @@ class StatusModel(BaseModel): def get_request_path(self): return paths.REQUEST_PATH % (self._get_id()) - def _get_message(self): + def get_message(self): if hasattr(self, 'message'): return self.message else: None + + def is_error(self): + return (self.status != 200 and self.status != 201 and self.status != 202) def _get_id(self): if hasattr(self, 'requestId') and self.requestId: http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/main/python/ambari_client/model/utils.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/main/python/ambari_client/model/utils.py b/ambari-client/src/main/python/ambari_client/model/utils.py index 4a7fa5e..38add26 100755 --- a/ambari-client/src/main/python/ambari_client/model/utils.py +++ b/ambari-client/src/main/python/ambari_client/model/utils.py @@ -17,7 +17,8 @@ import logging import sys -import unicodedata +import unicodedata +from ambari_client.core import errors LOG = logging.getLogger(__name__) @@ -28,9 +29,22 @@ ref_pkg_dic = {"ClusterModelRef":"ambari_client.model.cluster"} LIST_KEY = "items" class ModelUtils(object): + + @staticmethod + def _check_is_error(expected_class, model_dict, resource_root): + from ambari_client.model.status import StatusModel + + if model_dict.has_key("status"): + resp = ModelUtils.create_model(StatusModel, model_dict.copy(), resource_root, "NO_KEY", check_errors=False) + + if expected_class!=StatusModel or resp.is_error(): + if resp.status in errors._exceptions_to_codes: + raise errors._exceptions_to_codes[resp.status](resp, resource_root) + else: + raise errors.UnknownServerError(resp, resource_root) @staticmethod - def get_model_list(member_list_clss, member_cls, collection_dict, resource_root , RESOURCE_KEY_WORD): + def get_model_list(member_list_clss, member_cls, collection_dict, resource_root , RESOURCE_KEY_WORD, check_errors=True): """ create a model. @param member_list_clss : model_list class. @@ -40,6 +54,9 @@ class ModelUtils(object): @param RESOURCE_KEY_WORD : tsake subset of model_dict based on this key. @return: A ModelList object. """ + if check_errors: + ModelUtils._check_is_error(member_list_clss, collection_dict, resource_root) + #print locals() json_list = [] @@ -67,7 +84,7 @@ class ModelUtils(object): @staticmethod - def create_model(model_cls, model_dict, resource_root, RESOURCE_KEY_WORD, exception_cls=None): + def create_model(model_cls, model_dict, resource_root, RESOURCE_KEY_WORD, check_errors=True): """ create a model. @param model_cls : model class. @@ -76,6 +93,9 @@ class ModelUtils(object): @param RESOURCE_KEY_WORD : tsake subset of model_dict based on this key. @return: A model_cls object. """ + if check_errors: + ModelUtils._check_is_error(model_cls, model_dict, resource_root) + #print locals() rw_dict = { } LOG.debug ("model_dict = " + str(model_dict)) @@ -86,10 +106,6 @@ class ModelUtils(object): if isinstance(model_dict, dict) and model_dict.has_key("Requests"): model_dict = model_dict["Requests"] LOG.debug ("model_dict has Requests ;subset = %s" % (str(model_dict.items()))) - if isinstance(model_dict, dict) and model_dict.has_key("status") and exception_cls: - LOG.debug ("model_dict has status ,might be a exception from Ambari ;model_cls = %s ;exception_clss = %s" % - (str(model_cls), str(exception_cls))) - return ModelUtils.create_model(exception_cls, model_dict, resource_root, "NO_KEY") for k, v in model_dict.items(): http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/85fcfb1a/ambari-client/src/test/python/TestAmbariClient.py ---------------------------------------------------------------------- diff --git a/ambari-client/src/test/python/TestAmbariClient.py b/ambari-client/src/test/python/TestAmbariClient.py index 0fba169..2dcdfce 100755 --- a/ambari-client/src/test/python/TestAmbariClient.py +++ b/ambari-client/src/test/python/TestAmbariClient.py @@ -21,6 +21,7 @@ limitations under the License. from mock.mock import MagicMock, patch from ambari_client.ambari_api import AmbariClient +from ambari_client.core.errors import BadRequest import unittest @@ -167,7 +168,27 @@ class TestAmbariClient(unittest.TestCase): self.assertEqual(str(ganglia.state), "STARTED", "The ganglia service state should be fetched as STARTED") self.assertEqual(ganglia.clusterRef.cluster_name, cluster.cluster_name, "The clusterRef value for service should be fetched ") - + @patch("ambari_client.core.http_client.HttpClient") + def test_exceptions(self , http_client): + """ + Test exceptions from ambari.client.core.errors + """ + http_client_mock = MagicMock() + http_client.returned_obj = http_client_mock + mocked_code = "200" + mocked_content = "text/plain" + + http_client_mock.invoke.side_effect = http_client_invoke_side_effects + client = AmbariClient("localhost", 8080, "admin", "admin", version=1, client=http_client_mock) + cluster = client.get_cluster('test1') + + try: + cluster.delete_host('deleted_nonexistant_cluster') + self.fail('Exception should have been thrown!') + except BadRequest, ex: + self.assertEquals(str(ex), 'exception: 400. Attempted to add unknown hosts to a cluster. These hosts have not been registered with the server: dev05') + except Exception, ex: + self.fail('Wrong exception thrown!') @@ -208,3 +229,6 @@ def http_client_invoke_side_effects(*args, **kwargs): elif args[1] == "//clusters/test1/services/GANGLIA": mocked_response = open('json/get_service.json', 'r').read() return mocked_response, mocked_code , mocked_content + elif args[1] == "//clusters/test1/hosts/deleted_nonexistant_cluster": + mocked_response = open('json/error_adding_host.json', 'r').read() + return mocked_response, mocked_code , mocked_content
