aboutsummaryrefslogtreecommitdiffstats
path: root/molecule/default/tests/test_client_setup.py
diff options
context:
space:
mode:
Diffstat (limited to 'molecule/default/tests/test_client_setup.py')
-rw-r--r--molecule/default/tests/test_client_setup.py329
1 files changed, 254 insertions, 75 deletions
diff --git a/molecule/default/tests/test_client_setup.py b/molecule/default/tests/test_client_setup.py
index 78e751a..231b946 100644
--- a/molecule/default/tests/test_client_setup.py
+++ b/molecule/default/tests/test_client_setup.py
@@ -1,5 +1,7 @@
"""Tests for client setup configuration"""
+import shlex
+
import pytest
testinfra_hosts = [
@@ -8,6 +10,7 @@ testinfra_hosts = [
"borg-client-multi",
"borg-client-nonroot",
"borg-client-multi-keys",
+ "borg-client-transition",
]
@@ -17,6 +20,7 @@ CLIENT_USER_MAP = {
"borg-client-multi": "root",
"borg-client-nonroot": "backupuser",
"borg-client-multi-keys": "root",
+ "borg-client-transition": "root",
}
CLIENT_SSH_KEY_TYPE_MAP = {
@@ -25,6 +29,7 @@ CLIENT_SSH_KEY_TYPE_MAP = {
"borg-client-multi": "rsa",
"borg-client-nonroot": "rsa",
"borg-client-multi-keys": "ed25519",
+ "borg-client-transition": "rsa",
}
CLIENT_SSH_KEY_PER_REPO_MAP = {
@@ -33,6 +38,7 @@ CLIENT_SSH_KEY_PER_REPO_MAP = {
"borg-client-multi": False,
"borg-client-nonroot": False,
"borg-client-multi-keys": True,
+ "borg-client-transition": False,
}
@@ -56,6 +62,76 @@ def get_client_home(host):
return f"/home/{user}" if user != "root" else "/root"
+def get_backup_script_paths(hostname):
+ if hostname == "borg-client-multi":
+ return [
+ "/usr/local/bin/run_borg_backup@configs",
+ "/usr/local/bin/run_borg_backup@home-data",
+ ]
+ if hostname == "borg-client-multi-keys":
+ return [
+ "/usr/local/bin/run_borg_backup@configs-keys",
+ "/usr/local/bin/run_borg_backup@home-data-keys",
+ ]
+ if hostname == "borg-client-nonroot":
+ return ["/usr/local/bin/run_borg_backup@borg-server"]
+ if hostname == "borg-client":
+ return ["/usr/local/bin/run_borg_backup@borg-server"]
+ if hostname == "borg-client-2":
+ return ["/usr/local/bin/run_borg_backup@borg-server-2"]
+ if hostname == "borg-client-transition":
+ return ["/usr/local/bin/run_borg_backup@transition-repo"]
+ return []
+
+
+def get_prune_script_paths(hostname):
+ if hostname == "borg-client":
+ return ["/usr/local/bin/run_borg_prune@borg-server"]
+ if hostname == "borg-client-2":
+ return ["/usr/local/bin/run_borg_prune@borg-server-2"]
+ if hostname == "borg-client-transition":
+ return ["/usr/local/bin/run_borg_prune@transition-repo"]
+ return []
+
+
+def run_script_with_fake_borg(host, script_path, failed_command, failed_status):
+ """Run a generated script with a fake borg and return its result and calls."""
+ temp_dir_result = host.run("mktemp -d /tmp/borg-exit-test.XXXXXX")
+ assert temp_dir_result.rc == 0
+ temp_dir = temp_dir_result.stdout.strip()
+ fake_borg_path = f"{temp_dir}/borg"
+ log_path = f"{temp_dir}/calls"
+
+ fake_borg = """#!/bin/bash
+printf '%s\\n' "$1" >> "${BORG_TEST_LOG}"
+if [ "$1" = "${BORG_FAIL_COMMAND}" ]; then
+ exit "${BORG_FAIL_STATUS}"
+fi
+exit 0
+"""
+ setup = host.run(
+ f"cat > {shlex.quote(fake_borg_path)} <<'EOF'\n"
+ f"{fake_borg}"
+ "EOF\n"
+ f"chmod 0755 {shlex.quote(fake_borg_path)}"
+ )
+ assert setup.rc == 0
+
+ try:
+ result = host.run(
+ "env "
+ f"BORG_FAIL_COMMAND={shlex.quote(failed_command)} "
+ f"BORG_FAIL_STATUS={failed_status} "
+ f"BORG_TEST_LOG={shlex.quote(log_path)} "
+ f"PATH={shlex.quote(temp_dir)}:$PATH "
+ f"{shlex.quote(script_path)}"
+ )
+ calls = host.file(log_path).content_string.splitlines()
+ return result, calls
+ finally:
+ host.run(f"rm -rf {shlex.quote(temp_dir)}")
+
+
class TestSSHSetup:
def test_ssh_directory_exists(self, host):
client_home = get_client_home(host)
@@ -162,8 +238,8 @@ class TestBackupScript:
assert script.user == client_user
assert script.group == client_user
assert script.mode == 0o711
- elif hostname in ("borg-client", "borg-client-2"):
- script = host.file("/usr/local/bin/run_borg_backup")
+ elif hostname in ("borg-client", "borg-client-2", "borg-client-transition"):
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.exists
assert script.user == client_user
assert script.group == client_user
@@ -187,12 +263,156 @@ class TestBackupScript:
elif hostname == "borg-client-nonroot":
script = host.file("/usr/local/bin/run_borg_backup@borg-server")
assert script.contains("borg create")
- elif hostname in ("borg-client", "borg-client-2"):
- script = host.file("/usr/local/bin/run_borg_backup")
+ elif hostname in ("borg-client", "borg-client-2", "borg-client-transition"):
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.contains("borg create")
+ assert not script.contains("borg prune")
+ assert not script.contains("borg compact")
else:
pytest.fail(f"Unexpected hostname: {hostname}")
+
+class TestPruneScript:
+ def test_prune_script_created_when_pruning_enabled(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname not in ("borg-client", "borg-client-2", "borg-client-transition"):
+ return
+
+ for script_path in get_prune_script_paths(hostname):
+ script = host.file(script_path)
+ assert script.exists
+ assert script.mode == 0o711
+ assert script.contains("borg prune")
+ assert script.contains("borg compact")
+
+ def test_scripts_have_valid_shell_syntax(self, host):
+ hostname = host.backend.get_hostname()
+
+ for script_path in get_backup_script_paths(hostname) + get_prune_script_paths(
+ hostname
+ ):
+ result = host.run(f"bash -n {script_path}")
+ assert result.rc == 0
+ assert "ANSIBLE MANAGED BLOCK" not in host.file(script_path).content_string
+
+ def test_after_backup_prune_script_flow_and_options(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client":
+ return
+
+ backup = host.file("/usr/local/bin/run_borg_backup@borg-server")
+ prune = host.file("/usr/local/bin/run_borg_prune@borg-server")
+ content = prune.content_string
+
+ assert "borg create" in backup.content_string
+ assert "borg prune" not in backup.content_string
+ assert "borg compact" not in backup.content_string
+
+ prune_index = content.index("borg prune")
+ prune_status_index = content.index("borg_prune_exit=$?")
+ prune_guard_index = content.index('if [ "${borg_prune_exit}" -ne 0 ]; then')
+ compact_index = content.index("borg compact")
+
+ assert prune_index < prune_status_index < prune_guard_index < compact_index
+ assert "--checkpoint-interval 1800" in content
+ assert "--glob-archives '{hostname}-*'" in content
+ assert "--keep-last 2" in content
+ assert "--stats" in content
+ assert "--threshold 1" in content
+
+ def test_timer_prune_script_flow_and_options(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client-2":
+ return
+
+ backup_script = host.file("/usr/local/bin/run_borg_backup@borg-server-2")
+ prune_script = host.file("/usr/local/bin/run_borg_prune@borg-server-2")
+ prune_content = prune_script.content_string
+
+ assert "borg prune" not in backup_script.content_string
+ assert "borg compact" not in backup_script.content_string
+
+ prune_index = prune_content.index("borg prune")
+ prune_status_index = prune_content.index("borg_prune_exit=$?")
+ prune_guard_index = prune_content.index(
+ 'if [ "${borg_prune_exit}" -ne 0 ]; then'
+ )
+ compact_index = prune_content.index("borg compact")
+
+ assert prune_index < prune_status_index < prune_guard_index < compact_index
+ assert "--checkpoint-interval 1800" in prune_content
+ assert "--glob-archives '{hostname}-*'" in prune_content
+ assert "--keep-daily 7" in prune_content
+ assert "--keep-weekly 4" in prune_content
+ assert "--stats" in prune_content
+ assert "--threshold 10" in prune_content
+
+ def test_transition_host_uses_separate_prune_script(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client-transition":
+ return
+
+ backup = host.file("/usr/local/bin/run_borg_backup@transition-repo")
+ prune = host.file("/usr/local/bin/run_borg_prune@transition-repo")
+
+ assert "borg create" in backup.content_string
+ assert "borg prune" not in backup.content_string
+ assert "borg prune" in prune.content_string
+ assert "borg compact" in prune.content_string
+ assert "--keep-last 2" in prune.content_string
+ assert "--glob-archives '{hostname}-*'" in prune.content_string
+
+ def test_backup_propagates_create_failure_status(self, host):
+ if host.backend.get_hostname() != "borg-client":
+ return
+
+ result, calls = run_script_with_fake_borg(
+ host,
+ "/usr/local/bin/run_borg_backup@borg-server",
+ "create",
+ 42,
+ )
+
+ assert result.rc == 42
+ assert calls == ["create"]
+
+ def test_after_backup_preserves_borg_warning_status(self, host):
+ if host.backend.get_hostname() != "borg-client":
+ return
+
+ result, calls = run_script_with_fake_borg(
+ host,
+ "/usr/local/bin/run_borg_backup@borg-server",
+ "create",
+ 1,
+ )
+
+ assert result.rc == 1
+ assert calls == ["create"]
+
+ @pytest.mark.parametrize(
+ ("failed_command", "failed_status", "expected_calls"),
+ [
+ ("prune", 43, ["prune"]),
+ ("compact", 44, ["prune", "compact"]),
+ ],
+ )
+ def test_timer_prune_propagates_failure_status(
+ self, host, failed_command, failed_status, expected_calls
+ ):
+ if host.backend.get_hostname() != "borg-client-2":
+ return
+
+ result, calls = run_script_with_fake_borg(
+ host,
+ "/usr/local/bin/run_borg_prune@borg-server-2",
+ failed_command,
+ failed_status,
+ )
+
+ assert result.rc == failed_status
+ assert calls == expected_calls
+
def test_backup_script_contains_compression(self, host):
hostname = host.backend.get_hostname()
@@ -206,18 +426,15 @@ class TestBackupScript:
script2 = host.file("/usr/local/bin/run_borg_backup@home-data-keys")
assert script1.contains("-C zstd")
assert script2.contains("-C lz4")
- elif hostname == "borg-client-2":
- script = host.file("/usr/local/bin/run_borg_backup")
- assert script.contains("-C")
- assert script.contains("lz4")
- elif hostname in ("borg-client", "borg-client-nonroot"):
- script = (
- host.file("/usr/local/bin/run_borg_backup")
- if hostname == "borg-client"
- else host.file("/usr/local/bin/run_borg_backup@borg-server")
- )
+ elif hostname in (
+ "borg-client",
+ "borg-client-2",
+ "borg-client-nonroot",
+ "borg-client-transition",
+ ):
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.contains("-C")
- assert script.contains("zstd")
+ assert script.contains("lz4" if hostname == "borg-client-2" else "zstd")
else:
pytest.fail(f"Unexpected hostname: {hostname}")
@@ -243,13 +460,17 @@ class TestBackupScript:
assert script.contains("borg@borg-server")
assert script.contains("/opt/borg")
elif hostname == "borg-client":
- script = host.file("/usr/local/bin/run_borg_backup")
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.contains("borg@borg-server")
assert script.contains("/opt/borg")
elif hostname == "borg-client-2":
- script = host.file("/usr/local/bin/run_borg_backup")
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.contains("backupserver@borg-server-2")
assert script.contains("/var/backups")
+ elif hostname == "borg-client-transition":
+ script = host.file("/usr/local/bin/run_borg_backup@transition-repo")
+ assert script.contains("borg@borg-server")
+ assert script.contains("/opt/borg/transition-repo")
else:
pytest.fail(f"Unexpected hostname: {hostname}")
@@ -272,8 +493,8 @@ class TestBackupScript:
script = host.file("/usr/local/bin/run_borg_backup@borg-server")
content = script.content_string
assert "/etc" in content
- elif hostname in ("borg-client", "borg-client-2"):
- script = host.file("/usr/local/bin/run_borg_backup")
+ elif hostname in ("borg-client", "borg-client-2", "borg-client-transition"):
+ script = host.file(get_backup_script_paths(hostname)[0])
content = script.content_string
assert "/etc" in content or "/home" in content
else:
@@ -295,8 +516,8 @@ class TestBackupScript:
elif hostname == "borg-client-nonroot":
script = host.file("/usr/local/bin/run_borg_backup@borg-server")
assert script.mode == 0o711
- elif hostname in ("borg-client", "borg-client-2"):
- script = host.file("/usr/local/bin/run_borg_backup")
+ elif hostname in ("borg-client", "borg-client-2", "borg-client-transition"):
+ script = host.file(get_backup_script_paths(hostname)[0])
assert script.mode == 0o711
else:
pytest.fail(f"Unexpected hostname: {hostname}")
@@ -313,7 +534,7 @@ class TestBackupScript:
assert "--one-file-system" in script2.content_string
assert "--exclude-caches" in script2.content_string
elif hostname == "borg-client-2":
- script = host.file("/usr/local/bin/run_borg_backup")
+ script = host.file(get_backup_script_paths(hostname)[0])
content = script.content_string
assert "-C zlib,6" in content
@@ -322,7 +543,7 @@ class TestBackupScript:
if hostname != "borg-client-2":
return
- script = host.file("/usr/local/bin/run_borg_backup")
+ script = host.file(get_backup_script_paths(hostname)[0])
content = script.content_string
assert "-C lz4" in content
@@ -344,57 +565,15 @@ class TestBackupScript:
assert "ssh -i" in script2.content_string
-class TestMultiInstanceBaseScript:
- def test_base_script_exists(self, host):
- hostname = host.backend.get_hostname()
- if hostname != "borg-client-multi":
- return
-
- client_user = get_client_user(host)
- base_script = host.file("/usr/local/bin/run_borg_backup")
- assert base_script.exists
- assert base_script.user == client_user
- assert base_script.mode == 0o711
-
- def test_base_script_contains_both_blocks(self, host):
- hostname = host.backend.get_hostname()
- if hostname != "borg-client-multi":
- return
-
- base_script = host.file("/usr/local/bin/run_borg_backup")
- content = base_script.content_string
+class TestAggregateScripts:
+ def test_legacy_aggregate_backup_script_is_preserved(self, host):
+ aggregate = host.file("/usr/local/bin/run_borg_backup")
- assert "borg-server/configs" in content
- assert "borg-server/home-data" in content
-
- def test_base_script_contains_both_repos(self, host):
- hostname = host.backend.get_hostname()
- if hostname != "borg-client-multi":
- return
-
- base_script = host.file("/usr/local/bin/run_borg_backup")
- content = base_script.content_string
-
- assert "/opt/borg/configs" in content
- assert "/opt/borg/home-data" in content
-
- def test_base_script_contains_both_compressions(self, host):
- hostname = host.backend.get_hostname()
- if hostname != "borg-client-multi":
- return
-
- base_script = host.file("/usr/local/bin/run_borg_backup")
- content = base_script.content_string
-
- assert "-C zstd" in content
- assert "-C lz4" in content
-
- def test_base_script_two_borg_create_commands(self, host):
- hostname = host.backend.get_hostname()
- if hostname != "borg-client-multi":
- return
-
- base_script = host.file("/usr/local/bin/run_borg_backup")
- content = base_script.content_string
+ if host.backend.get_hostname() == "borg-client-transition":
+ assert aggregate.exists
+ assert "legacy-backup-block" in aggregate.content_string
+ else:
+ assert not aggregate.exists
- assert content.count("borg create") == 2
+ def test_aggregate_prune_script_is_not_created(self, host):
+ assert not host.file("/usr/local/bin/run_borg_prune").exists