Test the weekly upgrade's timer and its health (issue 1574, ADR 0477)
This commit is contained in:
@@ -101,3 +101,53 @@ func TestTheReflectorPackageIsDeclaredBeforeTheFileItShips(t *testing.T) {
|
||||
t.Fatalf("a package's file and timer come after the package: %v", order)
|
||||
}
|
||||
}
|
||||
|
||||
// The weekly upgrade (issue 1574, ADR 0477 clause 5): a timer on Saturday 04:00 local time, persistent so a
|
||||
// node that was off upgrades at its next start, the same moment on every node (no random delay), starting a
|
||||
// oneshot service that runs the very upgrade pacman_upgrade runs.
|
||||
func TestTheWeeklyUpgradeIsATimerOnSaturdayFourPersistentRunningTheToolsUpgrade(t *testing.T) {
|
||||
m := manifest(t)
|
||||
timer := m.resource(t, "upgrade-timer")
|
||||
service := m.resource(t, "upgrade-service")
|
||||
if timer["path"] != "/etc/systemd/system/mesh-pacman-weekly-upgrade.timer" || service["path"] != "/etc/systemd/system/"+WeeklyUnit {
|
||||
t.Fatalf("paths: %v %v", timer["path"], service["path"])
|
||||
}
|
||||
tt, st := templateText(t, timer), templateText(t, service)
|
||||
for _, want := range []string{"\nOnCalendar=Sat *-*-* 04:00:00\n", "\nPersistent=true\n", "\nWantedBy=timers.target\n"} {
|
||||
if !strings.Contains(tt, want) {
|
||||
t.Errorf("the timer lacks %q:\n%s", strings.TrimSpace(want), tt)
|
||||
}
|
||||
}
|
||||
for _, not := range []string{"RandomizedDelaySec", "OnBootSec", "OnUnitActiveSec", "UTC"} {
|
||||
if strings.Contains(tt, not) {
|
||||
t.Errorf("the timer says %s: the same local moment on every node, and no other moment", not)
|
||||
}
|
||||
}
|
||||
if strings.Count(tt, "OnCalendar=") != 1 {
|
||||
t.Errorf("one moment only:\n%s", tt)
|
||||
}
|
||||
if !strings.Contains(st, "\nType=oneshot\n") {
|
||||
t.Errorf("the service is a oneshot:\n%s", st)
|
||||
}
|
||||
if want := "\nExecStart=/usr/bin/pacman " + strings.Join(UpgradeArgs, " ") + "\n"; !strings.Contains(st, want) {
|
||||
t.Errorf("the service does not run the upgrade pacman_upgrade runs (%q):\n%s", want, st)
|
||||
}
|
||||
if strings.Contains(st, "reboot") || strings.Contains(tt, "reboot") || strings.Contains(st, "systemctl") {
|
||||
t.Error("the mesh restarts no machine for an upgrade")
|
||||
}
|
||||
r := m.resource(t, "weekly-upgrade")
|
||||
if r["type"] != "service" || r["unit"] != "mesh-pacman-weekly-upgrade.timer" || r["state"] != "running" || r["boot"] != "enabled" {
|
||||
t.Fatalf("the timer runs and is enabled: %v", r)
|
||||
}
|
||||
h, _ := r["health"].(map[string]any)
|
||||
if h["kind"] != "tool" || h["tool"] != "pacman_upgrade_health" {
|
||||
t.Errorf("the upgrade's outcome is the resource's health: %v", r["health"])
|
||||
}
|
||||
order := map[string]int{}
|
||||
for i, x := range m.Resources {
|
||||
order[x["id"].(string)] = i
|
||||
}
|
||||
if order["upgrade-service"] > order["weekly-upgrade"] || order["upgrade-timer"] > order["weekly-upgrade"] || order["package"] > order["upgrade-service"] {
|
||||
t.Errorf("the units are placed before the timer is started: %v", order)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,3 +374,38 @@ func TestConfigIsReadAsPacmanConfResolvesIt(t *testing.T) {
|
||||
t.Fatalf("%+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// The weekly upgrade's outcome is the module's health (issue 1574, ADR 0477 clause 5).
|
||||
func upgradeHealthMachine(props, journal string) *Machine {
|
||||
return machine(byLine(map[string]Ran{
|
||||
"systemctl show " + WeeklyUnit + " --no-pager --property=LoadState --property=ActiveState --property=Result --property=ExecMainStatus --property=ExecMainExitTimestamp": {Stdout: props},
|
||||
"sudo -n journalctl --no-pager -o cat -n 40 -u " + WeeklyUnit: {Stdout: journal},
|
||||
}, nil), 1000)
|
||||
}
|
||||
|
||||
func TestAFailedWeeklyUpgradeIsUnhealthyNamingTheError(t *testing.T) {
|
||||
h := upgradeHealthMachine("LoadState=loaded\nActiveState=failed\nResult=exit-code\nExecMainStatus=1\nExecMainExitTimestamp=Sat 2026-10-24 04:03:11 CEST\n",
|
||||
":: Synchronizing package databases...\nerror: failed to commit transaction (conflicting files)\nErrors occurred, no packages were upgraded.\n").UpgradeHealth()
|
||||
if h.Healthy || !strings.Contains(h.Why, "failed to commit transaction (conflicting files)") || !strings.Contains(h.Why, "exit-code") || !strings.Contains(h.Why, "Sat 2026-10-24 04:03:11 CEST") {
|
||||
t.Fatalf("%+v", h)
|
||||
}
|
||||
h = upgradeHealthMachine("LoadState=loaded\nActiveState=failed\nResult=exit-code\nExecMainStatus=1\nExecMainExitTimestamp=\n", "").UpgradeHealth()
|
||||
if h.Healthy || !strings.Contains(h.Why, "status 1") {
|
||||
t.Fatalf("a failure without a journal is still unhealthy: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPassedRunningOrNeverRunWeeklyUpgradeIsHealthy(t *testing.T) {
|
||||
for name, props := range map[string]string{
|
||||
"passed": "LoadState=loaded\nActiveState=inactive\nResult=success\nExecMainStatus=0\nExecMainExitTimestamp=Sat 2026-10-24 04:09:00 CEST\n",
|
||||
"running": "LoadState=loaded\nActiveState=activating\nResult=success\nExecMainStatus=0\nExecMainExitTimestamp=\n",
|
||||
"never run": "LoadState=loaded\nActiveState=inactive\nResult=success\nExecMainStatus=0\nExecMainExitTimestamp=\n",
|
||||
} {
|
||||
if h := upgradeHealthMachine(props, "").UpgradeHealth(); !h.Healthy {
|
||||
t.Errorf("%s: %+v", name, h)
|
||||
}
|
||||
}
|
||||
if h := machine(fake(func(call) Ran { return Ran{Status: 1, Stderr: "boom"} }, nil), 1000).UpgradeHealth(); h.Healthy {
|
||||
t.Errorf("a unit that cannot be read is not healthy: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user