From 5c2645f1ed7b9b48156e30719a38af02a66d55b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Armando=20Garc=C3=ADa=20Sancio?= Date: Sun, 22 Feb 2015 14:12:11 +0000 Subject: [PATCH] dcos-336 implements watching deployment --- bin/start_integration_test.sh | 7 ++-- dcos/api/marathon.py | 25 +++++++++++++++ dcos/cli/app/main.py | 60 +++++++++++++++++++++++++++++++++-- dcos/cli/config/main.py | 2 +- dcos/cli/help/main.py | 2 +- integrations/cli/test_app.py | 37 +++++++++++++++++---- 6 files changed, 121 insertions(+), 12 deletions(-) diff --git a/bin/start_integration_test.sh b/bin/start_integration_test.sh index dcace91..23726de 100755 --- a/bin/start_integration_test.sh +++ b/bin/start_integration_test.sh @@ -15,8 +15,11 @@ dpkg -l marathon mesos zookeeper | grep '^ii' # Start zookeeper /usr/share/zookeeper/bin/zkServer.sh start -# Start Mesos and redirect stdout to /dev/null -/usr/bin/mesos-local --num_slaves=2 --quiet & +# Start Mesos master +/etc/init.d/mesos-master start + +# Start Mesos slave +/etc/init.d/mesos-slave start # Start Marathon /etc/init.d/marathon start diff --git a/dcos/api/marathon.py b/dcos/api/marathon.py index e6a1a1f..832ae4f 100644 --- a/dcos/api/marathon.py +++ b/dcos/api/marathon.py @@ -327,6 +327,31 @@ class Client(object): else: return (None, self._response_to_error(response)) + def get_deployment(self, deployment_id): + """Returns a deployment. + + :param deployemnt_id: the id of the application to restart + :type deployemnt_id: str + :returns: a deployment + :rtype: (dict, Error) + """ + + url = self._create_url('v2/deployments') + + logger.info('Getting %r', url) + response = requests.get(url) + logger.info('Got (%r): %r', response.status_code, response.text) + + if _success(response.status_code): + deployment = next( + (deployment for deployment in response.json() + if deployment_id == deployment['id']), + None) + + return (deployment, None) + else: + return (None, self._response_to_error(response)) + def get_deployments(self, app_id=None): """Returns a list of deployments, optionally limited to an app. diff --git a/dcos/cli/app/main.py b/dcos/cli/app/main.py index 33e8748..2b7f9ff 100644 --- a/dcos/cli/app/main.py +++ b/dcos/cli/app/main.py @@ -4,6 +4,8 @@ Usage: dcos app deployment list [] dcos app deployment rollback dcos app deployment stop + dcos app deployment watch [--max-count=] [--interval=] + dcos app info dcos app list dcos app remove [--force] @@ -30,6 +32,7 @@ Options: application definition. --max-count= Maximum number of entries to try to fetch and return + --interval= Number of seconds to wait between actions Positional arguments: The application id @@ -42,6 +45,7 @@ Positional arguments: import json import os import sys +import time import docopt import pkg_resources @@ -63,13 +67,13 @@ def main(): version='dcos-app version {}'.format(constants.version)) if not args['app']: - emitter.publish(options.make_generic_usage_error(__doc__)) + emitter.publish(options.make_generic_usage_message(__doc__)) return 1 returncode, err = cmds.execute(_cmds(), args) if err is not None: emitter.publish(err) - emitter.publish(options.make_generic_usage_error(__doc__)) + emitter.publish(options.make_generic_usage_message(__doc__)) return 1 return returncode @@ -102,6 +106,11 @@ def _cmds(): arg_keys=[''], function=_deployment_stop), + cmds.Command( + hierarchy=['deployment', 'watch'], + arg_keys=['', '--max-count', '--interval'], + function=_deployment_watch), + cmds.Command(hierarchy=['info'], arg_keys=[], function=_info), cmds.Command(hierarchy=['add'], arg_keys=[], function=_add), @@ -589,6 +598,53 @@ def _deployment_rollback(deployment_id): return 0 +def _deployment_watch(deployment_id, max_count, interval): + """ + :param deployment_id: the application id + :type deployment_di: str + :param max_count: maximum number of polling calls + :type max_count: str + :param interval: wait interval in seconds between polling calls + :type interval: str + :returns: process status + :rtype: int + """ + + if max_count is not None: + max_count, err = _parse_int(max_count) + if err is not None: + emitter.publish(err) + return 1 + + if interval is not None: + interval, err = _parse_int(interval) + if err is not None: + emitter.publish(err) + return 1 + else: + interval = 1 + + client = marathon.create_client( + config.load_from_path( + os.environ[constants.DCOS_CONFIG_ENV])) + + count = 0 + while max_count is None or count < max_count: + deployment, err = client.get_deployment(deployment_id) + if err is not None: + emitter.publish(err) + return 1 + + if deployment is None: + return 0 + + emitter.publish(deployment) + time.sleep(interval) + count += 1 + + return 0 + + def _update_from_stdin(app_id, force): """ :param app_id: the id of the application diff --git a/dcos/cli/config/main.py b/dcos/cli/config/main.py index 2dc1c0c..91c03da 100644 --- a/dcos/cli/config/main.py +++ b/dcos/cli/config/main.py @@ -59,7 +59,7 @@ def main(): _save_config_file(config_path, toml_config) else: - emitter.publish(options.make_generic_usage_error(__doc__)) + emitter.publish(options.make_generic_usage_message(__doc__)) return 1 return 0 diff --git a/dcos/cli/help/main.py b/dcos/cli/help/main.py index 406a3a5..4abc6e8 100644 --- a/dcos/cli/help/main.py +++ b/dcos/cli/help/main.py @@ -45,7 +45,7 @@ def main(): return 0 else: - emitter.publish(options.make_generic_usage_error(__doc__)) + emitter.publish(options.make_generic_usage_message(__doc__)) return 1 diff --git a/integrations/cli/test_app.py b/integrations/cli/test_app.py index 5655cd8..89fcb5f 100644 --- a/integrations/cli/test_app.py +++ b/integrations/cli/test_app.py @@ -1,6 +1,5 @@ import json -import pytest from common import exec_command @@ -13,6 +12,8 @@ def test_help(): dcos app deployment list [] dcos app deployment rollback dcos app deployment stop + dcos app deployment watch [--max-count=] [--interval=] + dcos app info dcos app list dcos app remove [--force] @@ -39,6 +40,7 @@ Options: application definition. --max-count= Maximum number of entries to try to fetch and return + --interval= Number of seconds to wait between actions Positional arguments: The application id @@ -245,10 +247,11 @@ def test_stop_missing_app(): assert stderr == b'' -@pytest.mark.skipif(True, reason='We need to wait for the start to finish') def test_stop_app(): _add_app('tests/data/marathon/zero_instance_sleep.json') - _start_app('zero-instance-app') + _start_app('zero-instance-app', 3) + result = _list_deployments(1, 'zero-instance-app') + _watch_deployment(result[0]['id']) returncode, stdout, stderr = exec_command( ['dcos', 'app', 'stop', 'zero-instance-app']) @@ -360,15 +363,16 @@ def test_restarting_missing_app(): assert stderr == b'' -@pytest.mark.skipif(True, reason='We need to wait for the start to finish') def test_restarting_app(): _add_app('tests/data/marathon/zero_instance_sleep.json') - _start_app('zero-instance-app') + _start_app('zero-instance-app', 3) + result = _list_deployments(1, 'zero-instance-app') + _watch_deployment(result[0]['id']) returncode, stdout, stderr = exec_command( ['dcos', 'app', 'restart', 'zero-instance-app']) - assert returncode == 1 + assert returncode == 0 assert stdout.decode().startswith('Created deployment ') assert stderr == b'' @@ -487,6 +491,19 @@ def test_stop_deployment(): _remove_app('zero-instance-app') +def test_watching_missing_deployment(): + _watch_deployment('missing-deployment') + + +def test_watching_deployment(): + _add_app('tests/data/marathon/zero_instance_sleep.json') + _start_app('zero-instance-app', 3) + result = _list_deployments(1, 'zero-instance-app') + _watch_deployment(result[0]['id']) + _list_deployments(0, 'zero-instance-app') + _remove_app('zero-instance-app') + + def _list_apps(app_id=None): returncode, stdout, stderr = exec_command(['dcos', 'app', 'list']) @@ -593,3 +610,11 @@ def _list_deployments(expected_count, app_id=None): assert stderr == b'' return result + + +def _watch_deployment(deployment_id): + returncode, stdout, stderr = exec_command( + ['dcos', 'app', 'deployment', 'watch', deployment_id]) + + assert returncode == 0 + assert stderr == b''