osupdate: the health baseline is the start of the leg (inventory reading merged with the apply's own) — an app that stops during the run fails it (found live on demo-hp)
gates / gates (push) Successful in 19s
gates / gates (push) Successful in 19s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -240,6 +240,32 @@ func HealthVerdict(before, after *Health) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// MergeBaseline is the health baseline from two readings: a container counts as running (and healthy) if EITHER
|
||||
// reading saw it so. nil-safe. Pinned by TestHealth_BaselineIsTheStartOfTheLeg.
|
||||
func MergeBaseline(a, b *Health) *Health {
|
||||
if a == nil {
|
||||
return b
|
||||
}
|
||||
if b == nil {
|
||||
return a
|
||||
}
|
||||
out := &Health{DockerOK: a.DockerOK && b.DockerOK, NetworkOK: a.NetworkOK && b.NetworkOK, Controller: b.Controller,
|
||||
Containers: map[string]Container{}}
|
||||
for _, h := range []*Health{a, b} {
|
||||
for n, c := range h.Containers {
|
||||
cur, seen := out.Containers[n]
|
||||
if !seen || (cur.State != "running" && c.State == "running") {
|
||||
out.Containers[n] = c
|
||||
continue
|
||||
}
|
||||
if c.State == "running" && c.Health == "healthy" {
|
||||
out.Containers[n] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// call writes the plan and runs the wrapper once.
|
||||
func (l *Leg) call(ctx context.Context, runID, mode string, vmid int, rel hub.WireOSRelease, pkgs []Package) (WrapperReport, error) {
|
||||
dir := l.PlanDir
|
||||
@@ -374,8 +400,12 @@ func (l *Leg) Run(ctx context.Context, vmid int, trigger string) Report {
|
||||
}
|
||||
deadline := l.now().Add(wait)
|
||||
cur := ap.HealthAfter
|
||||
// The baseline is the guest as it was at the START of the leg (the inventory's reading) merged with the
|
||||
// apply's own "before": an app that stops at any point during the run counts. Found live 2026-10-04: an app
|
||||
// stopped between the inventory and the apply's own reading was taken as "stopped before" and ignored.
|
||||
base := MergeBaseline(inv.HealthAfter, ap.HealthBefore)
|
||||
for {
|
||||
ok, why := HealthVerdict(ap.HealthBefore, cur)
|
||||
ok, why := HealthVerdict(base, cur)
|
||||
rep.Healthy, rep.HealthReason = ok, why
|
||||
if ok || !l.now().Before(deadline) || ctx.Err() != nil {
|
||||
break
|
||||
|
||||
@@ -263,3 +263,19 @@ func TestWrapperSuite(t *testing.T) {
|
||||
t.Fatalf("wrapper suite did not report OK:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// An app that stops BETWEEN the start of the leg and the apply's own "before" reading still fails the run.
|
||||
// Measured live 2026-10-04 on demo-hp (privatebin stopped 1 s after the apply plan was written: the old rule
|
||||
// passed). Red-proof: use ap.HealthBefore alone as the baseline and this fails.
|
||||
func TestHealth_BaselineIsTheStartOfTheLeg(t *testing.T) {
|
||||
stoppedEarly := &Health{DockerOK: true, NetworkOK: true, Controller: "healthy", Containers: map[string]Container{
|
||||
"felhom-controller": {State: "running", Health: "healthy"}, "app": {State: "exited"}}}
|
||||
w := &fakeWrapper{t: t, pending: pend, applyRep: WrapperReport{Upgraded: []Package{{Name: "libc6"}},
|
||||
HealthBefore: stoppedEarly, HealthAfter: stoppedEarly},
|
||||
healthSeq: []*Health{stoppedEarly, stoppedEarly, stoppedEarly, stoppedEarly, stoppedEarly, stoppedEarly, stoppedEarly}}
|
||||
l, _ := newLeg(t, w, &hub.WireOSUpdate{Ring: 0, Enabled: true})
|
||||
rep := l.Run(context.Background(), 9201, "night")
|
||||
if rep.Outcome != "health_failed" || !strings.Contains(rep.HealthReason, "app was running") {
|
||||
t.Fatalf("an app that stopped during the run passed: %+v", rep)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user