Merge pull request #46 from mesosphere/dcos-329-cancel-deployment

dcos-329 implements canceling deployments
This commit is contained in:
José Armando García Sancio
2015-02-25 14:31:11 -08:00
3 changed files with 186 additions and 47 deletions

View File

@@ -101,7 +101,7 @@ class Client(object):
logger.info('Getting %r', url)
response = requests.get(url)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
# Looks like Marathon return different JSON for versions
@@ -138,7 +138,7 @@ class Client(object):
logger.info('Getting %r', url)
response = requests.get(url)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
if max_count is None:
@@ -159,7 +159,7 @@ class Client(object):
logger.info('Getting %r', url)
response = requests.get(url)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
apps = response.json()['apps']
@@ -186,7 +186,7 @@ class Client(object):
logger.info('Posting %r to %r', app_json, url)
response = requests.post(url, json=app_json)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
return (response.json(), None)
@@ -217,7 +217,7 @@ class Client(object):
logger.info('Putting %r to %r', payload, url)
response = requests.put(url, json=payload)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
return (response.json().get('deploymentId'), None)
@@ -248,7 +248,7 @@ class Client(object):
logger.info('Putting to %r', url)
response = requests.put(url, json={'instances': int(instances)})
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
deployment = response.json()['deploymentId']
@@ -320,7 +320,7 @@ class Client(object):
logger.info('Posting %r', url)
response = requests.post(url)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
return (response.json(), None)
@@ -340,7 +340,7 @@ class Client(object):
logger.info('Getting %r', url)
response = requests.get(url)
logger.info('Got (%r): %r', response.status_code, response.json())
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
if app_id is not None:
@@ -356,6 +356,60 @@ class Client(object):
else:
return (None, self._response_to_error(response))
def _cancel_deployment(self, deployment_id, force):
"""Cancels an application deployment.
:param deployment_id: the deployment id
:type deployment_id: str
:param force: if set to `False`, stop the deployment and
create a new rollback deployment to reinstate the
previous configuration. If set to `True`, simply stop the
deployment.
:type force: bool
:returns: an error if unable to rollback the deployment; None otherwise
:rtype: Error
"""
if not force:
params = None
else:
params = {'force': 'true'}
url = self._create_url(
'v2/deployments/{}'.format(deployment_id),
params)
logger.info('Deleting %r', url)
response = requests.delete(url)
logger.info('Got (%r): %r', response.status_code, response.text)
if _success(response.status_code):
return None
else:
return self._response_to_error(response)
def rollback_deployment(self, deployment_id):
"""Rolls back an application deployment.
:param deployment_id: the deployment id
:type deployment_id: str
:returns: an error if unable to rollback the deployment; None otherwise
:rtype: Error
"""
return self._cancel_deployment(deployment_id, False)
def stop_deployment(self, deployment_id):
"""Stops an application deployment.
:param deployment_id: the deployment id
:type deployment_id: str
:returns: an error if unable to stop the deployment; None otherwise
:rtype: Error
"""
return self._cancel_deployment(deployment_id, True)
class Error(errors.Error):
""" Class for describing errors while talking to the Marathon server.

View File

@@ -2,6 +2,8 @@
Usage:
dcos app add
dcos app deployment list [<app-id>]
dcos app deployment rollback <deployment-id>
dcos app deployment stop <deployment-id>
dcos app info
dcos app list
dcos app remove [--force] <app-id>
@@ -31,6 +33,7 @@ Options:
Positional arguments:
<app-id> The application id
<deployment-id> The deployment id
<instances> The number of instances to start
<properties> Optional key-value pairs to be included in the
command. The separator between the key and value
@@ -79,10 +82,6 @@ def _cmds():
"""
return [
cmds.Command(hierarchy=['info'], arg_keys=[], function=_info),
cmds.Command(hierarchy=['add'], arg_keys=[], function=_add),
cmds.Command(
hierarchy=['version', 'list'],
arg_keys=['<app-id>', '--max-count'],
@@ -93,6 +92,20 @@ def _cmds():
arg_keys=['<app-id>'],
function=_deployment_list),
cmds.Command(
hierarchy=['deployment', 'rollback'],
arg_keys=['<deployment-id>'],
function=_deployment_rollback),
cmds.Command(
hierarchy=['deployment', 'stop'],
arg_keys=['<deployment-id>'],
function=_deployment_stop),
cmds.Command(hierarchy=['info'], arg_keys=[], function=_info),
cmds.Command(hierarchy=['add'], arg_keys=[], function=_add),
cmds.Command(hierarchy=['list'], arg_keys=[], function=_list),
cmds.Command(
@@ -536,6 +549,46 @@ def _deployment_list(app_id):
return 0
def _deployment_stop(deployment_id):
"""
:param deployment_id: the application id
:type deployment_di: str
:returns: process status
:rtype: int
"""
client = marathon.create_client(
config.load_from_path(
os.environ[constants.DCOS_CONFIG_ENV]))
err = client.stop_deployment(deployment_id)
if err is not None:
emitter.publish(err)
return 1
return 0
def _deployment_rollback(deployment_id):
"""
:param deployment_id: the application id
:type deployment_di: str
:returns: process status
:rtype: int
"""
client = marathon.create_client(
config.load_from_path(
os.environ[constants.DCOS_CONFIG_ENV]))
err = client.rollback_deployment(deployment_id)
if err is not None:
emitter.publish(err)
return 1
return 0
def _update_from_stdin(app_id, force):
"""
:param app_id: the id of the application

View File

@@ -11,6 +11,8 @@ def test_help():
assert stdout == b"""Usage:
dcos app add
dcos app deployment list [<app-id>]
dcos app deployment rollback <deployment-id>
dcos app deployment stop <deployment-id>
dcos app info
dcos app list
dcos app remove [--force] <app-id>
@@ -40,6 +42,7 @@ Options:
Positional arguments:
<app-id> The application id
<deployment-id> The deployment id
<instances> The number of instances to start
<properties> Optional key-value pairs to be included in the
command. The separator between the key and value
@@ -416,59 +419,71 @@ def test_list_version_max_count():
def test_list_empty_deployment():
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'list'])
assert returncode == 0
assert stdout == b'[]\n'
assert stderr == b''
_list_deployments(0)
def test_list_deployment():
_add_app('tests/data/marathon/zero_instance_sleep.json')
_start_app('zero-instance-app')
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'list'])
result = json.loads(stdout.decode('utf-8'))
assert returncode == 0
assert len(result) == 1
assert stderr == b''
_start_app('zero-instance-app', 3)
_list_deployments(1)
_remove_app('zero-instance-app')
def test_list_deployment_missing_app():
_add_app('tests/data/marathon/zero_instance_sleep.json')
_start_app('zero-instance-app')
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'list', 'missing-id'])
result = json.loads(stdout.decode('utf-8'))
assert returncode == 0
assert len(result) == 0
assert stderr == b''
_list_deployments(0, 'missing-id')
_remove_app('zero-instance-app')
def test_list_deployment_app():
_add_app('tests/data/marathon/zero_instance_sleep.json')
_start_app('zero-instance-app')
_start_app('zero-instance-app', 3)
_list_deployments(1, 'zero-instance-app')
_remove_app('zero-instance-app')
def test_rollback_missing_deployment():
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'rollback', 'missing-deployment'])
assert returncode == 1
assert (stdout ==
b'Error: DeploymentPlan missing-deployment does not exist\n')
assert stderr == b''
def test_rollback_deployment():
_add_app('tests/data/marathon/zero_instance_sleep.json')
_start_app('zero-instance-app', 3)
result = _list_deployments(1, 'zero-instance-app')
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'list', 'zero-instance-app'])
result = json.loads(stdout.decode('utf-8'))
['dcos', 'app', 'deployment', 'rollback', result[0]['id']])
assert returncode == 0
assert len(result) == 1
assert stdout == b''
assert stderr == b''
_list_deployments(0)
_remove_app('zero-instance-app')
def test_stop_deployment():
_add_app('tests/data/marathon/zero_instance_sleep.json')
_start_app('zero-instance-app', 3)
result = _list_deployments(1, 'zero-instance-app')
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'deployment', 'stop', result[0]['id']])
assert returncode == 0
assert stdout == b''
assert stderr == b''
_list_deployments(0)
_remove_app('zero-instance-app')
@@ -526,9 +541,12 @@ def _show_app(app_id, version=None):
return result
def _start_app(app_id):
returncode, stdout, stderr = exec_command(
['dcos', 'app', 'start', app_id])
def _start_app(app_id, instances=None):
cmd = ['dcos', 'app', 'start', app_id]
if instances is not None:
cmd.append(str(instances))
returncode, stdout, stderr = exec_command(cmd)
assert returncode == 0
assert stdout.decode().startswith('Created deployment ')
@@ -560,4 +578,18 @@ def _list_versions(app_id, expected_count, max_count=None):
assert len(result) == expected_count
assert stderr == b''
def _list_deployments(expected_count, app_id=None):
cmd = ['dcos', 'app', 'deployment', 'list']
if app_id is not None:
cmd.append(app_id)
returncode, stdout, stderr = exec_command(cmd)
result = json.loads(stdout.decode('utf-8'))
assert returncode == 0
assert len(result) == expected_count
assert stderr == b''
return result