133 lines
5.5 KiB
Python
133 lines
5.5 KiB
Python
"""Tests for what a deploy run does: deploy everything, wait for all of it, then hook."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import alakazam
|
|
from alakazam import deploy_apps
|
|
|
|
CONFIG = {"a.org": {
|
|
"nextcloud": {"app_domain": "cloud.a.org", "server": "a.org", "version": "2.0.0"},
|
|
"wekan": {"app_domain": "boards.a.org", "server": "a.org", "version": "2.0.0"},
|
|
}}
|
|
|
|
|
|
@pytest.fixture
|
|
def steps(monkeypatch):
|
|
"""Records what a deploy run does, in order, against an abra that answers every call."""
|
|
steps = []
|
|
monkeypatch.setattr(alakazam, "INSTANCE_CONFIGS", CONFIG)
|
|
monkeypatch.setattr(alakazam, "sleep", lambda seconds: steps.append(f"sleep:{seconds}"))
|
|
monkeypatch.setattr(alakazam, "execute_cmds",
|
|
lambda config, **kw: steps.append(f"hook:{config['app_domain']}"))
|
|
monkeypatch.setattr(alakazam, "get_apps", lambda recipes, **kw: {
|
|
"a.org": [[app, config["app_domain"]] for app, config in CONFIG["a.org"].items()
|
|
if not recipes or app in recipes]})
|
|
monkeypatch.setattr(alakazam, "get_apps_by_deployment", lambda recipes, deployed=True: {
|
|
"a.org": [[app, config["app_domain"]] + (["2.0.0"] if deployed else [])
|
|
for app, config in CONFIG["a.org"].items() if not recipes or app in recipes]})
|
|
|
|
def abra(*args, **kwargs):
|
|
if args[:2] == ("app", "ps"):
|
|
steps.append(f"ps:{args[2]}")
|
|
return {"app": {"service": "app", "status": "healthy", "version": "2.0.0",
|
|
"chaos": "false", "state": "running", "image": "img"}}
|
|
steps.append(" ".join(a for a in args if a))
|
|
return ""
|
|
|
|
monkeypatch.setattr(alakazam, "abra", abra)
|
|
return steps
|
|
|
|
|
|
def selected(steps, prefix):
|
|
return [step for step in steps if step.startswith(prefix)]
|
|
|
|
|
|
def run(args):
|
|
result = CliRunner().invoke(alakazam.deploy, ["-n"] + args, standalone_mode=False)
|
|
assert result.exit_code == 0, result.exception
|
|
return result
|
|
|
|
|
|
class TestDeployHooks:
|
|
def test_the_hooks_do_not_switch_on_converge_checks(self, steps):
|
|
"""Waiting for each app in turn is what the separate flag is for."""
|
|
run(["-e"])
|
|
assert all("--no-converge-checks" in s for s in selected(steps, "app deploy"))
|
|
|
|
def test_converge_checks_come_from_their_own_flag_only(self, steps):
|
|
run(["-c"])
|
|
assert not any("--no-converge-checks" in s for s in selected(steps, "app deploy"))
|
|
|
|
def test_every_app_is_deployed_before_the_first_hook(self, steps):
|
|
run(["-e"])
|
|
deploys = selected(steps, "app deploy")
|
|
assert max(steps.index(s) for s in deploys) < steps.index("hook:cloud.a.org")
|
|
|
|
def test_the_hooks_run_after_the_wait(self, steps):
|
|
"""A hook would otherwise reach a container the deployment has not brought up yet."""
|
|
run(["-e"])
|
|
assert steps.index("ps:cloud.a.org") < steps.index("hook:cloud.a.org")
|
|
assert steps.index("ps:boards.a.org") < steps.index("hook:boards.a.org")
|
|
|
|
def test_the_health_is_only_read_after_the_services_settled(self, steps):
|
|
"""A replaced service still reports its old container, which has been healthy all along."""
|
|
run(["-e"])
|
|
assert steps.index(f"sleep:{alakazam.PS_WAIT_SETTLE}") < steps.index("ps:cloud.a.org")
|
|
assert max(steps.index(s) for s in selected(steps, "app deploy")) \
|
|
< steps.index(f"sleep:{alakazam.PS_WAIT_SETTLE}")
|
|
|
|
def test_without_the_flag_nothing_is_hooked(self, steps):
|
|
run([])
|
|
assert selected(steps, "hook:") == []
|
|
|
|
def test_the_wait_happens_without_the_flag_too(self, steps):
|
|
"""It is what tells a deploy run whether the apps came up at all."""
|
|
run([])
|
|
assert selected(steps, "ps:") == ["ps:cloud.a.org", "ps:boards.a.org"]
|
|
|
|
def test_a_filtered_run_waits_for_its_own_apps_only(self, steps):
|
|
run(["-e", "-r", "wekan"])
|
|
assert selected(steps, "ps:") == ["ps:boards.a.org"]
|
|
assert selected(steps, "hook:") == ["hook:boards.a.org"]
|
|
|
|
|
|
class TestDeployAppsWithoutWaiting:
|
|
"""setup arranges its own waiting, per dependency level and once for the whole environment."""
|
|
|
|
def test_the_wait_can_be_opted_out_of(self, steps):
|
|
deploy_apps({"a.org": [["nextcloud", "cloud.a.org"]]}, wait=False)
|
|
assert selected(steps, "ps:") == []
|
|
assert selected(steps, "sleep:") == []
|
|
|
|
def test_the_hooks_still_run_without_a_wait(self, steps):
|
|
deploy_apps({"a.org": [["nextcloud", "cloud.a.org"]]}, execute_hooks=True, wait=False)
|
|
assert selected(steps, "hook:") == ["hook:cloud.a.org"]
|
|
|
|
def test_nothing_to_deploy_skips_the_wait(self, steps):
|
|
"""Waiting on a run that did not happen would report on unrelated apps."""
|
|
deploy_apps({})
|
|
assert steps == []
|
|
|
|
|
|
class TestSettleOption:
|
|
def test_the_delay_can_be_set(self, steps):
|
|
run(["--settle", "5"])
|
|
assert "sleep:5" in steps
|
|
assert f"sleep:{alakazam.PS_WAIT_SETTLE}" not in steps
|
|
|
|
def test_zero_skips_the_delay_without_skipping_the_wait(self, steps):
|
|
"""A converge-checked run has waited already, the health still has to be confirmed."""
|
|
run(["--settle", "0"])
|
|
assert selected(steps, "sleep:") == []
|
|
assert selected(steps, "ps:") == ["ps:cloud.a.org", "ps:boards.a.org"]
|
|
|
|
def test_the_default_is_used_when_it_is_not_given(self, steps):
|
|
run([])
|
|
assert f"sleep:{alakazam.PS_WAIT_SETTLE}" in steps
|