Files
deb-python-dcos/cli/tests/integrations/test_experimental.py

505 lines
16 KiB
Python

import contextlib
import json
import os
import re
import shutil
import tempfile
import time
import dcoscli
from dcos import util
from .helpers.common import (assert_command, exec_command, file_json_ast,
zip_contents_as_json)
from .helpers.marathon import watch_all_deployments
command_base = ['dcos', 'experimental']
data_dir = os.path.join(os.getcwd(), 'tests', 'data')
build_data_dir = os.path.join(data_dir, 'package_build')
def runnable_package_path(index):
return os.path.join(
build_data_dir, 'helloworld', 'helloworld{}.json'.format(index))
def test_experimental():
command = command_base + ['--help']
with open('dcoscli/data/help/experimental.txt') as content:
assert_command(command, stdout=content.read().encode())
def test_info():
command = command_base + ['--info']
out = b'Manage commands that are under development\n'
assert_command(command, stdout=out)
def test_version():
command = command_base + ['--version']
out = b'dcos-experimental version SNAPSHOT\n'
assert_command(command, stdout=out)
def test_package_build_with_only_resources():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_resource_only_reference.json"),
expected_package_path=os.path.join(
build_data_dir,
"package_resource_only_reference_expected.json"))
def test_package_build_with_only_config_with_no_references():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_config_reference_expected.json"),
expected_package_path=os.path.join(
build_data_dir,
"package_config_reference_expected.json"))
def test_package_build_with_only_config():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_config_reference.json"),
expected_package_path=os.path.join(
build_data_dir,
"package_config_reference_expected.json"))
def test_package_build_with_only_marathon():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_marathon_reference.json"),
expected_package_path=os.path.join(
build_data_dir,
"package_marathon_reference_expected.json"))
def test_package_build_with_only_resources_reference():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_resource_reference.json"))
def test_package_build_with_no_references():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_no_references.json"))
def test_package_build_with_all_references():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_all_references.json"))
def test_package_build_with_all_references_json():
_successful_package_build_test(
os.path.join(
build_data_dir,
"package_all_references.json"),
expects_json=True)
def test_package_build_where_build_definition_does_not_exist():
with _temporary_directory() as output_directory:
build_definition_path = os.path.join(build_data_dir,
"does_not_exist.json")
stderr = ("The file [{}] does not exist\n"
.format(build_definition_path)
.encode())
_package_build_failure(build_definition_path,
output_directory,
stderr=stderr)
def test_package_build_where_project_is_missing_references():
with _temporary_directory() as output_directory:
build_definition_path = (
os.path.join(build_data_dir,
"package_missing_references.json"))
marathon_json_path = os.path.join(build_data_dir,
"marathon.json")
stderr = ("Error opening file [{}]: No such file or directory\n"
.format(marathon_json_path)
.encode())
_package_build_failure(build_definition_path,
output_directory,
stderr=stderr)
def test_package_build_where_reference_does_not_match_schema():
with _temporary_directory() as output_directory:
build_definition_path = os.path.join(
build_data_dir,
"package_reference_does_not_match_schema.json"
)
bad_resource_path = os.path.join(
build_data_dir,
"resource-bad.json"
)
stderr = ("Error validating package: "
"[{}] does not conform to the specified schema\n"
.format(bad_resource_path)
.encode())
_package_build_failure(build_definition_path,
output_directory,
stderr=stderr)
def test_package_build_where_build_definition_does_not_match_schema():
with _temporary_directory() as output_directory:
bad_build_definition_path = os.path.join(
build_data_dir,
"package_no_match_schema.json"
)
stderr = ("Error validating package: "
"[{}] does not conform to the specified schema\n"
.format(bad_build_definition_path)
.encode())
_package_build_failure(bad_build_definition_path,
output_directory,
stderr=stderr)
def test_package_build_where_build_definition_has_badly_formed_reference():
with _temporary_directory() as output_directory:
bad_build_definition_path = os.path.join(
build_data_dir,
"package_badly_formed_reference.json"
)
stderr = ("Error validating package: "
"[{}] does not conform to the specified schema\n"
.format(bad_build_definition_path)
.encode())
_package_build_failure(bad_build_definition_path,
output_directory,
stderr=stderr)
def test_package_add_argument_exclussion():
command = command_base + ['package', 'add',
'--dcos-package', runnable_package_path(1),
'--package-version', '3.0']
code, out, err = exec_command(command)
assert code == 1
assert err == b''
stdout = out.decode()
not_recognized = 'Command not recognized'
assert not_recognized in stdout
def test_service_start_happy_path():
with _temporary_directory() as output_directory:
runnable_package = _package_build(
runnable_package_path(2), output_directory)
name, version = _package_add(runnable_package)
try:
_service_start(name, version)
finally:
_service_stop(name)
def test_service_start_happy_path_json():
with _temporary_directory() as output_directory:
runnable_package = _package_build(
runnable_package_path(3), output_directory)
name, version = _package_add(runnable_package, expects_json=True)
try:
_service_start(name, version, expects_json=True)
finally:
_service_stop(name)
def test_service_start_happy_path_from_universe():
package_name = 'hello-world'
name, version = _package_add_universe(package_name)
try:
_service_start(name, version)
finally:
_service_stop(name)
def test_service_start_happy_path_from_universe_json():
package_name = 'cassandra'
name, version = _package_add_universe(package_name, expects_json=True)
try:
_service_start(name, version)
finally:
_service_stop(name)
def test_service_start_by_starting_same_service_twice():
name, version = _package_add_universe('kafka')
try:
_service_start(name, version)
stderr = b'The DC/OS service has already been started\n'
_service_start_failure(name, version, stderr=stderr)
finally:
_service_stop(name)
def test_service_start_by_starting_service_not_added():
stderr = b'Package [foo] not found\n'
_service_start_failure('foo', stderr=stderr)
def _service_stop_cmd(package_name):
return ['dcos', 'package', 'uninstall', package_name]
def _service_list_cmd():
return ['dcos', 'package', 'list', '--json']
def _service_start_cmd(package_name,
package_version=None,
options=None,
json=False):
return (command_base +
(['service', 'start']) +
(['--json'] if json else []) +
([package_name]) +
(['--package-version', package_version]
if package_version else []) +
(['--options', options] if options else []))
def _package_add_cmd(dcos_package=None,
package_name=None,
package_version=None,
json=False):
return (command_base +
(['package', 'add']) +
(['--json'] if json else []) +
(['--dcos-package', dcos_package] if dcos_package else []) +
(['--package-name', package_name] if package_name else []) +
(['--package-version', package_version]
if package_version else []))
def _package_build_cmd(build_definition,
output_directory=None,
expects_json=False):
return (command_base +
(['package', 'build']) +
(['--json'] if expects_json else []) +
(['--output-directory', output_directory]
if output_directory else []) +
([build_definition]))
def _service_stop(package_name):
command = _service_stop_cmd(package_name)
exec_command(command)
watch_all_deployments()
def _service_list():
command = _service_list_cmd()
code, out, err = exec_command(command)
assert code == 0
assert err == b''
return json.loads(out.decode())
def _service_start(package_name,
package_version,
options=None,
expects_json=False):
command = _service_start_cmd(package_name,
package_version,
options,
json=expects_json)
code = 1
max_retries = 10
retry_number = 0
while code != 0:
code, out, err = exec_command(command)
assert retry_number != max_retries, \
'Waiting for package add to complete took too long'
retry_number += 1
time.sleep(5)
assert code == 0
assert err == b''
if expects_json:
expected = {
'packageName': package_name,
'packageVersion': package_version
}
actual = json.loads(out.decode())
actual.pop('appId')
assert expected == actual, (expected, actual)
else:
stdout = 'The service [{}] version [{}] has been started\n'.format(
package_name, package_version).encode()
assert out == stdout, (out, stdout)
running_services = _service_list()
assert package_name in map(lambda pkg: pkg['name'], running_services)
def _service_start_failure(package_name,
package_version=None,
options=None,
return_code=1,
stdout=b'',
stderr=b''):
command = _service_start_cmd(package_name,
package_version,
options)
assert_command(command,
returncode=return_code,
stdout=stdout,
stderr=stderr)
def _package_add(package, expects_json=False):
command = _package_add_cmd(dcos_package=package, json=expects_json)
code, out, err = exec_command(command)
assert code == 0
assert err == b''
if expects_json:
metadata = json.loads(out.decode())
metadata.pop('releaseVersion')
assert metadata == zip_contents_as_json(package, 'metadata.json')
else:
metadata = zip_contents_as_json(package, 'metadata.json')
stdout = (
'The package [{}] version [{}] has been added to DC/OS\n'.format(
metadata['name'], metadata['version'])).encode()
assert out == stdout, (out, stdout)
return metadata['name'], metadata['version']
def _package_add_universe(package_name,
package_version=None,
expects_json=False):
command = _package_add_cmd(package_name=package_name,
package_version=package_version,
json=expects_json)
code, out, err = exec_command(command)
assert code == 0
assert err == b''
if expects_json:
metadata = json.loads(out.decode())
name = metadata['name']
version = metadata['version']
else:
name_version = re.search("\[(.*)\].*\[(.*)\]", out.decode())
name = name_version.group(1)
version = name_version.group(2)
stdout = (
'The package [{}] version [{}] has been added to DC/OS\n'.format(
name, version)).encode()
assert out == stdout, (out, stdout)
assert name == package_name
assert version == package_version if package_version else True
return name, version
def _package_build(build_definition_path,
output_directory,
metadata=None,
manifest=None,
expects_json=False):
command = _package_build_cmd(build_definition_path,
output_directory,
expects_json=expects_json)
code, out, err = exec_command(command)
assert code == 0
assert err == b''
out_str = out.decode()
if expects_json:
out_json = json.loads(out_str)
assert out_json, out_str
package_path = out_json.get('package_path')
else:
assert out_str.startswith("Created DC/OS Universe Package")
package_path = re.search("\[(.*)\]", out_str).group(1)
assert package_path, out_str
assert os.path.exists(package_path)
name, version, md5 = _decompose_name(package_path)
build_definition = file_json_ast(build_definition_path)
assert name == build_definition['name']
assert version == build_definition['version']
assert md5 == _get_md5_hash(package_path)
assert (manifest is None or
manifest == zip_contents_as_json(package_path, 'manifest.json'))
assert (metadata is None or
metadata == zip_contents_as_json(package_path, 'metadata.json'))
return package_path
def _package_build_failure(build_definition_path,
output_directory,
return_code=1,
stdout=b'',
stderr=b''):
command = _package_build_cmd(build_definition_path, output_directory)
assert_command(command,
returncode=return_code,
stdout=stdout,
stderr=stderr)
assert len(os.listdir(output_directory)) == 0
def _successful_package_build_test(
build_definition_path,
expected_package_path=os.path.join(
build_data_dir,
"package_no_references.json"),
expects_json=False):
with _temporary_directory() as output_directory:
metadata = file_json_ast(expected_package_path)
manifest = {
'built-by': "dcoscli.version={}".format(dcoscli.version)
}
_package_build(build_definition_path,
output_directory,
metadata=metadata,
manifest=manifest,
expects_json=expects_json)
def _decompose_name(package_path):
parts = re.search(
'^([^-]+)-(.+)-([^-]+)\.dcos',
os.path.basename(package_path))
assert parts is not None, package_path
return parts.group(1), parts.group(2), parts.group(3)
def _get_md5_hash(path):
with open(path, 'rb') as f:
return util.md5_hash_file(f)
@contextlib.contextmanager
def _temporary_directory():
tmp_dir = tempfile.mkdtemp()
try:
yield tmp_dir
finally:
shutil.rmtree(tmp_dir)