Add integration tests for the dcos-marathon cli

This commit is contained in:
José Armando García Sancio
2015-01-29 21:34:13 +00:00
parent 1e4fc3f9f4
commit 6570a28a43
11 changed files with 273 additions and 101 deletions

View File

@@ -13,4 +13,5 @@
sleep 2 sleep 2
# Run the tox integration tests # Run the tox integration tests
tox -c /dcos-cli/tox.ini cd /dcos-cli
make clean all

View File

@@ -1,9 +1,14 @@
import json import json
import urllib import logging
import requests import requests
from dcos.api import errors from dcos.api import errors
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
class Client(object): class Client(object):
"""Class for talking to the Marathon server. """ """Class for talking to the Marathon server. """
@@ -38,7 +43,7 @@ class Client(object):
path=path) path=path)
if query_params is not None: if query_params is not None:
query_string = urllib.urlencode(query_params) query_string = urlencode(query_params)
url = (url + '?{}').format(query_string) url = (url + '?{}').format(query_string)
return url return url
@@ -64,6 +69,11 @@ class Client(object):
:rtype: Error :rtype: Error
""" """
message = response.json().get('message')
if message is None:
logging.error('Missing message from json: %s', response.json())
return Error('Unknown error from Marathon')
return Error('Error: {}'.format(response.json()['message'])) return Error('Error: {}'.format(response.json()['message']))
def get_app(self, app_id): def get_app(self, app_id):

View File

@@ -1,24 +1,26 @@
""" """
Usage: Usage:
dcos marathon describe [--json] <app_id>
dcos marathon info dcos marathon info
dcos marathon list dcos marathon list
dcos marathon describe <app_id> dcos marathon remove [--force] <app_id>
dcos marathon scale [--force] <app_id> <instances>
dcos marathon start <app_resource> dcos marathon start <app_resource>
dcos marathon scale <app_id> <instances> [--force] dcos marathon suspend [--force] <app_id>
dcos marathon suspend <app_id> [--force]
dcos marathon remove <app_id> [--force]
dcos marathon --help
dcos marathon --version
Options: Options:
-h, --help Show this screen -h, --help Show this screen
--version Show version --version Show version
--force This flag disable checks in Marathon during update
operations.
--json Outputs JSON format instead of default (TOML) format
""" """
import json import json
import os import os
import docopt import docopt
import toml
from dcos.api import config, constants, marathon, options from dcos.api import config, constants, marathon, options
@@ -35,7 +37,7 @@ def main():
return _list(toml_config) return _list(toml_config)
elif args['marathon'] and args['describe']: elif args['marathon'] and args['describe']:
toml_config = config.load_from_path(config_path) toml_config = config.load_from_path(config_path)
return _describe(args['<app_id>'], toml_config) return _describe(args['<app_id>'], args['--json'], toml_config)
elif args['marathon'] and args['start']: elif args['marathon'] and args['start']:
toml_config = config.load_from_path(config_path) toml_config = config.load_from_path(config_path)
return _start(args['<app_resource>'], toml_config) return _start(args['<app_resource>'], toml_config)
@@ -94,7 +96,7 @@ def _list(config):
return 1 return 1
if not apps: if not apps:
print("No apps to list.") print("No applications to list.")
for app in apps: for app in apps:
print(app['id']) print(app['id'])
@@ -102,11 +104,13 @@ def _list(config):
return 0 return 0
def _describe(app_id, config): def _describe(app_id, is_json, config):
"""Show details of a Marathon applications. """Show details of a Marathon application.
:param app_id: ID of the app to suspend :param app_id: ID of the app to suspend
:type app_id: str :type app_id: str
:param is_json: Whether to print in JSON format or TOML
:type is_json: bool
:param config: Configuration dictionary :param config: Configuration dictionary
:type config: config.Toml :type config: config.Toml
:returns: Process status :returns: Process status
@@ -119,9 +123,10 @@ def _describe(app_id, config):
print(err.error()) print(err.error())
return 1 return 1
print(json.dumps(app, if is_json:
sort_keys=True, print(json.dumps(app, sort_keys=True, indent=2))
indent=2)) else:
print(toml.dumps(app))
return 0 return 0

