deploy: potentially fix self-deploy issue?
This commit is contained in:
196
tests/deploy-finalize.nix
Normal file
196
tests/deploy-finalize.nix
Normal file
@@ -0,0 +1,196 @@
|
||||
# Test for modules/server-deploy-finalize.nix.
|
||||
#
|
||||
# Covers the decision and scheduling logic with fabricated profile directories,
|
||||
# since spawning a second booted NixOS toplevel to diff kernels is too heavy for
|
||||
# a runNixOSTest. We rely on the shellcheck pass baked into writeShellApplication
|
||||
# to catch syntax regressions in the script itself.
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "deploy-finalize";
|
||||
|
||||
node.specialArgs = {
|
||||
inherit inputs lib;
|
||||
username = "testuser";
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/server-deploy-finalize.nix
|
||||
];
|
||||
|
||||
services.deployFinalize = {
|
||||
enable = true;
|
||||
# Shorter default in the test to make expected-substring assertions
|
||||
# stable and reinforce that the option is wired through.
|
||||
delay = 15;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
# Test fixtures: fabricated profile trees whose kernel/initrd/kernel-modules
|
||||
# symlinks are under test control. `readlink -e` requires the targets to
|
||||
# exist, so we point at real files in /tmp rather than non-existent paths.
|
||||
machine.succeed(
|
||||
"mkdir -p /tmp/profile-same /tmp/profile-changed-kernel "
|
||||
"/tmp/profile-changed-initrd /tmp/profile-changed-modules "
|
||||
"/tmp/profile-missing /tmp/fake-targets"
|
||||
)
|
||||
machine.succeed(
|
||||
"touch /tmp/fake-targets/alt-kernel /tmp/fake-targets/alt-initrd "
|
||||
"/tmp/fake-targets/alt-modules"
|
||||
)
|
||||
|
||||
booted_kernel = machine.succeed("readlink -e /run/booted-system/kernel").strip()
|
||||
booted_initrd = machine.succeed("readlink -e /run/booted-system/initrd").strip()
|
||||
booted_modules = machine.succeed("readlink -e /run/booted-system/kernel-modules").strip()
|
||||
|
||||
def link_profile(path, kernel, initrd, modules):
|
||||
machine.succeed(f"ln -sf {kernel} {path}/kernel")
|
||||
machine.succeed(f"ln -sf {initrd} {path}/initrd")
|
||||
machine.succeed(f"ln -sf {modules} {path}/kernel-modules")
|
||||
|
||||
# profile-same: matches booted exactly → should choose `switch`.
|
||||
link_profile("/tmp/profile-same", booted_kernel, booted_initrd, booted_modules)
|
||||
machine.succeed("mkdir -p /tmp/profile-same/bin")
|
||||
machine.succeed(
|
||||
"ln -sf /run/current-system/bin/switch-to-configuration "
|
||||
"/tmp/profile-same/bin/switch-to-configuration"
|
||||
)
|
||||
|
||||
# profile-changed-kernel: kernel differs only → should choose `reboot`.
|
||||
link_profile(
|
||||
"/tmp/profile-changed-kernel",
|
||||
"/tmp/fake-targets/alt-kernel",
|
||||
booted_initrd,
|
||||
booted_modules,
|
||||
)
|
||||
|
||||
# profile-changed-initrd: initrd differs only → should choose `reboot`.
|
||||
link_profile(
|
||||
"/tmp/profile-changed-initrd",
|
||||
booted_kernel,
|
||||
"/tmp/fake-targets/alt-initrd",
|
||||
booted_modules,
|
||||
)
|
||||
|
||||
# profile-changed-modules: kernel-modules differs only → should choose `reboot`.
|
||||
# Catches the obelisk PR / nixpkgs auto-upgrade case where modules rebuild
|
||||
# against the same kernel but ABI-incompatible.
|
||||
link_profile(
|
||||
"/tmp/profile-changed-modules",
|
||||
booted_kernel,
|
||||
booted_initrd,
|
||||
"/tmp/fake-targets/alt-modules",
|
||||
)
|
||||
|
||||
# profile-missing: no kernel/initrd/kernel-modules → should fail closed.
|
||||
|
||||
with subtest("dry-run against identical profile selects switch"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-same 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
assert "action=switch" in out, out
|
||||
assert "services only" in out, out
|
||||
assert "dry-run — not scheduling" in out, out
|
||||
assert "would run: /tmp/profile-same/bin/switch-to-configuration switch" in out, out
|
||||
assert "would schedule: systemd-run" in out, out
|
||||
|
||||
with subtest("dry-run against changed-kernel profile selects reboot"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-changed-kernel 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
assert "action=reboot" in out, out
|
||||
assert "reason=kernel changed" in out, out
|
||||
assert "systemctl reboot" in out, out
|
||||
|
||||
with subtest("dry-run against changed-initrd profile selects reboot"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-changed-initrd 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
assert "action=reboot" in out, out
|
||||
assert "reason=initrd changed" in out, out
|
||||
|
||||
with subtest("dry-run against changed-modules profile selects reboot"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-changed-modules 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
assert "action=reboot" in out, out
|
||||
assert "reason=kernel-modules changed" in out, out
|
||||
|
||||
with subtest("dry-run against empty profile fails closed with rc=1"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-missing 2>&1"
|
||||
)
|
||||
assert rc == 1, f"rc={rc}\n{out}"
|
||||
assert "missing kernel, initrd, or kernel-modules" in out, out
|
||||
|
||||
with subtest("--delay override is reflected in output"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --delay 7 --profile /tmp/profile-same 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
assert "delay=7s" in out, out
|
||||
|
||||
with subtest("configured default delay from module option is used"):
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --dry-run --profile /tmp/profile-same 2>&1"
|
||||
)
|
||||
assert rc == 0, f"rc={rc}\n{out}"
|
||||
# module option delay=15 in nodes.machine above.
|
||||
assert "delay=15s" in out, out
|
||||
|
||||
with subtest("unknown option rejected with rc=2"):
|
||||
rc, out = machine.execute("deploy-finalize --bogus 2>&1")
|
||||
assert rc == 2, f"rc={rc}\n{out}"
|
||||
assert "unknown option --bogus" in out, out
|
||||
|
||||
with subtest("non-dry run arms a transient systemd timer"):
|
||||
# Long delay so the timer doesn't fire during the test. We stop it
|
||||
# explicitly afterwards.
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --delay 3600 --profile /tmp/profile-same 2>&1"
|
||||
)
|
||||
assert rc == 0, f"scheduling rc={rc}\n{out}"
|
||||
# Confirm exactly one transient timer is active.
|
||||
timers = machine.succeed(
|
||||
"systemctl list-units --type=timer --no-legend 'deploy-finalize-*.timer' "
|
||||
"--state=waiting | awk 'NF{print $1}'"
|
||||
).strip().splitlines()
|
||||
assert len(timers) == 1, f"expected exactly one pending timer, got {timers}"
|
||||
assert timers[0].startswith("deploy-finalize-"), timers
|
||||
|
||||
with subtest("back-to-back scheduling cancels the previous timer"):
|
||||
# The previous subtest left one timer armed. Schedule again; the old
|
||||
# one should be stopped before the new unit name is created.
|
||||
machine.succeed("sleep 1") # ensure a distinct unit-name timestamp
|
||||
rc, out = machine.execute(
|
||||
"deploy-finalize --delay 3600 --profile /tmp/profile-same 2>&1"
|
||||
)
|
||||
assert rc == 0, f"second-schedule rc={rc}\n{out}"
|
||||
timers = machine.succeed(
|
||||
"systemctl list-units --type=timer --no-legend 'deploy-finalize-*.timer' "
|
||||
"--state=waiting | awk 'NF{print $1}'"
|
||||
).strip().splitlines()
|
||||
assert len(timers) == 1, f"expected only the new timer, got {timers}"
|
||||
|
||||
# Clean up so the test's shutdown path is quiet.
|
||||
machine.succeed(
|
||||
"systemctl stop 'deploy-finalize-*.timer' 'deploy-finalize-*.service' "
|
||||
"2>/dev/null || true"
|
||||
)
|
||||
'';
|
||||
}
|
||||
@@ -13,6 +13,7 @@ in
|
||||
minecraftTest = handleTest ./minecraft.nix;
|
||||
jellyfinQbittorrentMonitorTest = handleTest ./jellyfin-qbittorrent-monitor.nix;
|
||||
deployGuardTest = handleTest ./deploy-guard.nix;
|
||||
deployFinalizeTest = handleTest ./deploy-finalize.nix;
|
||||
filePermsTest = handleTest ./file-perms.nix;
|
||||
|
||||
# fail2ban tests
|
||||
|
||||
Reference in New Issue
Block a user