The node-engine reports reads-wants; a failed apply is said; the have's fingerprint and digest follow its health (R6-A A6)
This commit is contained in:
@@ -491,6 +491,7 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
|
||||
if res.Failed {
|
||||
a.attempt[module] = attempt{FP: fp, Composed: dig, WantRev: w.Rev, CompRev: comp.Rev}
|
||||
a.mu.Unlock()
|
||||
a.env.Say(fmt.Sprintf("applying %s at %s failed: %s", module, fp, res.Detail))
|
||||
a.env.Haves.Write(ctx, Have{Module: module, FP: fp, Composed: dig, Phase: string(delivery.PhaseFailed), Detail: res.Detail})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -461,3 +461,40 @@ func TestTheFirstHaveWrittenIsToldToTheProof(t *testing.T) {
|
||||
t.Fatal("a have already saying the same was not told")
|
||||
}
|
||||
}
|
||||
|
||||
// R6-A AC6: an applied module's have names its fingerprint and the digest of the composition applied; a health check that
|
||||
// fails after the module's start window gives health failed, and the have keeps both.
|
||||
func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth(t *testing.T) {
|
||||
l := newHaveLink()
|
||||
q, _ := aHaveQueue(l)
|
||||
fp := "src2:" + strings.Repeat("a", 64)
|
||||
dig := "sha256:" + strings.Repeat("b", 64)
|
||||
q.NoteModule(context.Background(), ModuleFact{Module: "web", FP: fp, Composed: dig})
|
||||
h := l.have(t, "n1.web")
|
||||
if h.FP != fp || h.Composed != dig || h.Phase != delivery.PhaseRunning || h.Health != delivery.HealthUnknown {
|
||||
t.Fatalf("the have after the apply: %+v", h)
|
||||
}
|
||||
// Starting: it waits. Past its start window the check fails: failed.
|
||||
q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateStarting}))
|
||||
if got := l.have(t, "n1.web"); got.Health != delivery.HealthWaits || got.FP != fp || got.Composed != dig {
|
||||
t.Fatalf("while it starts: %+v", got)
|
||||
}
|
||||
q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateUnhealthy}))
|
||||
if got := l.have(t, "n1.web"); got.Health != delivery.HealthFailed || got.FP != fp || got.Composed != dig {
|
||||
t.Fatalf("a failing check after the start window: %+v", got)
|
||||
}
|
||||
|
||||
// A module held for a failed backup says so and what it keeps running; removed is written once, over its key.
|
||||
q.NoteModule(context.Background(), ModuleFact{Module: "web", FP: fp, Composed: dig, Phase: string(delivery.PhaseHeld), Detail: "the repository is locked"})
|
||||
if got := l.have(t, "n1.web"); got.Phase != delivery.PhaseHeld || got.Detail == "" {
|
||||
t.Fatalf("held: %+v", got)
|
||||
}
|
||||
q.NoteRemoved(context.Background(), "web", "")
|
||||
if got := l.have(t, "n1.web"); got.Phase != delivery.PhaseRemoved {
|
||||
t.Fatalf("removed: %+v", got)
|
||||
}
|
||||
q.NoteNode(context.Background(), dig)
|
||||
if n, err := delivery.DecodeHaveNode(l.kv["n1.node"].value); err != nil || n.Composed != dig {
|
||||
t.Fatalf("the node key: %+v %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ const (
|
||||
// controller sends the field only to a node that reports it, since an older node-engine refuses the key.
|
||||
CapContainerGroups = "container-groups"
|
||||
|
||||
// CapReadsWants says this node-engine follows its wants (novox/hq ADR 0539 §9): it watches `want.<node>.>` and its
|
||||
// compositions and applies one module at a time, and hears no declaration. A fact about the program: the controller
|
||||
// stops sending a node declarations only once it reports it.
|
||||
CapReadsWants = "reads-wants"
|
||||
|
||||
// The network manager this machine runs, one capability per dialect (novox/hq ADR 0161): the
|
||||
// uplink seat's holder declares its own, so the holder for a manager the machine does not run
|
||||
// is refused the way any missing capability is, naming it. Active, not installed — a machine
|
||||
@@ -190,6 +195,20 @@ func (containerGroups) Detect(context.Context) Verdict {
|
||||
}
|
||||
}
|
||||
|
||||
// readsWants reports that this node-engine follows its wants. True of the program that is running, so it is always
|
||||
// present: its absence on a node is what tells the controller the node-engine still waits for declarations.
|
||||
type readsWants struct{}
|
||||
|
||||
func (readsWants) Name() string { return CapReadsWants }
|
||||
|
||||
func (readsWants) Detect(context.Context) Verdict {
|
||||
return Verdict{
|
||||
Name: CapReadsWants, Present: true,
|
||||
Detail: "follows its wants and applies one module's composition at a time; hears no declaration",
|
||||
How: "a fact about this program, not probed: the node-engine that answers follows its wants",
|
||||
}
|
||||
}
|
||||
|
||||
// graphicalSession reports whether anything could display a window here.
|
||||
//
|
||||
// Environment rather than a probe, because a display server is reachable through a socket a
|
||||
@@ -230,6 +249,7 @@ func Default(runner Runner) []Detector {
|
||||
privileged{},
|
||||
fileReferences{},
|
||||
containerGroups{},
|
||||
readsWants{},
|
||||
graphicalSession{},
|
||||
seat{},
|
||||
battery{},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// R6-A: the node's profile carries the capability that says the node-engine follows its wants, which the controller
|
||||
// checks before it stops sending the node declarations (novox/hq ADR 0539 §9).
|
||||
func TestTheProfileSaysTheNodeEngineReadsItsWants(t *testing.T) {
|
||||
got := Detect(context.Background(), Default(func(context.Context, string, ...string) (string, error) {
|
||||
return "", nil
|
||||
}), time.Second)
|
||||
v := verdictOf(t, got, CapReadsWants)
|
||||
if !v.Present || v.Detail == "" || v.How == "" {
|
||||
t.Errorf("reads-wants = %+v, want present with a detail and a method", v)
|
||||
}
|
||||
if CapReadsWants != "reads-wants" {
|
||||
t.Errorf("the capability is named %q", CapReadsWants)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user