View File

@@ -1,3 +1,4 @@
import logging
import subprocess import subprocess
@@ -8,12 +9,20 @@ def exec_command(cmd, env=None):
:type cmd: list of str :type cmd: list of str
:param env: Environment variables :param env: Environment variables
:type env: dict of str to str :type env: dict of str to str
:returns: Object to the running process :returns: A tuple with the returncode, stdout and stderr
:rtype: subprocess.Popen :rtype: (int, bytes, bytes)
""" """
return subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
env=env) env=env)
stdout, stderr = process.communicate()
# We should always print the stdout and stderr
logging.error('STDOUT: %s', stdout.decode('utf-8'))
logging.error('STDERR: %s', stderr.decode('utf-8'))
return (process.returncode, stdout, stderr)

View File

@@ -2,10 +2,9 @@ from common import exec_command
def test_help(): def test_help():
process = exec_command(['dcos', 'config', '--help']) returncode, stdout, stderr = exec_command(['dcos', 'config', '--help'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b"""Usage: assert stdout == b"""Usage:
dcos config info dcos config info
dcos config <name> [<value>] dcos config <name> [<value>]
@@ -21,29 +20,25 @@ Options:
def test_info(): def test_info():
process = exec_command(['dcos', 'config', 'info']) returncode, stdout, stderr = exec_command(['dcos', 'config', 'info'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'Get and set DCOS command line options\n' assert stdout == b'Get and set DCOS command line options\n'
assert stderr == b'' assert stderr == b''
def test_version(): def test_version():
process = exec_command(['dcos', 'config', '--version']) returncode, stdout, stderr = exec_command(['dcos', 'config', '--version'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'dcos-config version 0.1.0\n' assert stdout == b'dcos-config version 0.1.0\n'
assert stderr == b'' assert stderr == b''
def test_list_property(): def test_list_property():
process = exec_command(['dcos', 'config', '--list']) returncode, stdout, stderr = exec_command(['dcos', 'config', '--list'])
stdout, stderr = process.communicate() assert returncode == 0
assert process.returncode == 0
assert stdout == b"""marathon.host=localhost assert stdout == b"""marathon.host=localhost
marathon.port=8080 marathon.port=8080
package.cache=tmp/cache package.cache=tmp/cache
@@ -81,40 +76,33 @@ def test_set_missing_property():
def _set_value(key, value): def _set_value(key, value):
process = exec_command(['dcos', 'config', key, value]) returncode, stdout, stderr = exec_command(['dcos', 'config', key, value])
stdout, stderr = process.communicate() assert returncode == 0
assert process.returncode == 0
assert stdout == b'' assert stdout == b''
assert stderr == b'' assert stderr == b''
def _get_value(key, value): def _get_value(key, value):
process = exec_command(['dcos', 'config', key]) returncode, stdout, stderr = exec_command(['dcos', 'config', key])
stdout, stderr = process.communicate() assert returncode == 0
assert process.returncode == 0
assert stdout == '{}\n'.format(value).encode('utf-8') assert stdout == '{}\n'.format(value).encode('utf-8')
assert stderr == b'' assert stderr == b''
def _unset_value(key): def _unset_value(key):
process = exec_command(['dcos', 'config', '--unset', key]) returncode, stdout, stderr = exec_command(
['dcos', 'config', '--unset', key])
stdout, stderr = process.communicate() assert returncode == 0
assert process.returncode == 0
assert stdout == b'' assert stdout == b''
assert stderr == b'' assert stderr == b''
def _get_missing_value(key): def _get_missing_value(key):
process = exec_command(['dcos', 'config', key]) returncode, stdout, stderr = exec_command(['dcos', 'config', key])
stdout, stderr = process.communicate() assert returncode == 1
assert process.returncode == 1
assert stdout == b'' assert stdout == b''
assert stderr == b'' assert stderr == b''

View File

@@ -5,10 +5,9 @@ from dcos.cli.main import _which
def test_help(): def test_help():
process = exec_command(['dcos', '--help']) returncode, stdout, stderr = exec_command(['dcos', '--help'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b"""Usage: assert stdout == b"""Usage:
dcos <command> [<args>...] dcos <command> [<args>...]
@@ -23,10 +22,9 @@ read about a specific subcommand.
def test_version(): def test_version():
process = exec_command(['dcos', '--version']) returncode, stdout, stderr = exec_command(['dcos', '--version'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'dcos version 0.1.0\n' assert stdout == b'dcos version 0.1.0\n'
assert stderr == b'' assert stderr == b''
@@ -37,10 +35,9 @@ def test_missing_dcos_config():
'DCOS_PATH': os.environ['DCOS_PATH'], 'DCOS_PATH': os.environ['DCOS_PATH'],
} }
process = exec_command(['dcos'], env=env) returncode, stdout, stderr = exec_command(['dcos'], env=env)
stdout, stderr = process.communicate()
assert process.returncode == 1 assert returncode == 1
assert stdout == (b"Environment variable 'DCOS_CONFIG' must be set " assert stdout == (b"Environment variable 'DCOS_CONFIG' must be set "
b"to the DCOS config file.\n") b"to the DCOS config file.\n")
assert stderr == b'' assert stderr == b''
@@ -53,10 +50,9 @@ def test_dcos_config_not_a_file():
'DCOS_CONFIG': 'missing/file', 'DCOS_CONFIG': 'missing/file',
} }
process = exec_command(['dcos'], env=env) returncode, stdout, stderr = exec_command(['dcos'], env=env)
stdout, stderr = process.communicate()
assert process.returncode == 1 assert returncode == 1
assert stdout == (b"Environment variable 'DCOS_CONFIG' maps to " assert stdout == (b"Environment variable 'DCOS_CONFIG' maps to "
b"'missing/file' and it is not a file.\n") b"'missing/file' and it is not a file.\n")
assert stderr == b'' assert stderr == b''
@@ -68,10 +64,9 @@ def test_missing_dcos_path():
'DCOS_CONFIG': os.environ['DCOS_CONFIG'], 'DCOS_CONFIG': os.environ['DCOS_CONFIG'],
} }
process = exec_command(['dcos'], env=env) returncode, stdout, stderr = exec_command(['dcos'], env=env)
stdout, stderr = process.communicate()
assert process.returncode == 1 assert returncode == 1
assert stdout == (b"Environment variable 'DCOS_PATH' not set to the DCOS " assert stdout == (b"Environment variable 'DCOS_PATH' not set to the DCOS "
b"CLI path.\n") b"CLI path.\n")
assert stderr == b'' assert stderr == b''
@@ -84,10 +79,9 @@ def test_dcos_path_not_a_dir():
'DCOS_PATH': 'missing/dir', 'DCOS_PATH': 'missing/dir',
} }
process = exec_command(['dcos'], env=env) returncode, stdout, stderr = exec_command(['dcos'], env=env)
stdout, stderr = process.communicate()
assert process.returncode == 1 assert returncode == 1
assert stdout == (b"Environment variable 'DCOS_PATH' maps to " assert stdout == (b"Environment variable 'DCOS_PATH' maps to "
b"'missing/dir' which is not a directory.\n") b"'missing/dir' which is not a directory.\n")
assert stderr == b'' assert stderr == b''
@@ -99,9 +93,8 @@ def test_missing_path():
'DCOS_PATH': os.environ['DCOS_PATH'], 'DCOS_PATH': os.environ['DCOS_PATH'],
} }
process = exec_command([_which('dcos')], env=env) returncode, stdout, stderr = exec_command([_which('dcos')], env=env)
stdout, stderr = process.communicate()
assert process.returncode == 1 assert returncode == 1
assert stdout == b"Environment variable 'PATH' not set.\n" assert stdout == b"Environment variable 'PATH' not set.\n"
assert stderr == b'' assert stderr == b''

