505 lines
16 KiB
Python
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)
|