diff --git a/tests/test_zdd.py b/tests/test_zdd.py index 88f7a25d..fdd55b69 100644 --- a/tests/test_zdd.py +++ b/tests/test_zdd.py @@ -2,6 +2,7 @@ import zdd import mock import json +from zdd_exceptions import * class Arguments: @@ -332,4 +333,4 @@ def test_complete_cur_exception(self): args = Arguments() args.complete_cur = True - self.assertRaises(Exception, zdd.do_zdd, args) + self.assertRaises(InvalidArgException, zdd.do_zdd, args) diff --git a/zdd.py b/zdd.py index 0541bf49..118af559 100755 --- a/zdd.py +++ b/zdd.py @@ -15,6 +15,8 @@ import sys import subprocess from utils import * +from zdd_exceptions import * +import traceback logger = logging.getLogger('zdd') @@ -57,8 +59,12 @@ def query_yes_no(question, default="yes"): def marathon_get_request(args, path): url = args.marathon + path - response = requests.get(url, auth=get_marathon_auth_params(args)) - response.raise_for_status() + try: + response = requests.get(url, auth=get_marathon_auth_params(args)) + response.raise_for_status() + except requests.exceptions.RequestException: + raise MarathonEndpointException( + "Error while querying marathon", url, traceback.format_exc()) return response @@ -273,7 +279,13 @@ def max_wait_not_exceeded(max_wait, timestamp): def find_tasks_to_kill(args, new_app, old_app, timestamp): marathon_lb_urls = get_marathon_lb_urls(args) haproxy_count = len(marathon_lb_urls) - listeners = fetch_app_listeners(new_app, marathon_lb_urls) + try: + listeners = fetch_app_listeners(new_app, marathon_lb_urls) + except requests.exceptions.RequestException: + raise MarathonLbEndpointException( + "Error while querying Marathon-LB", + marathon_lb_urls, + traceback.format_exc()) while max_wait_not_exceeded(args.max_wait, timestamp): time.sleep(args.step_delay) @@ -411,9 +423,13 @@ def safe_delete_app(args, app, new_app): def delete_marathon_app(args, app): url = args.marathon + '/v2/apps' + app['id'] - response = requests.delete(url, - auth=get_marathon_auth_params(args)) - response.raise_for_status() + try: + response = requests.delete(url, + auth=get_marathon_auth_params(args)) + response.raise_for_status() + except requests.exceptions.RequestException: + raise AppDeleteException( + "Error while deleting the app", url, traceback.format_exc()) return response @@ -421,9 +437,14 @@ def kill_marathon_tasks(args, ids): data = json.dumps({'ids': ids}) url = args.marathon + "/v2/tasks/delete?scale=true" headers = {'Content-Type': 'application/json'} - response = requests.post(url, headers=headers, data=data, - auth=get_marathon_auth_params(args)) - response.raise_for_status() + try: + response = requests.post(url, headers=headers, data=data, + auth=get_marathon_auth_params(args)) + response.raise_for_status() + except requests.exceptions.RequestException: + # This is App Scale Down, so raising AppScale Exception + raise AppScaleException( + "Error while scaling the app", url, data, traceback.format_exc()) return response @@ -431,11 +452,14 @@ def scale_marathon_app_instances(args, app, instances): url = args.marathon + "/v2/apps" + app['id'] data = json.dumps({'instances': instances}) headers = {'Content-Type': 'application/json'} - - response = requests.put(url, headers=headers, data=data, - auth=get_marathon_auth_params(args)) - - response.raise_for_status() + try: + response = requests.put(url, headers=headers, data=data, + auth=get_marathon_auth_params(args)) + response.raise_for_status() + except requests.exceptions.RequestException: + # This is App Scale Up, so raising AppScale Exception + raise AppScaleException( + "Error while scaling the app", url, data, traceback.format_exc()) return response @@ -443,9 +467,13 @@ def deploy_marathon_app(args, app): url = args.marathon + "/v2/apps" data = json.dumps(app) headers = {'Content-Type': 'application/json'} - response = requests.post(url, headers=headers, data=data, - auth=get_marathon_auth_params(args)) - response.raise_for_status() + try: + response = requests.post(url, headers=headers, data=data, + auth=get_marathon_auth_params(args)) + response.raise_for_status() + except requests.exceptions.RequestException: + raise AppCreateException( + "Error while creating the app", url, data, traceback.format_exc()) return response @@ -469,16 +497,20 @@ def set_service_port(app, servicePort): def validate_app(app): if app['id'] is None: - raise Exception("App doesn't contain a valid App ID") + raise MissingFieldException("App doesn't contain a valid App ID", + 'id') if 'labels' not in app: - raise Exception("No labels found. Please define the" - "HAPROXY_DEPLOYMENT_GROUP label") + raise MissingFieldException("No labels found. Please define the" + " HAPROXY_DEPLOYMENT_GROUP label", + 'label') if 'HAPROXY_DEPLOYMENT_GROUP' not in app['labels']: - raise Exception("Please define the " - "HAPROXY_DEPLOYMENT_GROUP label") + raise MissingFieldException("Please define the " + "HAPROXY_DEPLOYMENT_GROUP label", + 'HAPROXY_DEPLOYMENT_GROUP') if 'HAPROXY_DEPLOYMENT_ALT_PORT' not in app['labels']: - raise Exception("Please define the " - "HAPROXY_DEPLOYMENT_ALT_PORT label") + raise MissingFieldException("Please define the " + "HAPROXY_DEPLOYMENT_ALT_PORT label", + 'HAPROXY_DEPLOYMENT_ALT_PORT') def set_app_ids(app, colour): @@ -621,12 +653,12 @@ def do_zdd(args, out=sys.stdout): previous_deploys = fetch_previous_deploys(args, app) if len(previous_deploys) > 1: - # There is a stuck deploy + # There is a stuck deploy or hybrid deploy return safe_resume_deploy(args, previous_deploys) if args.complete_cur or args.complete_prev: - raise Exception("Cannot use --complete-cur, --complete-prev" - " flags when config is not hybrid") + raise InvalidArgException("Cannot use --complete-cur, --complete-prev" + " flags when config is not hybrid") new_app = prepare_deploy(args, previous_deploys, app) @@ -756,7 +788,19 @@ def process_arguments(): set_request_retries() setup_logging(logger, args.syslog_socket, args.log_format, args.log_level) - if do_zdd(args): - sys.exit(0) - else: - sys.exit(1) + try: + if do_zdd(args): + sys.exit(0) + else: + sys.exit(1) + except Exception as e: + if hasattr(e, 'zdd_exit_status'): + if hasattr(e, 'error'): + logger.exception(str(e.error)) + else: + logger.exception(traceback.print_exc()) + sys.exit(e.zdd_exit_status) + else: + # For Unknown Exceptions + logger.exception(traceback.print_exc()) + sys.exit(2) diff --git a/zdd_exceptions.py b/zdd_exceptions.py new file mode 100644 index 00000000..db09361d --- /dev/null +++ b/zdd_exceptions.py @@ -0,0 +1,81 @@ +""" Exit Status 1 is already used in the script. + Zdd returns with exit status 1 when app is not force + deleted either through argument or through prompt. + Exit Status 2 is used for Unknown Exceptions. +""" + + +class InvalidArgException(Exception): + """ This exception indicates invalid combination of arguments + passed to zdd""" + def __init__(self, msg): + super(InvalidArgException, self).__init__(msg) + self.error = msg + self.zdd_exit_status = 3 + + +class MissingFieldException(Exception): + """ This exception indicates required fields which are missing + in JSON payload passed to zdd""" + def __init__(self, msg, field): + super(MissingFieldException, self).__init__(msg) + self.error = msg + self.missing_field = field + self.zdd_exit_status = 4 + + +class MarathonLbEndpointException(Exception): + """ This excaption indicates issue with one of the marathonlb + endpoints specified as argument to Zdd""" + def __init__(self, msg, url, error): + super(MarathonLbEndpointException, self).__init__(msg) + self.msg = msg + self.url = url + self.error = error + self.zdd_exit_status = 5 + + +class MarathonEndpointException(Exception): + """ This excaption indicates issue with marathon endpoint + specified as argument to Zdd""" + def __init__(self, msg, url, error): + super(MarathonEndpointException, self).__init__(msg) + self.msg = msg + self.url = url + self.error = error + self.zdd_exit_status = 6 + + +class AppCreateException(Exception): + """ This exception indicates there was a error while creating the + new App and hence it was not created.""" + def __init__(self, msg, url, payload, error): + super(AppCreateException, self).__init__(msg) + self.msg = msg + self.error = error + self.url = url + self.payload = payload + self.zdd_exit_status = 7 + + +class AppDeleteException(Exception): + """ This exception indicates there was a error while deleting the + old App and hence it was not deleted """ + def __init__(self, msg, url, appid, error): + super(AppDeleteException, self).__init__(msg) + self.msg = msg + self.error = error + self.url = url + self.zdd_exit_status = 8 + + +class AppScaleException(Exception): + """ This exception indicated there was a error while either scaling up + new app or while scaling down old app""" + def __init__(self, msg, url, payload, error): + super(AppScaleException, self).__init__(msg) + self.msg = msg + self.error = error + self.url = url + self.payload = payload + self.zdd_exit_status = 9