aboutsummaryrefslogtreecommitdiffstats
path: root/molecule/default/tests
diff options
context:
space:
mode:
Diffstat (limited to 'molecule/default/tests')
-rw-r--r--molecule/default/tests/test_client_setup.py329
-rw-r--r--molecule/default/tests/test_manual_backup.py122
-rw-r--r--molecule/default/tests/test_server_setup.py2
-rw-r--r--molecule/default/tests/test_systemd.py84
4 files changed, 461 insertions, 76 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
diff --git a/molecule/default/tests/test_manual_backup.py b/molecule/default/tests/test_manual_backup.py
index 8cd044c..ea48655 100644
--- a/molecule/default/tests/test_manual_backup.py
+++ b/molecule/default/tests/test_manual_backup.py
@@ -84,3 +84,125 @@ def test_backup_restore(host, compression):
assert c.rc == 0
assert c.stdout == ""
assert c.stderr == ""
+
+
+def test_after_backup_scripts_prune_old_archives(host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client":
+ return
+
+ server_user, server_host, server_path = get_server_info(hostname)
+ backup_script_path = "/usr/local/bin/run_borg_backup@borg-server"
+ prune_script_path = "/usr/local/bin/run_borg_prune@borg-server"
+
+ original_backup_script = host.file(backup_script_path).content_string
+ original_prune_script = host.file(prune_script_path).content_string
+ prefix = f"testprune-{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
+
+ modified_backup_script = original_backup_script.replace(
+ "::{hostname}-{now:%Y-%m-%dT%H:%M:%S}",
+ f"::{prefix}-${{BORG_TEST_ITERATION}}-{{now:%Y-%m-%dT%H:%M:%S}}",
+ )
+ modified_prune_script = original_prune_script.replace(
+ "{hostname}-*",
+ f"{prefix}-*",
+ )
+
+ rewrite = host.run(
+ "python3 - <<'PY'\n"
+ "from pathlib import Path\n"
+ f"Path({backup_script_path!r}).write_text({modified_backup_script!r})\n"
+ f"Path({prune_script_path!r}).write_text({modified_prune_script!r})\n"
+ "PY"
+ )
+ assert rewrite.rc == 0
+ assert host.run(f"bash -n {backup_script_path}").rc == 0
+ assert host.run(f"bash -n {prune_script_path}").rc == 0
+
+ try:
+ for iteration in range(4):
+ backup = host.run(f"BORG_TEST_ITERATION={iteration} {backup_script_path}")
+ assert backup.rc == 0
+ prune = host.run(prune_script_path)
+ assert prune.rc == 0
+
+ archives = host.run(
+ f"borg list {server_user}@{server_host}:{server_path}/{hostname} --glob-archives '{prefix}-*'"
+ )
+ assert archives.rc == 0
+
+ archive_lines = [line for line in archives.stdout.splitlines() if line.strip()]
+ assert len(archive_lines) == 2
+ finally:
+ restore = host.run(
+ "python3 - <<'PY'\n"
+ "from pathlib import Path\n"
+ f"Path({backup_script_path!r}).write_text({original_backup_script!r})\n"
+ f"Path({prune_script_path!r}).write_text({original_prune_script!r})\n"
+ "PY"
+ )
+ assert restore.rc == 0
+
+
+def test_manual_prune_script_prunes_old_archives(host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client-2":
+ return
+
+ server_user, server_host, server_path = get_server_info(hostname)
+ prefix = f"testprune-timer-{datetime.now().strftime('%Y%m%d%H%M%S%f')}"
+ script_path = "/usr/local/bin/run_borg_prune@borg-server-2"
+
+ for iteration in range(4):
+ create = host.run(
+ f"borg create -C lz4 {server_user}@{server_host}:{server_path}/{hostname}::{prefix}-{iteration}-{{now:%Y-%m-%dT%H:%M:%S}} /etc"
+ )
+ assert create.rc == 0
+
+ original_script = host.file(script_path).content_string
+ assert "--keep-daily 7" in original_script
+ assert "--keep-weekly 4" in original_script
+
+ modified_script = (
+ original_script.replace("{hostname}-*", f"{prefix}-*")
+ .replace("--keep-daily 7", "--keep-last 2")
+ .replace("--keep-weekly 4", "")
+ )
+ assert "--keep-last 2" in modified_script
+ assert "--keep-daily 7" not in modified_script
+ assert "--keep-weekly 4" not in modified_script
+
+ rewrite = host.run(
+ "python3 - <<'PY'\n"
+ "from pathlib import Path\n"
+ f"Path({script_path!r}).write_text({modified_script!r})\n"
+ "PY"
+ )
+ assert rewrite.rc == 0
+ syntax_check = host.run(f"bash -n {script_path}")
+ assert syntax_check.rc == 0
+
+ try:
+ result = host.run(script_path)
+ assert result.rc == 0
+
+ archives = host.run(
+ f"borg list {server_user}@{server_host}:{server_path}/{hostname} --glob-archives '{prefix}-*'"
+ )
+ assert archives.rc == 0
+
+ archive_lines = [line for line in archives.stdout.splitlines() if line.strip()]
+ assert len(archive_lines) == 2
+ finally:
+ restore = host.run(
+ "python3 - <<'PY'\n"
+ "from pathlib import Path\n"
+ f"Path({script_path!r}).write_text({original_script!r})\n"
+ "PY"
+ )
+ assert restore.rc == 0
+
+ cleanup = host.run(
+ f"borg delete --glob-archives '{prefix}-*' {server_user}@{server_host}:{server_path}/{hostname}"
+ )
+ assert cleanup.rc == 0
diff --git a/molecule/default/tests/test_server_setup.py b/molecule/default/tests/test_server_setup.py
index 93c319d..9a5156b 100644
--- a/molecule/default/tests/test_server_setup.py
+++ b/molecule/default/tests/test_server_setup.py
@@ -73,6 +73,8 @@ class TestBorgSSHSetup:
"borg-client-multi",
"borg-client-nonroot",
"borg-client-multi-keys",
+ "borg-client-transition",
+ "borg-client-validation",
)
for line in content.split("\n"):
if not line.strip():
diff --git a/molecule/default/tests/test_systemd.py b/molecule/default/tests/test_systemd.py
index e68e232..297ae0d 100644
--- a/molecule/default/tests/test_systemd.py
+++ b/molecule/default/tests/test_systemd.py
@@ -8,6 +8,7 @@ testinfra_hosts = [
"borg-client-multi",
"borg-client-nonroot",
"borg-client-multi-keys",
+ "borg-client-transition",
]
CLIENT_CONFIGS = {
@@ -16,18 +17,33 @@ CLIENT_CONFIGS = {
"server": "borg-server",
"schedule": "*-*-* 02:00:00",
"success_exit_status": False,
+ "prune_enabled": True,
+ "prune_timer": False,
},
"borg-client-2": {
"user": "root",
"server": "borg-server-2",
"schedule": "*-*-* 03:00:00",
"success_exit_status": True,
+ "prune_enabled": True,
+ "prune_timer": True,
+ "prune_schedule": "*-*-* 05:00:00",
},
"borg-client-nonroot": {
"user": "backupuser",
"server": "borg-server",
"schedule": "*-*-* 02:00:00",
"success_exit_status": False,
+ "prune_enabled": False,
+ "prune_timer": False,
+ },
+ "borg-client-transition": {
+ "user": "root",
+ "server": "transition-repo",
+ "schedule": "*-*-* 02:00:00",
+ "success_exit_status": False,
+ "prune_enabled": True,
+ "prune_timer": False,
},
}
@@ -85,7 +101,19 @@ class TestSystemdServiceFile:
assert service.contains("[Service]")
assert service.contains("[Install]")
assert service.contains("Type=oneshot")
- assert service.contains("ExecStart=/usr/local/bin/run_borg_backup")
+ assert service.contains(
+ f"ExecStart=/usr/local/bin/run_borg_backup@{config['server']}"
+ )
+ assert (
+ "ExecStart=/usr/local/bin/run_borg_backup\n"
+ not in service.content_string
+ )
+ if config["prune_enabled"] and not config["prune_timer"]:
+ assert service.contains(
+ f"OnSuccess=borg_prune@{config['server']}.service"
+ )
+ else:
+ assert not service.contains("OnSuccess=")
else:
for repo in config["repos"]:
service = host.file(f"/etc/systemd/system/borg_backup@{repo}.service")
@@ -93,6 +121,9 @@ class TestSystemdServiceFile:
assert service.contains("[Service]")
assert service.contains("[Install]")
assert service.contains("Type=oneshot")
+ assert service.contains(
+ f"ExecStart=/usr/local/bin/run_borg_backup@{repo}"
+ )
def test_service_user(self, host, config):
if config["type"] == "single":
@@ -193,3 +224,54 @@ class TestSystemdState:
def test_daemon_reload_ok(self, host, config):
c = host.run("systemctl daemon-reload")
assert c.rc == 0
+
+
+class TestPruneSystemd:
+ def test_prune_service_created_when_pruning_enabled(self, host, config):
+ if config["type"] != "single":
+ return
+
+ service = host.file(
+ f"/etc/systemd/system/borg_prune@{config['server']}.service"
+ )
+ assert service.exists == config["prune_enabled"]
+ if config["prune_enabled"]:
+ assert (
+ f"ExecStart=/usr/local/bin/run_borg_prune@{config['server']}"
+ in service.content_string
+ )
+
+ def test_prune_timer_created_when_configured(self, host, config):
+ if config["type"] != "single" or not config.get("prune_timer"):
+ return
+
+ timer = host.file(f"/etc/systemd/system/borg_prune@{config['server']}.timer")
+ service = host.file(
+ f"/etc/systemd/system/borg_prune@{config['server']}.service"
+ )
+ assert timer.exists
+ assert service.exists
+ assert f"OnCalendar={config['prune_schedule']}" in timer.content_string
+ assert (
+ f"ExecStart=/usr/local/bin/run_borg_prune@{config['server']}"
+ in service.content_string
+ )
+ assert "ExecStart=/usr/local/bin/run_borg_prune\n" not in service.content_string
+ assert "SuccessExitStatus=1 TEMPFAIL" in service.content_string
+
+ def test_prune_timer_not_created_for_after_backup(self, host, config):
+ if config["type"] != "single" or config.get("prune_timer"):
+ return
+
+ timer = host.file(f"/etc/systemd/system/borg_prune@{config['server']}.timer")
+ assert not timer.exists
+
+ def test_transition_host_stale_prune_timer_removed(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname != "borg-client-transition":
+ return
+
+ timer = host.file("/etc/systemd/system/borg_prune@transition-repo.timer")
+ service = host.file("/etc/systemd/system/borg_prune@transition-repo.service")
+ assert not timer.exists
+ assert service.exists