204 lines
6.7 KiB
Python
204 lines
6.7 KiB
Python
import json
|
|
import re
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from .helpers.common import assert_command, exec_command
|
|
from .helpers.marathon import (add_pod, pod, pod_spec_json, pods, remove_pod,
|
|
watch_all_deployments)
|
|
from ..fixtures.marathon import (DOUBLE_POD_FILE_PATH, DOUBLE_POD_ID,
|
|
GOOD_POD_FILE_PATH, GOOD_POD_ID,
|
|
POD_KILL_FILE_PATH, POD_KILL_ID,
|
|
pod_list_fixture, TRIPLE_POD_FILE_PATH,
|
|
TRIPLE_POD_ID, UNGOOD_POD_FILE_PATH,
|
|
UPDATED_GOOD_POD_FILE_PATH)
|
|
|
|
_POD_BASE_CMD = ['dcos', 'marathon', 'pod']
|
|
_POD_ADD_CMD = _POD_BASE_CMD + ['add']
|
|
_POD_KILL_CMD = _POD_BASE_CMD + ['kill']
|
|
_POD_LIST_CMD = _POD_BASE_CMD + ['list']
|
|
_POD_REMOVE_CMD = _POD_BASE_CMD + ['remove']
|
|
_POD_SHOW_CMD = _POD_BASE_CMD + ['show']
|
|
_POD_UPDATE_CMD = _POD_BASE_CMD + ['update']
|
|
|
|
|
|
def test_pod_add_from_file():
|
|
add_pod(GOOD_POD_FILE_PATH)
|
|
remove_pod(GOOD_POD_ID, force=False)
|
|
watch_all_deployments()
|
|
|
|
|
|
def test_pod_list():
|
|
expected_json = pod_list_fixture()
|
|
|
|
with pods({GOOD_POD_FILE_PATH: GOOD_POD_ID,
|
|
DOUBLE_POD_FILE_PATH: DOUBLE_POD_ID,
|
|
TRIPLE_POD_FILE_PATH: TRIPLE_POD_ID}):
|
|
|
|
_assert_pod_list_json(expected_json)
|
|
_assert_pod_list_table()
|
|
|
|
|
|
def test_pod_update_does_not_support_properties():
|
|
cmd = _POD_UPDATE_CMD + ['any-pod', 'foo=bar']
|
|
returncode, stdout, stderr = exec_command(cmd)
|
|
|
|
assert returncode == 1
|
|
assert stdout.startswith(b'Command not recognized\n')
|
|
assert stderr == b''
|
|
|
|
|
|
def test_pod_update_from_stdin():
|
|
with pod(GOOD_POD_FILE_PATH, GOOD_POD_ID):
|
|
# The deployment will never complete
|
|
_assert_pod_update_from_stdin(
|
|
extra_args=[],
|
|
pod_json_file_path=UNGOOD_POD_FILE_PATH)
|
|
|
|
# Override above failed deployment
|
|
_assert_pod_update_from_stdin(
|
|
extra_args=['--force'],
|
|
pod_json_file_path=UPDATED_GOOD_POD_FILE_PATH)
|
|
|
|
watch_all_deployments()
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
True, reason='https://mesosphere.atlassian.net/browse/DCOS-13368')
|
|
def test_pod_kill():
|
|
with pod(POD_KILL_FILE_PATH, POD_KILL_ID):
|
|
kill_1, keep, kill_2 = _get_pod_instance_ids(POD_KILL_ID, 3)
|
|
|
|
remove_args = [POD_KILL_ID, kill_1, kill_2]
|
|
assert_command(_POD_KILL_CMD + remove_args)
|
|
|
|
new_instance_ids = _get_pod_instance_ids(POD_KILL_ID, 3)
|
|
assert keep in new_instance_ids
|
|
assert kill_1 not in new_instance_ids
|
|
assert kill_2 not in new_instance_ids
|
|
# Marathon spins up new instances to replace the killed ones
|
|
assert len(new_instance_ids) == 3
|
|
|
|
|
|
def _pod_add_from_stdin(file_path):
|
|
cmd = _POD_ADD_CMD
|
|
with open(file_path) as fd:
|
|
returncode, stdout, stderr = exec_command(cmd, stdin=fd)
|
|
|
|
assert returncode == 0
|
|
assert re.fullmatch('Created deployment \S+\n', stdout.decode('utf-8'))
|
|
assert stderr == b''
|
|
|
|
watch_all_deployments()
|
|
|
|
|
|
def _assert_pod_list_json(expected_json):
|
|
cmd = _POD_LIST_CMD + ['--json']
|
|
returncode, stdout, stderr = exec_command(cmd)
|
|
assert returncode == 0
|
|
assert stderr == b''
|
|
|
|
actual_json = json.loads(stdout.decode('utf-8'))
|
|
_assert_pod_list_json_subset(expected_json, actual_json)
|
|
|
|
|
|
def _assert_pod_list_json_subset(expected_json, actual_json):
|
|
actual_pods_by_id = {pod['id']: pod for pod in actual_json}
|
|
|
|
for expected_pod in expected_json:
|
|
pod_id = expected_pod['id']
|
|
actual_pod = actual_pods_by_id[pod_id]
|
|
pod_spec_json(expected_pod['spec'], actual_pod['spec'])
|
|
|
|
assert len(actual_json) == len(expected_json)
|
|
|
|
|
|
def _assert_pod_list_table():
|
|
_wait_for_instances({'/double-pod': 2, '/good-pod': 1, '/winston': 1})
|
|
returncode, stdout, stderr = exec_command(_POD_LIST_CMD)
|
|
|
|
assert returncode == 0
|
|
assert stderr == b''
|
|
|
|
stdout_lines = stdout.decode('utf-8').split('\n')
|
|
|
|
pattern = r'ID\+TASKS +INSTANCES +VERSION +STATUS +STATUS SINCE +WAITING *'
|
|
assert re.fullmatch(pattern, stdout_lines[0])
|
|
|
|
assert stdout_lines[1].startswith('/double-pod')
|
|
assert stdout_lines[2].startswith(' |-thing-1')
|
|
assert stdout_lines[3].startswith(' |-thing-2')
|
|
assert stdout_lines[4].startswith('/good-pod')
|
|
assert stdout_lines[5].startswith(' |-good-container')
|
|
assert stdout_lines[6].startswith('/winston')
|
|
assert stdout_lines[7].startswith(' |-the-cat')
|
|
assert stdout_lines[8].startswith(' |-thing-1')
|
|
assert stdout_lines[9].startswith(' |-thing-2')
|
|
|
|
assert stdout_lines[10] == ''
|
|
assert len(stdout_lines) == 11
|
|
|
|
|
|
def _assert_pod_update_from_stdin(extra_args, pod_json_file_path):
|
|
cmd = _POD_UPDATE_CMD + [GOOD_POD_ID] + extra_args
|
|
with open(pod_json_file_path) as fd:
|
|
returncode, stdout, stderr = exec_command(cmd, stdin=fd)
|
|
|
|
assert returncode == 0
|
|
assert re.fullmatch('Created deployment \S+\n', stdout.decode('utf-8'))
|
|
assert stderr == b''
|
|
|
|
|
|
def _wait_for_instances(expected_instances, max_attempts=10):
|
|
"""Polls the `pod list` command until the instance counts are as expected.
|
|
|
|
:param expected_instances: a mapping from pod ID to instance count
|
|
:type expected_instances: {}
|
|
:param max_attempts: give up and fail the test after this many attempts
|
|
:type max_attempts: int
|
|
:rtype: None
|
|
"""
|
|
|
|
for attempt in range(max_attempts):
|
|
returncode, stdout, stderr = exec_command(_POD_LIST_CMD + ['--json'])
|
|
|
|
assert returncode == 0
|
|
assert stderr == b''
|
|
|
|
status_json = json.loads(stdout.decode('utf-8'))
|
|
actual_instances = {pod_obj['id']: len(pod_obj.get('instances', []))
|
|
for pod_obj in status_json}
|
|
|
|
if actual_instances == expected_instances:
|
|
return
|
|
time.sleep(1)
|
|
else:
|
|
assert False, "Timed out waiting for expected instance counts"
|
|
|
|
|
|
def _get_pod_instance_ids(pod_id, target_instance_count):
|
|
"""Waits for the given pod to reach a target instance count, then returns
|
|
the IDs of all instances.
|
|
|
|
:param pod_id: the pod to retrieve the instance IDs from
|
|
:type pod_id: str
|
|
:param target_instance_count: waits until the number of instances reaches
|
|
this number
|
|
:type target_instance_count: int
|
|
:returns: a tuple of the pod's instance IDs
|
|
:rtype: tuple(str)
|
|
"""
|
|
|
|
_wait_for_instances({'/{}'.format(pod_id): target_instance_count})
|
|
|
|
show_cmd = _POD_SHOW_CMD + [pod_id]
|
|
returncode, stdout, stderr = exec_command(show_cmd)
|
|
|
|
assert returncode == 0
|
|
assert stderr == b''
|
|
|
|
pod_status_json = json.loads(stdout.decode('utf-8'))
|
|
instances_json = pod_status_json['instances']
|
|
return tuple(instance['id'] for instance in instances_json)
|