Pin the node-engine at its ADR 0328 merge (#95), and speak its contract: a library's verdict is said for the modules that use it
This commit is contained in:
@@ -364,14 +364,14 @@ func TestTheDeclarationSaysItsLibrariesOnlyWhenComposed(t *testing.T) {
|
||||
}
|
||||
newer, err := sendable{Resources: []map[string]any{{"id": "a"}},
|
||||
Groups: []catalogue.GroupEntry{{Name: "mesh-media", GID: 50010}},
|
||||
Identities: []catalogue.IdentityEntry{{Module: "radarr", Name: "mesh-radarr", UID: 50020, GID: 50020, Groups: []string{"mesh-media"}}},
|
||||
Identities: []catalogue.IdentityEntry{{Module: "radarr", Name: "mesh-radarr", UID: 50020, GID: 50010}},
|
||||
Libraries: []catalogue.LibraryEntry{{Library: "media", Root: "/storage/media", Group: "mesh-media", GID: 50010,
|
||||
Home: true, Paths: []string{"movies"}, Runner: "jochen"}}}.Body()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{`"groups":[{"name":"mesh-media","gid":50010}]`,
|
||||
`"identities":[{"module":"radarr","name":"mesh-radarr","uid":50020,"gid":50020,"groups":["mesh-media"]}]`,
|
||||
`"identities":[{"module":"radarr","name":"mesh-radarr","uid":50020,"gid":50010}]`,
|
||||
`"libraries":[{"library":"media","root":"/storage/media","group":"mesh-media","gid":50010,"home":true,` +
|
||||
`"taken":false,"paths":["movies"],"runner":"jochen"}]`} {
|
||||
if !strings.Contains(string(newer), want) {
|
||||
@@ -418,3 +418,53 @@ func TestAnUnknownLibraryIsJudgedByWhetherTheMoveChangesIt(t *testing.T) {
|
||||
t.Fatal("a move that adds a library, and joins, reads unchanged — or an unchanged one reads changed")
|
||||
}
|
||||
}
|
||||
|
||||
// The engine states a library once, of no module; each module assigned to the machine that uses it is judged and said
|
||||
// by a copy of that verdict — its wait excused and passed by the gate as a wait for a person. A library unknown or
|
||||
// starting (a take walking under it) of a move that changes no library is not counted.
|
||||
func TestALibrarysVerdictIsSaidForTheModulesThatUseIt(t *testing.T) {
|
||||
inv := inventory.ForTest(t)
|
||||
ctx := t.Context()
|
||||
if _, err := inv.AddNode(ctx, "ace"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, m := range []catalogue.Manifest{radarrManifest, plexManifest, {Module: "plain", Version: "1"}} {
|
||||
if err := inv.RegisterModule(ctx, m, inventory.Source{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.Assign(ctx, "ace", m.Module); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
lib := inventory.ResourceHealth{Resource: "library:media", Kind: link.KindLibrary, Target: "/storage/media",
|
||||
State: link.StateWaiting, Waits: []inventory.Wait{{Part: "media", Access: "library:media", Reason: link.WaitKeep,
|
||||
Placed: "fs-000000000001", What: "let the mesh keep the library media"}}}
|
||||
copies := libraryVerdictsFor(ctx, inv, "ace", []inventory.ResourceHealth{lib})
|
||||
var modules []string
|
||||
for _, c := range copies {
|
||||
modules = append(modules, c.Module)
|
||||
}
|
||||
if strings.Join(modules, ",") != "plex,radarr" {
|
||||
t.Fatalf("the library's verdict is said for %v; want plex and radarr, the modules using it", modules)
|
||||
}
|
||||
now := time.Now()
|
||||
f := gateFacts{now: now, waits: libraryFactsWith(), groupsAdded: map[string]bool{"radarr": false},
|
||||
accessesChanged: map[string]bool{"radarr": false},
|
||||
health: map[string]inventory.NodeHealth{"ace": {Node: "ace", HeardAt: now, Resources: copies}}}
|
||||
if h, why := moduleHealthWord("radarr", "ace", now.Add(-time.Minute), f); h != healthPerson {
|
||||
t.Fatalf("radarr waiting on media reads %v %q; want a wait for a person", h, why)
|
||||
}
|
||||
for _, state := range []string{link.StateUnknown, link.StateStarting} {
|
||||
c := copies[1]
|
||||
c.State, c.Waits = state, nil
|
||||
f.health["ace"] = inventory.NodeHealth{Node: "ace", HeardAt: now, Resources: []inventory.ResourceHealth{c}}
|
||||
if h, why := moduleHealthWord("radarr", "ace", now.Add(-time.Minute), f); h != healthGood {
|
||||
t.Errorf("media %s, a move that changes no library: %v %q", state, h, why)
|
||||
}
|
||||
f.accessesChanged = map[string]bool{"radarr": true}
|
||||
if h, _ := moduleHealthWord("radarr", "ace", now.Add(-time.Minute), f); h != healthNotYet {
|
||||
t.Errorf("media %s, a move that changes a library: %v; want held to the bound", state, h)
|
||||
}
|
||||
f.accessesChanged = map[string]bool{"radarr": false}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,47 @@ const (
|
||||
moduleUnhealthyAfter = 2
|
||||
)
|
||||
|
||||
// libraryVerdictsFor is, for each library verdict in a statement (kind library, no module), one copy for each module
|
||||
// assigned to the machine whose manifest uses that library: what the module is judged and said by. None when the
|
||||
// assignments or the catalogue cannot be read: the library's own verdict stands alone, and no module is excused by it.
|
||||
func libraryVerdictsFor(ctx context.Context, inv *inventory.Inventory, node string,
|
||||
rs []inventory.ResourceHealth) []inventory.ResourceHealth {
|
||||
var libraries []inventory.ResourceHealth
|
||||
for _, r := range rs {
|
||||
if r.Module == "" && r.Kind == link.KindLibrary && strings.HasPrefix(r.Resource, libraryAccess) {
|
||||
libraries = append(libraries, r)
|
||||
}
|
||||
}
|
||||
if len(libraries) == 0 || inv == nil {
|
||||
return nil
|
||||
}
|
||||
assigned, err := inv.Assigned(ctx, node)
|
||||
if err != nil {
|
||||
// empty-on-error: the library's own verdict is kept and judged; no module is excused by a copy it could not make
|
||||
return nil
|
||||
}
|
||||
shelf, err := inv.Catalogue(ctx)
|
||||
if err != nil {
|
||||
// empty-on-error: the library's own verdict is kept and judged; no module is excused by a copy it could not make
|
||||
return nil
|
||||
}
|
||||
var out []inventory.ResourceHealth
|
||||
for _, lib := range libraries {
|
||||
name := strings.TrimPrefix(lib.Resource, libraryAccess)
|
||||
for _, module := range assigned {
|
||||
for _, u := range shelf[module].Libraries {
|
||||
if u.Library == name {
|
||||
copied := lib
|
||||
copied.Module = module
|
||||
out = append(out, copied)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// healthRefused counts the statements refused as older than the one kept, for the log and a test.
|
||||
var healthRefused atomic.Int64
|
||||
|
||||
@@ -85,6 +126,9 @@ func stateHealth(ctx context.Context, inv *inventory.Inventory, k *conditions.Ke
|
||||
}
|
||||
resources = append(resources, kept)
|
||||
}
|
||||
// **A library's verdict is said for the modules that use it** (novox/hq ADR 0328 §8): the engine states each
|
||||
// library once, as `library:<name>`, of no module; a module that uses it waits with it, and is said so.
|
||||
resources = append(resources, libraryVerdictsFor(ctx, inv, node, resources)...)
|
||||
// **A wait for the operator is checked before it is excused** (novox/hq ADR 0283): a waiting resource whose
|
||||
// wait does not check out is judged unhealthy, saying why; one that does is kept beside the unhealthy ones, so
|
||||
// judgeModuleHealth can say it as needs-operator. What is stored is what the machine said.
|
||||
@@ -688,7 +732,10 @@ func moduleHealthWord(module, machine string, since time.Time, f gateFacts) (hea
|
||||
// no wait. Of a module whose move leaves its accesses as they were it is a condition the move found, not
|
||||
// counted; of one whose move changes them it is not yet healthy, held to the gate's bound like anything still
|
||||
// unsettled, so the gate fails safe at the bound and never waits for ever.
|
||||
if r.Kind == link.KindAccess && r.State == link.StateUnknown && !changes {
|
||||
// The same for a library (ADR 0328): unknown this look, or starting while a take walks under it, of a move that
|
||||
// leaves the module's libraries as they were, is not this move's.
|
||||
if (r.Kind == link.KindAccess || r.Kind == link.KindLibrary) && !changes &&
|
||||
(r.State == link.StateUnknown || (r.Kind == link.KindLibrary && r.State == link.StateStarting)) {
|
||||
continue
|
||||
}
|
||||
switch r.State {
|
||||
|
||||
Reference in New Issue
Block a user