diff --git a/zunclient/api_versions.py b/zunclient/api_versions.py index 5dc5dfe5..e932037d 100644 --- a/zunclient/api_versions.py +++ b/zunclient/api_versions.py @@ -31,7 +31,7 @@ if not LOG.handlers: HEADER_NAME = "OpenStack-API-Version" SERVICE_TYPE = "container" MIN_API_VERSION = '1.1' -MAX_API_VERSION = '1.21' +MAX_API_VERSION = '1.22' DEFAULT_API_VERSION = MAX_API_VERSION _SUBSTITUTIONS = {} diff --git a/zunclient/common/utils.py b/zunclient/common/utils.py index 5585b6dd..69b68e25 100644 --- a/zunclient/common/utils.py +++ b/zunclient/common/utils.py @@ -16,6 +16,7 @@ import json import os +import re from oslo_utils import netutils from six.moves.urllib import parse @@ -276,6 +277,52 @@ def parse_nets(ns): return nets +def parse_health(hc_str): + err_msg = ("Invalid healthcheck argument '%s'. healthcheck arguments" + " must be of the form --healthcheck , and the unit " + "of time is s(seconds), m(minutes), h(hours).") % hc_str + keys = ["cmd", "interval", "retries", "timeout"] + health_info = {} + for kv_str in hc_str[0].split(","): + try: + k, v = kv_str.split("=", 1) + k = k.strip() + v = v.strip() + except ValueError: + raise apiexec.CommandError(err_msg) + if k in keys: + if health_info.get(k): + raise apiexec.CommandError(err_msg) + elif k in ['interval', 'timeout']: + health_info[k] = _convert_healthcheck_para(v, err_msg) + elif k == "retries": + health_info[k] = int(v) + else: + health_info[k] = v + else: + raise apiexec.CommandError(err_msg) + return health_info + + +def _convert_healthcheck_para(time, err_msg): + int_pattern = '^\d+$' + time_pattern = '^\d+(s|m|h)$' + ret = 0 + if re.match(int_pattern, time): + ret = int(time) + elif re.match(time_pattern, time): + if time.endswith('s'): + ret = int(time.split('s')[0]) + elif time.endswith('m'): + ret = int(time.split('m')[0]) * 60 + elif time.endswith('h'): + ret = int(time.split('h')[0]) * 3600 + else: + raise apiexec.CommandError(err_msg) + return ret + + def normalise_file_path_to_url(path): if parse.urlparse(path).scheme: return path diff --git a/zunclient/osc/v1/containers.py b/zunclient/osc/v1/containers.py index aa649edb..e6541f3d 100644 --- a/zunclient/osc/v1/containers.py +++ b/zunclient/osc/v1/containers.py @@ -185,6 +185,19 @@ class CreateContainer(command.ShowOne): action='store_true', default=False, help='Give extended privileges to this container') + parser.add_argument( + '--healthcheck', + action='append', + default=[], + metavar='', + help='Specify a test cmd to perform to check that the container' + 'is healthy. ' + 'cmd: Command to run to check health. ' + 'interval: Time between running the check (|s|m|h)' + ' (default 0s). ' + 'retries: Consecutive failures needed to report unhealthy.' + 'timeout: Maximum time to allow one check to run (s|m|h)' + ' (default 0s).') return parser def take_action(self, parsed_args): @@ -218,6 +231,9 @@ class CreateContainer(command.ShowOne): opts['disk'] = parsed_args.disk opts['availability_zone'] = parsed_args.availability_zone opts['auto_heal'] = parsed_args.auto_heal + if parsed_args.healthcheck: + opts['healthcheck'] = \ + zun_utils.parse_health(parsed_args.healthcheck) opts = zun_utils.remove_null_parms(**opts) container = client.containers.create(**opts) @@ -811,6 +827,19 @@ class RunContainer(command.ShowOne): action='store_true', default=False, help='Give extended privileges to this container') + parser.add_argument( + '--healthcheck', + action='append', + default=[], + metavar='', + help='Specify a test cmd to perform to check that the container' + 'is healthy. ' + 'cmd: Command to run to check health. ' + 'interval: Time between running the check (s|m|h)' + ' (default 0s). ' + 'retries: Consecutive failures needed to report unhealthy.' + 'timeout: Maximum time to allow one check to run (s|m|h)' + ' (default 0s).') return parser def take_action(self, parsed_args): @@ -844,6 +873,9 @@ class RunContainer(command.ShowOne): opts['disk'] = parsed_args.disk opts['availability_zone'] = parsed_args.availability_zone opts['auto_heal'] = parsed_args.auto_heal + if parsed_args.healthcheck: + opts['healthcheck'] = \ + zun_utils.parse_health(parsed_args.healthcheck) opts = zun_utils.remove_null_parms(**opts) container = client.containers.run(**opts) diff --git a/zunclient/tests/unit/test_shell.py b/zunclient/tests/unit/test_shell.py index b90b1c2f..cb35d9e1 100644 --- a/zunclient/tests/unit/test_shell.py +++ b/zunclient/tests/unit/test_shell.py @@ -246,7 +246,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.21')) + version=api_versions.APIVersion('1.22')) def test_main_option_region(self): self.make_env() @@ -274,7 +274,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.21')) + version=api_versions.APIVersion('1.22')) @mock.patch('zunclient.client.Client') def test_main_endpoint_internal(self, mock_client): @@ -288,7 +288,7 @@ class ShellTest(utils.TestCase): project_domain_id='', project_domain_name='', user_domain_id='', user_domain_name='', profile=None, endpoint_override=None, insecure=False, cacert=None, - version=api_versions.APIVersion('1.21')) + version=api_versions.APIVersion('1.22')) class ShellTestKeystoneV3(ShellTest): @@ -320,4 +320,4 @@ class ShellTestKeystoneV3(ShellTest): user_domain_id='', user_domain_name='Default', endpoint_override=None, insecure=False, profile=None, cacert=None, - version=api_versions.APIVersion('1.21')) + version=api_versions.APIVersion('1.22')) diff --git a/zunclient/tests/unit/v1/test_containers.py b/zunclient/tests/unit/v1/test_containers.py index f56c3c0f..a56460e6 100644 --- a/zunclient/tests/unit/v1/test_containers.py +++ b/zunclient/tests/unit/v1/test_containers.py @@ -38,6 +38,7 @@ CONTAINER1 = {'id': '1234', 'disk': '20', 'auto_heal': False, 'privileged': False, + 'healthcheck': {} } CONTAINER2 = {'id': '1235', @@ -59,6 +60,7 @@ CONTAINER2 = {'id': '1235', 'hostname': 'testhost', 'auto_heal': False, 'privileged': True, + 'healthcheck': {} } NETWORK1 = {'net_id': '99e90853-e1fd-4c57-a116-9e335deaa592', diff --git a/zunclient/v1/containers.py b/zunclient/v1/containers.py index a62d6e8a..ab54de16 100644 --- a/zunclient/v1/containers.py +++ b/zunclient/v1/containers.py @@ -25,7 +25,8 @@ CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory', 'restart_policy', 'interactive', 'image_driver', 'security_groups', 'hints', 'nets', 'auto_remove', 'runtime', 'hostname', 'mounts', 'disk', - 'availability_zone', 'auto_heal', 'privileged'] + 'availability_zone', 'auto_heal', 'privileged', + 'healthcheck'] class Container(base.Resource): diff --git a/zunclient/v1/containers_shell.py b/zunclient/v1/containers_shell.py index 9b9f1c5a..3e4c01d5 100644 --- a/zunclient/v1/containers_shell.py +++ b/zunclient/v1/containers_shell.py @@ -153,6 +153,18 @@ def _show_container(container): action='store_true', default=False, help='Give extended privileges to this container') +@utils.arg('--healthcheck', + action='append', + default=[], + metavar='', + help='Specify a test cmd to perform to check that the container' + 'is healthy. ' + 'cmd: Command to run to check health. ' + 'interval: Time between running the check (s|m|h)' + ' (default 0s). ' + 'retries: Consecutive failures needed to report unhealthy. ' + 'timeout: Maximum time to allow one check to run (s|m|h)' + ' (default 0s).') def do_create(cs, args): """Create a container.""" opts = {} @@ -175,6 +187,8 @@ def do_create(cs, args): opts['availability_zone'] = args.availability_zone opts['auto_heal'] = args.auto_heal opts['command'] = args.command + if args.healthcheck: + opts['healthcheck'] = zun_utils.parse_health(args.healthcheck) if args.security_group: opts['security_groups'] = args.security_group @@ -647,6 +661,18 @@ def do_kill(cs, args): action='store_true', default=False, help='Give extended privileges to this container') +@utils.arg('--healthcheck', + action='append', + default=[], + metavar='', + help='Specify a test cmd to perform to check that the container' + 'is healthy. ' + 'cmd: Command to run to check health. ' + 'interval: Time between running the check (s|m|h)' + ' (default 0s). ' + 'retries: Consecutive failures needed to report unhealthy. ' + 'timeout: Maximum time to allow one check to run (s|m|h)' + ' (default 0s).') def do_run(cs, args): """Run a command in a new container.""" opts = {} @@ -669,6 +695,8 @@ def do_run(cs, args): opts['availability_zone'] = args.availability_zone opts['auto_heal'] = args.auto_heal opts['command'] = args.command + if args.healthcheck: + opts['healthcheck'] = zun_utils.parse_health(args.healthcheck) if args.security_group: opts['security_groups'] = args.security_group