Files
mesh-controller/cmd/mesh-controller/machine_units_test.go
T
jochen 6c98e7ea51
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/315-a-failed-unit-is-a-condition delivered: every member is delivered
Raise a failed unit, the module's and the machine's (hq issue 315)
A module's failed unit now arrives among its resources and raises the
module's own condition, named by the unit. The machine's own failed
units, which no module places, are one warning for the machine listing
them, cleared when none is listed. Nothing a send moved is among them,
so the gate never holds a send on that finding. The statement's units
are kept with the machine's health (migration 0080) and node show says
them.
2026-10-08 11:28:52 +02:00

129 lines
5.4 KiB
Go

package main
import (
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/conditions"
"github.com/novox/mesh-controller/internal/link"
)
// A failed unit is never silent (novox/hq issue 315, "how it is checked"): a module's failed unit raises
// the module's condition on the second statement, naming the unit; the machine's own failed units raise
// one finding for the machine, listing them, cleared when none is listed; an engine that says nothing of
// its units raises and clears nothing; and the gate never holds a send on the machine's own.
// desktop is the workstation as found on 2026-10-08, with the mesh's names: the Razer daemon's packaged
// user unit, the module's; two mounts of the machine's own table and a unit a removed package left
// behind, the machine's.
func desktop(at time.Time) link.Health {
return link.Health{Contract: link.ReadinessContract, At: at,
Resources: []link.ResourceHealth{{Module: "openrazer", Resource: "openrazer.daemon", Kind: link.KindUnit,
Target: "openrazer-daemon.service", State: link.StateUnhealthy,
Reason: "failed in the account's own service manager (exit-code)", Since: h0, Streak: 2}},
Units: &link.UnitsHealth{State: link.UnitsDegraded, Failed: []link.FailedUnit{
{Unit: "mnt-recalbox.mount", Scope: "system", Load: "loaded", Result: "exit-code", Since: h0},
{Unit: "storage-media.mount", Scope: "system", Load: "loaded", Result: "timeout", Since: h0},
{Unit: "greenclip.service", Scope: "user", Load: "not-found", Result: "start-limit-hit", Since: h0},
}}}
}
func TestAFailedUnitIsTheModulesConditionAndTheMachinesOwnAreOneFinding(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
inv, k := open.inventory, conditionsFrom
openConditions := func() map[string]conditions.Condition {
t.Helper()
list, err := k.Open(ctx)
if err != nil {
t.Fatal(err)
}
out := map[string]conditions.Condition{}
for _, c := range list {
out[c.Key] = c
}
return out
}
say := func(at time.Time, h link.Health) {
t.Helper()
if err := stateHealth(ctx, inv, k, "laptop", h, at); err != nil {
t.Fatal(err)
}
}
say(h0, desktop(h0))
got := openConditions()
machine, raised := got["machine.laptop.units"]
if !raised || len(got) != 1 {
t.Fatalf("the machine's own failed units are one finding at once (the engine looked twice): %v", got)
}
for _, unit := range []string{"mnt-recalbox.mount", "storage-media.mount", "greenclip.service"} {
if !strings.Contains(machine.Summary, unit) {
t.Errorf("the finding names %s: %s", unit, machine.Summary)
}
}
if strings.Contains(machine.Summary, "openrazer") || machine.Severity != conditions.Warning {
t.Errorf("the module's unit is the module's, and the machine's a warning: %+v", machine)
}
say(h0.Add(time.Minute), desktop(h0.Add(time.Minute)))
got = openConditions()
module, raised := got["module.openrazer.laptop.unhealthy"]
if !raised || !strings.Contains(module.Summary, "openrazer-daemon.service") ||
!strings.Contains(module.Summary, "account's own service manager") {
t.Fatalf("the module's failed unit raises its condition on the second statement, naming the unit: %+v", got)
}
kept, had, err := inv.HealthOf(ctx, "laptop")
if err != nil || !had || kept.Units == nil || len(kept.Units.Failed) != 3 {
t.Fatalf("the statement's units were not kept: %+v %v", kept.Units, err)
}
lines := strings.Join(unitsLines(kept, had, h0), "\n")
for _, want := range []string{"degraded", "storage-media.mount (system) — no module places it",
"openrazer-daemon.service — openrazer's"} {
if !strings.Contains(lines, want) {
t.Errorf("node show does not say %q:\n%s", want, lines)
}
}
// An older engine says nothing of its units: nothing cleared on its word.
older := desktop(h0.Add(2 * time.Minute))
older.Units = nil
say(h0.Add(2*time.Minute), older)
if _, still := openConditions()["machine.laptop.units"]; !still {
t.Fatal("a statement saying nothing of units cleared the machine's finding")
}
// The operator mends the mounts and removes the leftover link; the module's unit still fails.
mended := desktop(h0.Add(3 * time.Minute))
mended.Units.Failed = []link.FailedUnit{}
say(h0.Add(3*time.Minute), mended)
got = openConditions()
if _, still := got["machine.laptop.units"]; still {
t.Fatalf("no failed unit of the machine's own, still open: %v", got)
}
if _, still := got["module.openrazer.laptop.unhealthy"]; !still {
t.Fatalf("the module's unit still fails, and its condition stands: %v", got)
}
// And the module's unit mended: running, nothing open.
well := link.Health{Contract: link.ReadinessContract, At: h0.Add(4 * time.Minute),
Units: &link.UnitsHealth{State: link.UnitsRunning, Failed: []link.FailedUnit{}}}
say(h0.Add(4*time.Minute), well)
if got = openConditions(); len(got) != 0 {
t.Fatalf("a running service manager leaves nothing open: %v", got)
}
}
func TestTheGateNeverHoldsASendOnTheMachinesOwnUnits(t *testing.T) {
since := h0
units := conditions.Condition{Key: "machine.laptop.units", Kind: kindMachineUnits,
Subject: conditions.Subject{Scope: conditions.ScopeMachine, ID: "laptop", Machine: "laptop"},
Summary: "laptop's service manager is degraded", Raised: since.Add(time.Minute), Source: sourceUnits}
if w := aboutTheMachine("laptop", []string{"openrazer"}, since, gateFacts{judged: true,
open: []conditions.Condition{units}}); w.whole != "" || w.waiting != "" || len(w.on) != 0 {
t.Fatalf("the machine's own failed units held a send: %+v", w)
}
}