View File

@@ -3,10 +3,9 @@ from common import exec_command
def test_help(): def test_help():
process = exec_command(['dcos', 'help', '--help']) returncode, stdout, stderr = exec_command(['dcos', 'help', '--help'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b"""Usage: assert stdout == b"""Usage:
dcos help info dcos help info
dcos help --all dcos help --all
@@ -20,28 +19,25 @@ Options:
def test_info(): def test_info():
process = exec_command(['dcos', 'help', 'info']) returncode, stdout, stderr = exec_command(['dcos', 'help', 'info'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'Display help information about DCOS\n' assert stdout == b'Display help information about DCOS\n'
assert stderr == b'' assert stderr == b''
def test_version(): def test_version():
process = exec_command(['dcos', 'help', '--version']) returncode, stdout, stderr = exec_command(['dcos', 'help', '--version'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'dcos-help version 0.1.0\n' assert stdout == b'dcos-help version 0.1.0\n'
assert stderr == b'' assert stderr == b''
def test_list_all(): def test_list_all():
process = exec_command(['dcos', 'help', '--all']) returncode, stdout, stderr = exec_command(['dcos', 'help', '--all'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == """Available DCOS command in '{}': assert stdout == """Available DCOS command in '{}':
\tconfig \tGet and set DCOS command line options \tconfig \tGet and set DCOS command line options

View File

@@ -0,0 +1,168 @@
import json
from common import exec_command
def test_help():
returncode, stdout, stderr = exec_command(['dcos', 'marathon', '--help'])
assert returncode == 0
assert stdout == b"""Usage:
dcos marathon describe [--json] <app_id>
dcos marathon info
dcos marathon list
dcos marathon remove [--force] <app_id>
dcos marathon scale [--force] <app_id> <instances>
dcos marathon start <app_resource>
dcos marathon suspend [--force] <app_id>
Options:
-h, --help Show this screen
--version Show version
--force This flag disable checks in Marathon during update
operations.
--json Outputs JSON format instead of default (TOML) format
"""
def test_version():
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', '--version'])
assert returncode == 0
assert stdout == b'dcos-marathon version 0.1.0\n'
assert stderr == b''
def test_info():
returncode, stdout, stderr = exec_command(['dcos', 'marathon', 'info'])
assert returncode == 0
assert stdout == b'Deploy and manage applications on Apache Mesos\n'
assert stderr == b''
def test_empty_list():
_list_apps()
def test_start_app():
_start_app('tests/data/marathon/sleep.json')
_list_apps('test-app')
_remove_app('test-app')
def test_remove_app():
_start_app('tests/data/marathon/sleep.json')
_remove_app('test-app')
_list_apps()
# TODO: Let's improve this once we have a fixed version of toml
def test_describe_app():
_start_app('tests/data/marathon/sleep.json')
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'describe', 'test-app'])
assert returncode == 0
assert stdout != b''
assert stderr == b''
_remove_app('test-app')
def test_describe_app_in_json():
_start_app('tests/data/marathon/sleep.json')
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'describe', '--json', 'test-app'])
result = json.loads(stdout.decode('utf-8'))
assert returncode == 0
assert isinstance(result, dict)
assert result['id'] == '/test-app'
assert stderr == b''
_remove_app('test-app')
def test_scale_app():
_start_app('tests/data/marathon/zero_instance_sleep.json')
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'scale', 'zero-instance-app', '2'])
assert returncode == 0
assert stdout.decode('utf-8').startswith('Created deployment ')
assert stderr == b''
_remove_app('zero-instance-app')
def test_force_scale_appp():
_start_app('tests/data/marathon/sleep.json')
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'scale', '--force', 'test-app', '2'])
assert returncode == 0
assert stdout.decode('utf-8').startswith('Created deployment ')
assert stderr == b''
_remove_app('test-app')
def test_suspend_app():
_start_app('tests/data/marathon/zero_instance_sleep.json')
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'suspend', 'zero-instance-app'])
assert returncode == 0
assert stdout.decode('utf-8').startswith('Created deployment ')
assert stderr == b''
_remove_app('zero-instance-app')
def test_remove_missing_app():
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'remove', 'missing-id'])
assert returncode == 1
assert stdout == b"Error: App '/missing-id' does not exist\n"
assert stderr == b''
def _start_app(file_path):
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'start', file_path])
assert returncode == 0
assert stdout == b''
assert stderr == b''
def _list_apps(app_id=None):
returncode, stdout, stderr = exec_command(['dcos', 'marathon', 'list'])
if app_id is None:
result = b'No applications to list.\n'
elif isinstance(app_id, str):
result = '/{}\n'.format(app_id).encode('utf-8')
else:
assert False
assert returncode == 0
assert stdout == result
assert stderr == b''
def _remove_app(app_id):
returncode, stdout, stderr = exec_command(
['dcos', 'marathon', 'remove', app_id])
assert returncode == 0
assert stdout == b''
assert stderr == b''

