JIRA: DCOS-2941 Add kill tasks to dcos/marathon.py and CLI; Documented in Marathon API here: https://mesosphere.github.io/marathon/docs/rest-api.html#delete-v2-apps-appid-tasks
Add tests, and CLI integration Add scale, and kill parameters to app kill command / API call Add test for host-based kill, but test requires >=2 hosts to succeed - skip if not true [TODO: FIX] Cleanup
This commit is contained in:
@@ -11,6 +11,7 @@ Usage:
|
||||
dcos marathon app show [--app-version=<app-version>] <app-id>
|
||||
dcos marathon app start [--force] <app-id> [<instances>]
|
||||
dcos marathon app stop [--force] <app-id>
|
||||
dcos marathon app kill [--scale] [--host=<host>] <app-id>
|
||||
dcos marathon app update [--force] <app-id> [<properties>...]
|
||||
dcos marathon app version list [--max-count=<max-count>] <app-id>
|
||||
dcos marathon deployment list [--json <app-id>]
|
||||
@@ -69,6 +70,12 @@ Options:
|
||||
|
||||
--interval=<interval> Number of seconds to wait between actions
|
||||
|
||||
--scale Scale the app down after performing the
|
||||
the operation.
|
||||
|
||||
--host=<host> The host name to isolate your command to
|
||||
|
||||
|
||||
Positional Arguments:
|
||||
<app-id> The application id
|
||||
|
||||
@@ -218,6 +225,11 @@ def _cmds():
|
||||
arg_keys=['<app-id>', '--force'],
|
||||
function=_restart),
|
||||
|
||||
cmds.Command(
|
||||
hierarchy=['marathon', 'app', 'kill'],
|
||||
arg_keys=['<app-id>', '--scale', '--host'],
|
||||
function=_kill),
|
||||
|
||||
cmds.Command(
|
||||
hierarchy=['marathon', 'group', 'add'],
|
||||
arg_keys=['<group-resource>'],
|
||||
@@ -714,6 +726,35 @@ def _restart(app_id, force):
|
||||
return 0
|
||||
|
||||
|
||||
def _kill(app_id, scale, host):
|
||||
"""
|
||||
:param app_id: the id of the application
|
||||
:type app_id: str
|
||||
:param: scale: Scale the app down
|
||||
:type: scale: bool
|
||||
:param: host: Kill only those tasks running on host specified
|
||||
:type: string
|
||||
:returns: process return code
|
||||
:rtype: int
|
||||
"""
|
||||
client = marathon.create_client()
|
||||
|
||||
payload = client.kill_tasks(app_id, host=host, scale=scale)
|
||||
# If scale is provided, the API return a "deploymentResult"
|
||||
# https://github.com/mesosphere/marathon/blob/50366c8/src/main/scala/mesosphere/marathon/api/RestResource.scala#L34-L36
|
||||
if scale:
|
||||
emitter.publish("Started deployment: {}".format(payload))
|
||||
else:
|
||||
if 'tasks' in payload:
|
||||
emitter.publish('Killed tasks: {}'.format(payload['tasks']))
|
||||
if len(payload['tasks']) == 0:
|
||||
return 1
|
||||
else:
|
||||
emitter.publish('Killed tasks: []')
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _version_list(app_id, max_count):
|
||||
"""
|
||||
:param app_id: the id of the application
|
||||
|
||||
@@ -11,6 +11,7 @@ Usage:
|
||||
dcos marathon app show [--app-version=<app-version>] <app-id>
|
||||
dcos marathon app start [--force] <app-id> [<instances>]
|
||||
dcos marathon app stop [--force] <app-id>
|
||||
dcos marathon app kill [--scale] [--host=<host>] <app-id>
|
||||
dcos marathon app update [--force] <app-id> [<properties>...]
|
||||
dcos marathon app version list [--max-count=<max-count>] <app-id>
|
||||
dcos marathon deployment list [--json <app-id>]
|
||||
@@ -69,6 +70,12 @@ Options:
|
||||
|
||||
--interval=<interval> Number of seconds to wait between actions
|
||||
|
||||
--scale Scale the app down after performing the
|
||||
the operation.
|
||||
|
||||
--host=<host> The host name to isolate your command to
|
||||
|
||||
|
||||
Positional Arguments:
|
||||
<app-id> The application id
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ Usage:
|
||||
dcos marathon app show [--app-version=<app-version>] <app-id>
|
||||
dcos marathon app start [--force] <app-id> [<instances>]
|
||||
dcos marathon app stop [--force] <app-id>
|
||||
dcos marathon app kill [--scale] [--host=<host>] <app-id>
|
||||
dcos marathon app update [--force] <app-id> [<properties>...]
|
||||
dcos marathon app version list [--max-count=<max-count>] <app-id>
|
||||
dcos marathon deployment list [--json <app-id>]
|
||||
@@ -88,6 +89,12 @@ Options:
|
||||
|
||||
--interval=<interval> Number of seconds to wait between actions
|
||||
|
||||
--scale Scale the app down after performing the
|
||||
the operation.
|
||||
|
||||
--host=<host> The host name to isolate your command to
|
||||
|
||||
|
||||
Positional Arguments:
|
||||
<app-id> The application id
|
||||
|
||||
@@ -465,6 +472,86 @@ def test_restarting_app():
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def test_killing_app():
|
||||
with _zero_instance_app():
|
||||
_start_app('zero-instance-app', 3)
|
||||
watch_all_deployments()
|
||||
task_set_1 = set([task['id']
|
||||
for task in _list_tasks(3, 'zero-instance-app')])
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'app', 'kill', 'zero-instance-app'])
|
||||
assert returncode == 0
|
||||
assert stdout.decode().startswith('Killed tasks: ')
|
||||
assert stderr == b''
|
||||
watch_all_deployments()
|
||||
task_set_2 = set([task['id']
|
||||
for task in _list_tasks(app_id='zero-instance-app')])
|
||||
assert len(task_set_1.intersection(task_set_2)) == 0
|
||||
|
||||
|
||||
def test_killing_scaling_app():
|
||||
with _zero_instance_app():
|
||||
_start_app('zero-instance-app', 3)
|
||||
watch_all_deployments()
|
||||
_list_tasks(3)
|
||||
command = ['dcos', 'marathon', 'app', 'kill', '--scale',
|
||||
'zero-instance-app']
|
||||
returncode, stdout, stderr = exec_command(command)
|
||||
assert returncode == 0
|
||||
assert stdout.decode().startswith('Started deployment: ')
|
||||
assert stdout.decode().find('version') > -1
|
||||
assert stdout.decode().find('deploymentId') > -1
|
||||
assert stderr == b''
|
||||
watch_all_deployments()
|
||||
_list_tasks(0)
|
||||
|
||||
|
||||
def test_killing_with_host_app():
|
||||
with _zero_instance_app():
|
||||
_start_app('zero-instance-app', 3)
|
||||
watch_all_deployments()
|
||||
existing_tasks = _list_tasks(3, 'zero-instance-app')
|
||||
task_hosts = set([task['host'] for task in existing_tasks])
|
||||
if len(task_hosts) <= 1:
|
||||
pytest.skip('test needs 2 or more agents to succeed, '
|
||||
'only {} agents available'.format(len(task_hosts)))
|
||||
assert len(task_hosts) > 1
|
||||
kill_host = list(task_hosts)[0]
|
||||
expected_to_be_killed = set([task['id']
|
||||
for task in existing_tasks
|
||||
if task['host'] == kill_host])
|
||||
not_to_be_killed = set([task['id']
|
||||
for task in existing_tasks
|
||||
if task['host'] != kill_host])
|
||||
assert len(not_to_be_killed) > 0
|
||||
assert len(expected_to_be_killed) > 0
|
||||
command = ['dcos', 'marathon', 'app', 'kill', '--host', kill_host,
|
||||
'zero-instance-app']
|
||||
returncode, stdout, stderr = exec_command(command)
|
||||
assert stdout.decode().startswith('Killed tasks: ')
|
||||
assert stderr == b''
|
||||
new_tasks = set([task['id'] for task in _list_tasks()])
|
||||
assert not_to_be_killed.intersection(new_tasks) == not_to_be_killed
|
||||
assert len(expected_to_be_killed.intersection(new_tasks)) == 0
|
||||
|
||||
|
||||
def test_kill_stopped_app():
|
||||
with _zero_instance_app():
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'app', 'kill', 'zero-instance-app'])
|
||||
assert returncode == 1
|
||||
assert stdout.decode().startswith('Killed tasks: []')
|
||||
|
||||
|
||||
def test_kill_missing_app():
|
||||
returncode, stdout, stderr = exec_command(
|
||||
['dcos', 'marathon', 'app', 'kill', 'app'])
|
||||
assert returncode == 1
|
||||
assert stdout.decode() == ''
|
||||
stderr_expected = "Error: App '/app' does not exist"
|
||||
assert stderr.decode().strip() == stderr_expected
|
||||
|
||||
|
||||
def test_list_version_missing_app():
|
||||
assert_command(
|
||||
['dcos', 'marathon', 'app', 'version', 'list', 'missing-id'],
|
||||
@@ -744,7 +831,7 @@ def _list_versions(app_id, expected_count, max_count=None):
|
||||
assert stderr == b''
|
||||
|
||||
|
||||
def _list_tasks(expected_count, app_id=None):
|
||||
def _list_tasks(expected_count=None, app_id=None):
|
||||
cmd = ['dcos', 'marathon', 'task', 'list', '--json']
|
||||
if app_id is not None:
|
||||
cmd.append(app_id)
|
||||
@@ -754,7 +841,8 @@ def _list_tasks(expected_count, app_id=None):
|
||||
result = json.loads(stdout.decode('utf-8'))
|
||||
|
||||
assert returncode == 0
|
||||
assert len(result) == expected_count
|
||||
if expected_count:
|
||||
assert len(result) == expected_count
|
||||
assert stderr == b''
|
||||
|
||||
return result
|
||||
|
||||
@@ -471,6 +471,29 @@ class Client(object):
|
||||
|
||||
_http_req(http.delete, url, params=params, timeout=self._timeout)
|
||||
|
||||
def kill_tasks(self, app_id, scale=None, host=None):
|
||||
"""Kills the tasks for a given application,
|
||||
and can target a given agent, with a future target scale
|
||||
|
||||
:param app_id: the id of the application to restart
|
||||
:type app_id: str
|
||||
:param scale: Scale the app down after killing the specified tasks
|
||||
:type scale: bool
|
||||
:param host: host to target restarts on
|
||||
:type host: string
|
||||
"""
|
||||
params = {}
|
||||
app_id = self.normalize_app_id(app_id)
|
||||
if host:
|
||||
params['host'] = host
|
||||
if scale:
|
||||
params['scale'] = scale
|
||||
url = self._create_url('v2/apps{}/tasks'.format(app_id))
|
||||
response = _http_req(http.delete, url,
|
||||
params=params,
|
||||
timeout=self._timeout)
|
||||
return response.json()
|
||||
|
||||
def restart_app(self, app_id, force=None):
|
||||
"""Performs a rolling restart of all of the tasks.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user