hub v0.131.1: a scanned host pass (reboot_scanned, agent 0.141.1) clears 'reboot needed', so the 14-day alarm does not fire after a reboot; live evidence partB/partD/partG
gates / gates (push) Successful in 30s
gates / gates (push) Successful in 30s
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:
@@ -304,3 +304,27 @@ func TestFleet_OneLinePerBoxBothLayers(t *testing.T) {
|
||||
}
|
||||
t.Fatal("no hp line")
|
||||
}
|
||||
|
||||
// A reboot clears "reboot needed": the agent scans the host on every pass (reboot_scanned) and a scanned pass that
|
||||
// finds nothing ends the run, so no alarm fires 14 days later. Red-proof: ignore RebootScanned in layerLine's predicate
|
||||
// and the cleared step still shows a date.
|
||||
func TestRebootNeeded_ClearedByAScannedPass(t *testing.T) {
|
||||
f := newFix(t)
|
||||
f.ingest(t, "hp", Report{Layer: LayerHost, Trigger: "night", Mode: "apply", Outcome: "applied", Healthy: true,
|
||||
Upgraded: []Package{pk("libc6", "2")}, RebootNeeded: true, RebootScanned: true})
|
||||
f.now = f.now.Add(24 * time.Hour)
|
||||
f.ingest(t, "hp", Report{Layer: LayerHost, Trigger: "night", Mode: "apply", Outcome: "nothing", Healthy: true, RebootNeeded: true, RebootScanned: true})
|
||||
if l := f.s.layerLine("hp", LayerHost, 0); f.now.Sub(l.RebootNeededSince) != 24*time.Hour {
|
||||
t.Fatalf("since = %v", l.RebootNeededSince)
|
||||
}
|
||||
f.now = f.now.Add(24 * time.Hour) // the operator rebooted
|
||||
f.ingest(t, "hp", Report{Layer: LayerHost, Trigger: "night", Mode: "apply", Outcome: "nothing", Healthy: true, RebootScanned: true})
|
||||
if l := f.s.layerLine("hp", LayerHost, 0); !l.RebootNeededSince.IsZero() {
|
||||
t.Fatalf("a scanned pass after the reboot did not clear it: %v", l.RebootNeededSince)
|
||||
}
|
||||
f.now = f.now.Add(20 * 24 * time.Hour)
|
||||
f.s.Alarms()
|
||||
if f.count(EventRebootNeeded) != 0 {
|
||||
t.Fatal("reboot alarm after the reboot")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ type Report struct {
|
||||
RestartNeeded []string `json:"restart_needed,omitempty"`
|
||||
DockerRestartNeeded bool `json:"docker_restart_needed,omitempty"`
|
||||
RebootNeeded bool `json:"reboot_needed,omitempty"`
|
||||
RebootScanned bool `json:"reboot_scanned,omitempty"` // agent ≥ 0.141.1: the pass looked (host: every pass)
|
||||
Refused json.RawMessage `json:"refused,omitempty"`
|
||||
PassSeconds float64 `json:"pass_seconds,omitempty"`
|
||||
}
|
||||
@@ -534,8 +535,8 @@ func (s *Service) layerLine(hostID, layer string, ring int) LayerLine {
|
||||
}
|
||||
l.NotCoveredFast = len(s.notCoveredFast(layer, ring, r))
|
||||
l.RebootNeededSince = s.sinceContinuously(hostID, layer, func(x Report) (bool, bool) {
|
||||
if len(x.Upgraded) == 0 && x.Outcome != "applied" {
|
||||
return false, false // a report without an install says nothing about reboot-needed: skip it
|
||||
if !x.RebootScanned && len(x.Upgraded) == 0 && x.Outcome != "applied" {
|
||||
return false, false // a pass that did not look says nothing about reboot-needed: skip it
|
||||
}
|
||||
return x.RebootNeeded, true
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user