View File

@@ -2,10 +2,9 @@ from common import exec_command
def test_package(): def test_package():
process = exec_command(['dcos', 'package', '--help']) returncode, stdout, stderr = exec_command(['dcos', 'package', '--help'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b"""Usage: assert stdout == b"""Usage:
dcos package configure <package_name> dcos package configure <package_name>
dcos package info dcos package info
@@ -41,28 +40,25 @@ Configuration:
def test_info(): def test_info():
process = exec_command(['dcos', 'package', 'info']) returncode, stdout, stderr = exec_command(['dcos', 'package', 'info'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'Install and manage DCOS software packages.\n' assert stdout == b'Install and manage DCOS software packages.\n'
assert stderr == b'' assert stderr == b''
def test_version(): def test_version():
process = exec_command(['dcos', 'package', '--version']) returncode, stdout, stderr = exec_command(['dcos', 'package', '--version'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b'dcos-package version 0.1.0\n' assert stdout == b'dcos-package version 0.1.0\n'
assert stderr == b'' assert stderr == b''
def test_sources_list(): def test_sources_list():
process = exec_command(['dcos', 'package', 'sources']) returncode, stdout, stderr = exec_command(['dcos', 'package', 'sources'])
stdout, stderr = process.communicate()
assert process.returncode == 0 assert returncode == 0
assert stdout == b"""cc5af1bcaec7323400a95e1c38caf61378f6f081 \ assert stdout == b"""cc5af1bcaec7323400a95e1c38caf61378f6f081 \
file:///Users/me/test-registry file:///Users/me/test-registry
0c854fa7f2ede3dcc3122bf2b7db160491cf9f33 \ 0c854fa7f2ede3dcc3122bf2b7db160491cf9f33 \

View File

@@ -1,11 +1,6 @@
[package] [package]
cache = "tmp/cache" cache = "tmp\/cache"
sources = [ sources = [ "file:\/\/\/Users\/me\/test-registry", "https:\/\/my.org\/registry", "git:\/\/github.com\/mesosphere\/universe.git",]
"file:///Users/me/test-registry",
"https://my.org/registry",
"git://github.com/mesosphere/universe.git"
]
[marathon] [marathon]
port = 8080
host = "localhost" host = "localhost"
port = 8080

View File

@@ -0,0 +1,11 @@
{
"id": "zero-instance-app",
"cmd": "sleep 1000",
"cpus": 0.1,
"mem": 16,
"instances": 0,
"labels": {
"PACKAGE_ID": "zero-instance-app",
"PACKAGE_VERSION": "1.2.3"
}
}