Watchman: an unhealthy finding read back at the start is kept as it was (issue 1952)
mesh/delivery merged: source.main moved to novox/mesh-delivery@91128d4f0af6
mesh/check checking 8a658a4290c9 on 91128d4f0af6
mesh/delivery merged: source.main moved to novox/mesh-delivery@91128d4f0af6
mesh/check checking 8a658a4290c9 on 91128d4f0af6
At a restart a standing finding is observed again by the replayed failed have: kept, not confirmed again, and its urgent signal runs on from the finding's Since; an urgent one stays urgent with no signal restarted. Quick: skip the confirm when a finding stands. Thorough, chosen: the same, plus urgency read from the finding itself. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -158,6 +158,29 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
expire(w, c, signalKey("unhealthy", "web.ace"), UnhealthyUrgentAfter)
|
||||
w.Have("ace.mail", haveOf(t, delivery.PhaseRunning, delivery.HealthWaits, "a part waits for the secret token"), Put) // needs-operator is not observed here
|
||||
}, map[string]string{"module.web.ace.unhealthy": Urgent}},
|
||||
{"WM3: a warning unhealthy finding read back at the start is kept as it was, urgent when its four hours from Since are out", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
seedUnhealthy(t, w, o, Warning, t0.Add(-time.Hour))
|
||||
w.Have("ace.web", haveOf(t, delivery.PhaseRunning, delivery.HealthFailed, "x"), Put)
|
||||
w.Reconcile()
|
||||
if o.findings["module.web.ace.unhealthy"].Severity != Warning {
|
||||
t.Fatalf("cleared: findings %v", o.findings)
|
||||
}
|
||||
if ttl := o.ttls[signalKey("unhealthy", "web.ace")]; ttl != UnhealthyUrgentAfter-time.Hour {
|
||||
t.Fatalf("urgent signal restarted from zero: %v", ttl)
|
||||
}
|
||||
if _, ok := w.signals[signalKey("unhealthy-confirm", "web.ace")]; ok {
|
||||
t.Fatal("a standing finding is confirmed again")
|
||||
}
|
||||
expire(w, c, signalKey("unhealthy", "web.ace"), UnhealthyUrgentAfter-time.Hour)
|
||||
}, map[string]string{"module.web.ace.unhealthy": Urgent}},
|
||||
{"WM3: an urgent unhealthy finding read back at the start stays urgent, with no signal restarted", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
seedUnhealthy(t, w, o, Urgent, t0.Add(-5*time.Hour))
|
||||
w.Have("ace.web", haveOf(t, delivery.PhaseRunning, delivery.HealthFailed, "x"), Put)
|
||||
w.Reconcile()
|
||||
if o.findings["module.web.ace.unhealthy"].Severity != Urgent || len(w.signals) != 0 {
|
||||
t.Fatalf("findings %v signals %v", o.findings, w.signals)
|
||||
}
|
||||
}, map[string]string{"module.web.ace.unhealthy": Urgent}},
|
||||
{"WM3: a restart replays a have already failed: it is confirmed after a minute like a new one", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.Have("novox.fail2ban", haveOf(t, delivery.PhaseRunning, delivery.HealthFailed, "down since 19:40"), Put)
|
||||
expire(w, c, signalKey("unhealthy-confirm", "fail2ban.novox"), UnhealthyConfirmAfter)
|
||||
@@ -370,3 +393,11 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func seedUnhealthy(t *testing.T, w *Watchman, o *fakeOut, sev string, since time.Time) {
|
||||
t.Helper()
|
||||
f := Finding{Scope: ScopeModule, ID: "web.ace", Token: "unhealthy", Kind: KindUnhealthy, Machine: "ace", Severity: sev, Source: "health", Since: since, Summary: "web on ace is not healthy: x"}
|
||||
v, _ := EncodeFinding(f)
|
||||
o.findings[f.Key()], _ = DecodeFinding(v)
|
||||
w.SeedFinding(f.Key(), v)
|
||||
}
|
||||
|
||||
@@ -504,12 +504,23 @@ func (w *Watchman) haveFindings(key string, node, module string, h *delivery.Hav
|
||||
Said: firstLine(orWords(h.Detail, "failed")),
|
||||
Headline: fmt.Sprintf("%s not working on %s", module, node)}
|
||||
id := module + "." + node
|
||||
if _, ok := w.signals[signalKey("unhealthy-confirm", id)]; !ok {
|
||||
urgentIn := UnhealthyUrgentAfter
|
||||
if w.seededFindings[unhealthyKey] {
|
||||
// Read back at the start: it stands, so it is observed again, kept as it was (not cleared and raised again), and
|
||||
// its four hours run on from its own Since, not from zero; one that already went urgent needs no signal.
|
||||
delete(w.seededFindings, unhealthyKey)
|
||||
old, err := DecodeFinding(w.findings[unhealthyKey])
|
||||
if err == nil && old.Severity == Urgent {
|
||||
urgentIn = 0
|
||||
} else if err == nil && !old.Since.IsZero() {
|
||||
urgentIn = max(time.Second, UnhealthyUrgentAfter-now.Sub(old.Since))
|
||||
}
|
||||
} else if _, ok := w.signals[signalKey("unhealthy-confirm", id)]; !ok {
|
||||
w.signal("unhealthy-confirm", id, UnhealthyConfirmAfter, signalDue{Do: dueFind, While: key, Finding: f})
|
||||
}
|
||||
if _, ok := w.signals[signalKey("unhealthy", id)]; !ok {
|
||||
if _, ok := w.signals[signalKey("unhealthy", id)]; !ok && urgentIn > 0 {
|
||||
f.Severity = Urgent
|
||||
w.signal("unhealthy", id, UnhealthyUrgentAfter, signalDue{Do: dueFind, While: key, Finding: f})
|
||||
w.signal("unhealthy", id, urgentIn, signalDue{Do: dueFind, While: key, Finding: f})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user