Say a running module ok once looked at when nothing of it is judged, so mesh-host's canary can be healthy and its other nodes get the new engine (issue 1945)
mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/check pass 958ccbde3ed4 on f06058390c0f | unit 297 run, 1038 kept, 5 skipped | integration 300 run, 1039 kept, 4 skipped | over bound: unit (slow…
mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/check pass 958ccbde3ed4 on f06058390c0f | unit 297 run, 1038 kept, 5 skipped | integration 300 run, 1039 kept, 4 skipped | over bound: unit (slow…
This commit is contained in:
+20
-6
@@ -36,6 +36,9 @@ type haveState struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
modules map[string]ModuleFact
|
modules map[string]ModuleFact
|
||||||
health map[string]delivery.Health
|
health map[string]delivery.Health
|
||||||
|
// looked says a look's health was taken: a running module it judges no resource of then has nothing that could be
|
||||||
|
// down, and is ok (issue 1945).
|
||||||
|
looked bool
|
||||||
node *delivery.HaveNode
|
node *delivery.HaveNode
|
||||||
// nodeComposed is the digest of the node's own composition it runs (ADR 0539 §9), kept beside the node's facts.
|
// nodeComposed is the digest of the node's own composition it runs (ADR 0539 §9), kept beside the node's facts.
|
||||||
nodeComposed string
|
nodeComposed string
|
||||||
@@ -110,7 +113,7 @@ func (q *Session) noteApply(r Report) bool {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
if r.Health != nil {
|
if r.Health != nil {
|
||||||
s.health = moduleHealths(r.Health)
|
s.health, s.looked = moduleHealths(r.Health), true
|
||||||
}
|
}
|
||||||
n := nodeFacts(r)
|
n := nodeFacts(r)
|
||||||
s.node = &n
|
s.node = &n
|
||||||
@@ -152,7 +155,7 @@ func (q *Session) noteApply(r Report) bool {
|
|||||||
}
|
}
|
||||||
s.modules = next
|
s.modules = next
|
||||||
if r.Health != nil {
|
if r.Health != nil {
|
||||||
s.health = moduleHealths(r.Health)
|
s.health, s.looked = moduleHealths(r.Health), true
|
||||||
}
|
}
|
||||||
n := nodeFacts(r)
|
n := nodeFacts(r)
|
||||||
s.node = &n
|
s.node = &n
|
||||||
@@ -166,20 +169,20 @@ func (q *Session) noteHealth(h Health) bool {
|
|||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
next := moduleHealths(&h)
|
next := moduleHealths(&h)
|
||||||
changed := false
|
changed := false
|
||||||
for module := range s.modules {
|
for module, f := range s.modules {
|
||||||
want, ok := next[module]
|
want, ok := next[module]
|
||||||
if !ok {
|
if !ok {
|
||||||
want = delivery.HealthUnknown
|
want = nothingJudged(f, true)
|
||||||
}
|
}
|
||||||
have, known := s.health[module]
|
have, known := s.health[module]
|
||||||
if !known {
|
if !known {
|
||||||
have = delivery.HealthUnknown
|
have = nothingJudged(f, s.looked)
|
||||||
}
|
}
|
||||||
if have != want {
|
if have != want {
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.health = next
|
s.health, s.looked = next, true
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +235,8 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) {
|
|||||||
}
|
}
|
||||||
if got, known := s.health[module]; known {
|
if got, known := s.health[module]; known {
|
||||||
h.Health = got
|
h.Health = got
|
||||||
|
} else {
|
||||||
|
h.Health = nothingJudged(f, s.looked)
|
||||||
}
|
}
|
||||||
key, err := delivery.HaveKey(node, module)
|
key, err := delivery.HaveKey(node, module)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -452,3 +457,12 @@ func (q *Session) ReadHave(ctx context.Context, module string) (delivery.Have, b
|
|||||||
}
|
}
|
||||||
return h, true
|
return h, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nothingJudged is the health of a module a look judged no resource of: ok when it was looked at and the module applied
|
||||||
|
// and runs (nothing of it could be down), unknown otherwise.
|
||||||
|
func nothingJudged(f ModuleFact, looked bool) delivery.Health {
|
||||||
|
if looked && !f.Failed && (f.Phase == "" || f.Phase == string(delivery.PhaseRunning)) {
|
||||||
|
return delivery.HealthOK
|
||||||
|
}
|
||||||
|
return delivery.HealthUnknown
|
||||||
|
}
|
||||||
|
|||||||
@@ -498,3 +498,26 @@ func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth(
|
|||||||
t.Fatalf("the node key: %+v %v", n, err)
|
t.Fatalf("the node key: %+v %v", n, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A module applied and running that has nothing long-running to judge is ok once the engine has looked: there is nothing
|
||||||
|
// of it that could be down. Unknown kept it from ever being healthy, so the assigner's canary rule held every other node
|
||||||
|
// of mesh-host back for good (issue 1945, live on 2026-10-25: ace's have of mesh-host said unknown, and novox and shanks
|
||||||
|
// never got the new node-engine). A failed one stays failed, and before any look it is unknown.
|
||||||
|
func TestAModuleWithNothingToJudgeIsOkOnceLookedAt(t *testing.T) {
|
||||||
|
l := newHaveLink()
|
||||||
|
q, _ := aHaveQueue(l)
|
||||||
|
q.SayFacts(context.Background(), Report{Modules: []ModuleFact{{Module: "mesh-host", Build: "b"}, {Module: "web", Build: "b"},
|
||||||
|
{Module: "broken", Build: "b", Failed: true, Detail: "x"}}})
|
||||||
|
if h := l.have(t, "n1.mesh-host").Health; h != delivery.HealthUnknown {
|
||||||
|
t.Fatalf("before any look: %s", h)
|
||||||
|
}
|
||||||
|
if !q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateHealthy})) {
|
||||||
|
t.Fatal("not said")
|
||||||
|
}
|
||||||
|
if h := l.have(t, "n1.mesh-host").Health; h != delivery.HealthOK {
|
||||||
|
t.Fatalf("a running module with nothing to judge after a look: %s", h)
|
||||||
|
}
|
||||||
|
if h := l.have(t, "n1.broken").Health; h == delivery.HealthOK {
|
||||||
|
t.Fatalf("a failed module was said ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user