From 9a70d697afee6665350ce84a34139918874ddf0b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:21:12 +0100 Subject: [PATCH] =?UTF-8?q?The=20view=20shows=20a=20row=20per=20module=20t?= =?UTF-8?q?he=20catalogue's=20placement=20puts=20on=20a=20node,=20and=20re?= =?UTF-8?q?ads=20the=20controller's=20assignments=20no=20more=20(issue=207?= =?UTF-8?q?74,=20ADR=200549=20=C2=A73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cmd/mesh-delivery-cli/main_test.go | 6 +- .../mesh-delivery-view/integration_test.go | 23 +++-- .../cmd/mesh-delivery-view/main.go | 4 +- .../mesh-delivery-view/internal/view/view.go | 83 ++++++---------- .../internal/view/view_test.go | 95 +++++++------------ .../internal/view/wants_test.go | 14 +-- modules/mesh-delivery-view/manifest_test.go | 2 +- modules/mesh-delivery-view/module.json | 1 - 8 files changed, 96 insertions(+), 132 deletions(-) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go index 1e0c713..a9ae081 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-cli/main_test.go @@ -19,7 +19,11 @@ import ( func serve(t *testing.T) string { t.Helper() m := view.New() - m.Apply(view.Entry{Bucket: view.AssignmentsBucket, Key: "a.x", Value: []byte(`{"node":"a","module":"x","build":"b1"}`)}) + placed, err := delivery.EncodePlacement(delivery.Placement{Node: "a", Modules: []delivery.PlacedModule{{Module: "x"}}}) + if err != nil { + t.Fatal(err) + } + m.Apply(view.Entry{Bucket: delivery.PlacementBucket, Key: "placement.a", Value: placed}) dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c") if err != nil { t.Fatal(err) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go index 55eedf8..a32dc7a 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go @@ -48,7 +48,7 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() kvs := map[string]jetstream.KeyValue{} - for _, b := range []string{delivery.HaveBucket, view.AssignmentsBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { + for _, b := range []string{delivery.HaveBucket, delivery.PlacementBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}) if err != nil { t.Fatal(err) @@ -68,9 +68,20 @@ func TestProcessWholeSeesTheBuckets(t *testing.T) { // 1134/AC8 } return b } - put(view.AssignmentsBucket, "a.x", []byte(`{"node":"a","module":"x","build":"b1"}`)) - put(view.AssignmentsBucket, "a.y", []byte(`{"node":"a","module":"y","build":"b2"}`)) - put(view.AssignmentsBucket, "b.x", []byte(`{"node":"b","module":"x","build":"b1","ended":"2026-10-18T11:30:00Z"}`)) + // The catalogue's placement (issue 774, ADR 0549 §3): x and y on a, nothing on b. + placeValue := func(node string, modules ...string) []byte { + p := delivery.Placement{Node: node} + for _, m := range modules { + p.Modules = append(p.Modules, delivery.PlacedModule{Module: m}) + } + v, err := delivery.EncodePlacement(p) + if err != nil { + t.Fatal(err) + } + return v + } + put(delivery.PlacementBucket, "placement.a", placeValue("a", "x", "y")) + put(delivery.PlacementBucket, "placement.b", placeValue("b")) put(view.BuildsBucket, "x.build.b1", []byte(`{"id":"b1","module":"x"}`)) put(view.BuildsBucket, "x.newest", []byte("b1")) put(delivery.HaveBucket, "a.x", haveValue("b1", delivery.PhaseChecking, delivery.HealthWaits)) @@ -150,7 +161,7 @@ func TestProcessWholeShowsWhatTheWatchmanSays(t *testing.T) { // 1168/AC5 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() kvs := map[string]jetstream.KeyValue{} - for _, b := range []string{delivery.HaveBucket, view.AssignmentsBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { + for _, b := range []string{delivery.HaveBucket, delivery.PlacementBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}) if err != nil { t.Fatal(err) @@ -236,7 +247,7 @@ func TestProcessWholeShowsTheIndexersBucketsOnceTheyExist(t *testing.T) { // 124 } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() - for _, b := range []string{delivery.HaveBucket, view.AssignmentsBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { + for _, b := range []string{delivery.HaveBucket, delivery.PlacementBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket} { if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: b, History: 5}); err != nil { t.Fatal(err) } diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go index ba9c342..3a2247b 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view/main.go @@ -1,4 +1,4 @@ -// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, assignments +// Command mesh-delivery-view is the operator's view of delivery (novox/hq ADR 0429 §3): it watches the have, the catalogue's placement // and builds buckets on its own bus credential, derives one row per module and node in memory, and serves them on a // local socket (for mesh-delivery-cli) and on a local web page. It starts, stops or retries nothing; the one thing it writes is the operator's answer to an ask that needs no proof of who // answered, under `.view` in its bucket answers (ADR 0528 clause 6). @@ -106,7 +106,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro return err }) model.SetBacklog(view.BacklogOver(js, 5*time.Second)) - for _, bucket := range []string{delivery.HaveBucket, view.AssignmentsBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket, delivery.CandidatesBucket, delivery.ModulesBucket, delivery.AsksBucket, delivery.AnswersBucket, delivery.BuilderBuildsBucket, delivery.AssignerWantsBucket, delivery.SourceBucket, delivery.ControllerComposedBucket, delivery.AssignerAsksBucket, delivery.ViewAnswersBucket, delivery.TakenBucket} { + for _, bucket := range []string{delivery.HaveBucket, delivery.PlacementBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket, delivery.CandidatesBucket, delivery.ModulesBucket, delivery.AsksBucket, delivery.AnswersBucket, delivery.BuilderBuildsBucket, delivery.AssignerWantsBucket, delivery.SourceBucket, delivery.ControllerComposedBucket, delivery.AssignerAsksBucket, delivery.ViewAnswersBucket, delivery.TakenBucket} { go follow(ctx, js, bucket, false, model, logger) } // The modules' and the source's history give each fingerprint's first commit and each head's source revision (ADR 0556). diff --git a/modules/mesh-delivery-view/internal/view/view.go b/modules/mesh-delivery-view/internal/view/view.go index ccdeb3b..165ad55 100644 --- a/modules/mesh-delivery-view/internal/view/view.go +++ b/modules/mesh-delivery-view/internal/view/view.go @@ -4,6 +4,7 @@ package view import ( "encoding/json" "fmt" + "maps" "slices" "sort" "strings" @@ -215,7 +216,7 @@ type UnreadableRow struct { // Model holds the rows' inputs in memory and nothing else: a restart fills it again from the buckets' last values. type Model struct { mu sync.Mutex - assigns map[string]assignment + placed *delivery.Placed // the catalogue's placement (the indexer's state, ADR 0549 §3) builds map[string]buildEntry // by .build. groups map[string]groupEntry // the indexer's candidates, by group modules map[string]moduleEntry // the indexer's modules, by module @@ -263,22 +264,20 @@ type stuckEntry struct { err string } -// assignment holds only the fields of the controller's Assignment the view reads. -type assignment struct { - Node string `json:"node"` - Module string `json:"module"` - Build string `json:"build"` - Commit string `json:"commit"` - Since time.Time `json:"since"` - Ended time.Time `json:"ended"` - State string `json:"state"` - PulledIn bool `json:"pulled-in"` - RequiredBy string `json:"required-by"` -} +// pair is a module the catalogue's placement puts on a node. +type pair struct{ Node, Module string } -// open says whether the value is an assignment that stands for the module on its node, as the controller's -// Assignment.Stands does. A module pulled in by another stands too. -func (a assignment) open() bool { return a.Ended.IsZero() && a.State == "" } +// placedPairs is every module the catalogue's placement puts on a node, by node then module. The caller holds mu. +func (m *Model) placedPairs() []pair { + byNode := m.placed.ByNode() + var out []pair + for _, node := range slices.Sorted(maps.Keys(byNode)) { + for _, module := range byNode[node] { + out = append(out, pair{Node: node, Module: module}) + } + } + return out +} // build holds only the fields of the controller's Build record the view reads. A record with no state and no failure // is a done build. @@ -356,7 +355,7 @@ type nodeEntry struct { // New is an empty model. func New() *Model { - return &Model{assigns: map[string]assignment{}, builds: map[string]buildEntry{}, groups: map[string]groupEntry{}, modules: map[string]moduleEntry{}, keys: map[string]keyEntry{}, asks: map[string]askRecord{}, wants: map[string]wantEntry{}, told: map[string]bool{}, initial: map[string]bool{}, composed: map[string]composedEntry{}, aasks: map[string]assignerAskRecord{}, chosen: map[string]delivery.ViewAnswer{}, answers: map[string]string{}, answerOf: map[string]delivery.Answer{}, holds: map[string]heldEntry{}, newest: map[string]string{}, + return &Model{placed: delivery.NewPlaced(), builds: map[string]buildEntry{}, groups: map[string]groupEntry{}, modules: map[string]moduleEntry{}, keys: map[string]keyEntry{}, asks: map[string]askRecord{}, wants: map[string]wantEntry{}, told: map[string]bool{}, initial: map[string]bool{}, composed: map[string]composedEntry{}, aasks: map[string]assignerAskRecord{}, chosen: map[string]delivery.ViewAnswer{}, answers: map[string]string{}, answerOf: map[string]delivery.Answer{}, holds: map[string]heldEntry{}, newest: map[string]string{}, haves: map[string]haveEntry{}, nodes: map[string]nodeEntry{}, presence: map[string]presenceEntry{}, stuck: map[string]stuckEntry{}, modHist: map[string][]delivery.Module{}, srcHist: map[uint64]map[string]string{}, taken: map[string]delivery.Taken{}, @@ -529,16 +528,9 @@ func (m *Model) apply(e Entry) { } st, err := delivery.DecodeStuck(e.Value) m.stuck[e.Key] = stuckEntry{value: st, err: errText(err)} - case AssignmentsBucket: - if e.Deleted { - delete(m.assigns, e.Key) - return - } - var a assignment - if json.Unmarshal(e.Value, &a) != nil || a.Node == "" || a.Module == "" { - return - } - m.assigns[a.Node+"."+a.Module] = a + case delivery.PlacementBucket: + // A key that does not read places nothing on its node; the indexer's own read tool says why. + _, _, _ = m.placed.Put(e.Key, e.Value, e.Deleted, e.At) case delivery.CandidatesBucket: if e.Deleted { delete(m.groups, e.Key) @@ -714,18 +706,12 @@ func (m *Model) snapshot() Snapshot { m.mu.Lock() defer m.mu.Unlock() rows := []Row{} - for key, a := range m.assigns { - if !a.open() { - continue - } - r := Row{Node: a.Node, Module: a.Module, Status: StatusAssigned, Assigned: a.Build} - if a.PulledIn { - r.RequiredBy = a.RequiredBy - } + for _, a := range m.placedPairs() { + r := Row{Node: a.Node, Module: a.Module, Status: StatusAssigned} var waits []string hk, herr := delivery.HaveKey(a.Node, a.Module) if herr != nil { - hk = key + continue } h, has := m.haves[hk] // The controller no longer writes the build of an assignment (R6): the row is the want and the have of the node. @@ -745,17 +731,8 @@ func (m *Model) snapshot() Snapshot { r.Have = h.have.FP } r.Since, r.Detail = h.at, h.have.Detail - if wanted { - if h.have.FP != w.value.FP { - waits = append(waits, "the node has not applied "+w.value.FP) - } - } else if a.Build != "" && h.have.Build != a.Build { - waits = append(waits, "the node has not applied "+a.Build) - } - } - if id := m.newest[a.Module]; id != "" && a.Build != "" && id != a.Build { - if b, ok := m.builds[a.Module+".build."+id]; ok && b.done() { - waits = append(waits, a.Module+" has a newer build "+id+" not yet assigned") + if wanted && h.have.FP != w.value.FP { + waits = append(waits, "the node has not applied "+w.value.FP) } } r.Waits = strings.Join(waits, "; ") @@ -766,7 +743,7 @@ func (m *Model) snapshot() Snapshot { if perr != nil { continue } - if a, ok := m.assigns[node+"."+module]; ok && a.open() { + if _, ok := m.placed.Since(node, module); ok { continue } r := Row{Node: node, Module: module, Status: StatusLeftBehind, Since: h.at, Error: h.err} @@ -984,8 +961,7 @@ func (s Snapshot) Counted() int { // The buckets the view reads besides the have bucket (which mesh-sdk's delivery package names). const ( - AssignmentsBucket = "mesh-controller_assignments" - BuildsBucket = "mesh-controller_builds" + BuildsBucket = "mesh-controller_builds" ) // The statuses of a row. @@ -1176,13 +1152,10 @@ func (m *Model) sourceRow() *SourceRow { waiting = append(waiting, fmt.Sprintf("%s on %s runs %s, not yet running and healthy", module, node, short(w.value.FP))) } } - for key, a := range m.assigns { - if !a.open() { - continue - } + for _, a := range m.placedPairs() { wk, werr := delivery.HaveKey(a.Node, a.Module) if werr != nil { - wk = key + continue } if _, ok := m.wants[wk]; ok { continue diff --git a/modules/mesh-delivery-view/internal/view/view_test.go b/modules/mesh-delivery-view/internal/view/view_test.go index d228cab..5acc786 100644 --- a/modules/mesh-delivery-view/internal/view/view_test.go +++ b/modules/mesh-delivery-view/internal/view/view_test.go @@ -25,9 +25,22 @@ func haveE(t *testing.T, node, module string, v []byte, at time.Time) Entry { return Entry{Bucket: delivery.HaveBucket, Key: node + "." + module, Value: v, At: at} } -func assign(node, module, build, extra string) Entry { - v := `{"node":"` + node + `","module":"` + module + `","build":"` + build + `","commit":"c","since":"2026-10-18T11:00:00Z"` + extra + `}` - return Entry{Bucket: AssignmentsBucket, Key: node + "." + module, Value: []byte(v), At: t0} +// place is the catalogue's placement of node, as the indexer keeps it (ADR 0549 §2): the modules placed there. +func place(t *testing.T, node string, modules ...string) Entry { + t.Helper() + p := delivery.Placement{Node: node} + for _, m := range modules { + p.Modules = append(p.Modules, delivery.PlacedModule{Module: m}) + } + key, err := delivery.PlacementKey(node) + if err != nil { + t.Fatal(err) + } + v, err := delivery.EncodePlacement(p) + if err != nil { + t.Fatal(err) + } + return Entry{Bucket: delivery.PlacementBucket, Key: key, Value: v, At: t0} } func buildRec(module, id, extra string) []Entry { @@ -58,7 +71,7 @@ func fill(m *Model, es ...Entry) { // base: two nodes (a, b), three modules (x, y, z). func base(t *testing.T) []Entry { es := []Entry{ - assign("a", "x", "b1", ""), assign("a", "y", "b2", ""), assign("b", "x", "b1", ""), assign("b", "z", "b3", ""), + place(t, "a", "x", "y"), place(t, "b", "x", "z"), haveE(t, "a", "x", have(t, "b1", delivery.PhaseRunning, delivery.HealthOK, ""), t0.Add(1*time.Minute)), haveE(t, "b", "x", have(t, "b1", delivery.PhaseChecking, delivery.HealthWaits, "starting"), t0.Add(2*time.Minute)), haveE(t, "b", "z", have(t, "b3", delivery.PhaseRunning, delivery.HealthOK, ""), t0.Add(3*time.Minute)), @@ -77,68 +90,35 @@ func TestRowsPerAssignedModuleAndNode(t *testing.T) { // 1134/AC1 t.Fatalf("want 4 rows, got %d counted %d: %+v", len(s.Rows), s.Counted(), s.Rows) } r := find(t, s, "b", "x") - want := Row{Node: "b", Module: "x", Status: StatusAssigned, Assigned: "b1", Have: "b1", Phase: "checking", Health: "waits", + want := Row{Node: "b", Module: "x", Status: StatusAssigned, Have: "b1", Phase: "checking", Health: "waits", Since: t0.Add(2 * time.Minute), Detail: "starting"} if !reflect.DeepEqual(r, want) { t.Fatalf("row = %+v, want %+v", r, want) } y := find(t, s, "a", "y") - if y.Waits != "no have yet" || y.Assigned != "b2" || y.Have != "" { + if y.Waits != "no have yet" || y.Have != "" { t.Fatalf("a.y = %+v, want 'no have yet'", y) } } -func TestWaitsForTheNodeAndForANewerBuild(t *testing.T) { // 1134/AC2 +func TestLeftBehindIsWhatThePlacementDoesNotPut(t *testing.T) { // 1134/AC3, issue 774 (the catalogue's placement) m := New() fill(m, base(t)...) - fill(m, haveE(t, "a", "x", have(t, "b0", delivery.PhaseRunning, delivery.HealthOK, ""), t0)) - if w := find(t, m.Snapshot(), "a", "x").Waits; w != "the node has not applied b1" { - t.Fatalf("waits = %q", w) - } - fill(m, buildRec("z", "b4", "")...) - if w := find(t, m.Snapshot(), "b", "z").Waits; w != "z has a newer build b4 not yet assigned" { - t.Fatalf("waits = %q", w) - } - // a newest build that is not done is not a newer build to assign - fill(m, buildRec("z", "b5", `,"failed":"boom"`)...) - if w := find(t, m.Snapshot(), "b", "z").Waits; w != "" { - t.Fatalf("a failed newest build must not be waited on, waits = %q", w) - } - fill(m, buildRec("z", "b6", `,"state":"asked"`)...) - if w := find(t, m.Snapshot(), "b", "z").Waits; w != "" { - t.Fatalf("an asked newest build must not be waited on, waits = %q", w) - } -} - -func TestLeftBehindIsNotRunningAndPulledInIs(t *testing.T) { // 1134/AC3 (both halves) - for name, extra := range map[string]string{"ended": `,"ended":"2026-10-18T11:30:00Z"`, "state": `,"state":"pending"`} { - m := New() - fill(m, base(t)...) - fill(m, assign("b", "z", "b3", extra)) - s := m.Snapshot() - r := find(t, s, "b", "z") - if r.Status != StatusLeftBehind { - t.Errorf("%s: status = %q, want %q", name, r.Status, StatusLeftBehind) - } - if s.Counted() != 3 { - t.Errorf("%s: counted = %d, want 3", name, s.Counted()) - } - } - m := New() - fill(m, base(t)...) - fill(m, haveE(t, "a", "w", have(t, "b9", delivery.PhaseRunning, delivery.HealthOK, ""), t0)) // no assignment at all + fill(m, place(t, "b", "x")) // z taken off b s := m.Snapshot() - if find(t, s, "a", "w").Status != StatusLeftBehind || s.Counted() != 4 { - t.Fatalf("a have with no assignment: %+v counted %d", find(t, s, "a", "w"), s.Counted()) + if r := find(t, s, "b", "z"); r.Status != StatusLeftBehind || s.Counted() != 3 { + t.Errorf("status = %q counted %d, want %q and 3", r.Status, s.Counted(), StatusLeftBehind) } - // A module pulled in by another stands: shown and counted, with what required it. m = New() fill(m, base(t)...) - fill(m, assign("b", "z", "b3", `,"pulled-in":true,"required-by":"x"`)) + fill(m, haveE(t, "a", "w", have(t, "b9", delivery.PhaseRunning, delivery.HealthOK, ""), t0)) // placed nowhere s = m.Snapshot() - r := find(t, s, "b", "z") - if r.Status != StatusAssigned || r.RequiredBy != "x" || r.Have != "b3" || s.Counted() != 4 { - t.Fatalf("pulled-in row = %+v counted %d, want shown and counted with required-by x", r, s.Counted()) + if find(t, s, "a", "w").Status != StatusLeftBehind || s.Counted() != 4 { + t.Fatalf("a have with no placement: %+v counted %d", find(t, s, "a", "w"), s.Counted()) + } + fill(m, Entry{Bucket: delivery.PlacementBucket, Key: "placement.b", Deleted: true}) + if s := m.Snapshot(); s.Counted() != 2 || find(t, s, "b", "x").Status != StatusLeftBehind { + t.Fatalf("b's placement deleted: counted %d, %+v", s.Counted(), s.Rows) } } @@ -181,18 +161,13 @@ func TestOnlyTheAffectedRowChanges(t *testing.T) { // 1134/AC5 if r := find(t, after, "b", "x"); r.Phase != "running" || r.Health != "ok" || !r.Since.Equal(t0.Add(9*time.Minute)) { t.Fatalf("changed row = %+v", r) } - // an assignment change and a build change touch only their rows - fill(m, assign("a", "y", "b7", "")) - if r := find(t, m.Snapshot(), "a", "y"); r.Assigned != "b7" { - t.Fatalf("assignment change not seen: %+v", r) + // a want's change touches only its row + fill(m, wantE(t, "a", "y", delivery.Want{FP: fpNew})) + if r := find(t, m.Snapshot(), "a", "y"); r.Assigned != fpNew { + t.Fatalf("want change not seen: %+v", r) } if !reflect.DeepEqual(find(t, m.Snapshot(), "b", "z"), find(t, after, "b", "z")) { - t.Fatal("an assignment of another row changed b.z") - } - fill(m, buildRec("x", "b8", "")...) - s := m.Snapshot() - if find(t, s, "a", "x").Waits == "" || find(t, s, "b", "x").Waits == "" || find(t, s, "b", "z").Waits != "" { - t.Fatalf("a build of x must touch x's rows only: %+v", s.Rows) + t.Fatal("a want of another row changed b.z") } // a deleted key (a bucket's expiry) removes its value m.Apply(Entry{Bucket: delivery.HaveBucket, Key: "a.x", Deleted: true}) diff --git a/modules/mesh-delivery-view/internal/view/wants_test.go b/modules/mesh-delivery-view/internal/view/wants_test.go index 6019daa..80cdbc5 100644 --- a/modules/mesh-delivery-view/internal/view/wants_test.go +++ b/modules/mesh-delivery-view/internal/view/wants_test.go @@ -101,7 +101,7 @@ func TestEveryWantIsARowAgainstItsHave(t *testing.T) { func TestASourceRevisionIsDerivedFromWantsAndHaves(t *testing.T) { m := New() fill(m, sourceE(t, 14355), moduleE(t, "web", fpNew), moduleE(t, "mail", fpNew), - assign("ace", "web", "b-1", ""), assign("g14", "web", "b-1", ""), + place(t, "ace", "web"), place(t, "g14", "web"), wantE(t, "ace", "web", delivery.Want{FP: fpNew}), haveFP(t, "ace", "web", fpNew, delivery.HealthOK), wantE(t, "g14", "web", delivery.Want{FP: fpNew}), haveFP(t, "g14", "web", fpNew, delivery.HealthOK), wantE(t, "ace", "mail", delivery.Want{FP: fpNew}), haveFP(t, "ace", "mail", fpNew, delivery.HealthOK)) @@ -119,7 +119,7 @@ func TestASourceRevisionIsDerivedFromWantsAndHaves(t *testing.T) { if s.State != SourceNotDelivered || !strings.Contains(strings.Join(s.Waiting, ";"), "mail") { t.Errorf("a want that is not the module's fingerprint: %+v", s) } - fill(m, wantE(t, "ace", "mail", delivery.Want{FP: fpNew}), assign("novox", "web", "b-1", "")) + fill(m, wantE(t, "ace", "mail", delivery.Want{FP: fpNew}), place(t, "novox", "web")) s = m.Snapshot().Source if s.State != SourceNotDelivered || !strings.Contains(strings.Join(s.Waiting, ";"), "novox") { t.Errorf("a node assigned the module with no want: %+v", s) @@ -177,7 +177,7 @@ func TestTheViewSaysOnceThatASourceRevisionIsDelivered(t *testing.T) { said = append(said, e) return nil }) - fill(m, sourceE(t, 14355), moduleE(t, "web", fpNew), assign("ace", "web", "b-1", ""), + fill(m, sourceE(t, 14355), moduleE(t, "web", fpNew), place(t, "ace", "web"), wantE(t, "ace", "web", delivery.Want{FP: fpNew})) if len(said) != 0 { t.Fatalf("said %v before any have", said) @@ -213,7 +213,7 @@ func TestTheViewSaysOnceThatASourceRevisionIsDelivered(t *testing.T) { // have: the row shows the fingerprint wanted and waits while the node runs another. func TestARowReadsTheWantWhenTheAssignmentNamesNoBuild(t *testing.T) { m := New() - fill(m, assign("ace", "web", "", ""), wantE(t, "ace", "web", delivery.Want{FP: fpNew}), haveFP(t, "ace", "web", fpOld, delivery.HealthOK)) + fill(m, place(t, "ace", "web"), wantE(t, "ace", "web", delivery.Want{FP: fpNew}), haveFP(t, "ace", "web", fpOld, delivery.HealthOK)) r := find(t, m.Snapshot(), "ace", "web") if r.Assigned != fpNew || r.Have != fpOld || !strings.Contains(r.Waits, "has not applied") { t.Fatalf("row = %+v, want it to wait for the want's fingerprint", r) @@ -228,10 +228,12 @@ func TestARowReadsTheWantWhenTheAssignmentNamesNoBuild(t *testing.T) { // and a have nothing is assigned to keeps the module's name. func TestADottedModuleNameIsFoundAndKept(t *testing.T) { m := New() - fill(m, assign("ace", "novox.be", "", ""), wantE(t, "ace", "novox.be", delivery.Want{FP: fpNew}), haveFP(t, "ace", "novox.be", fpNew, delivery.HealthOK), + // The catalogue's placement refuses a dotted module name (mesh-sdk delivery.Placement), so such a module is never placed: + // its have is a row left behind under its own name. + fill(m, place(t, "ace"), wantE(t, "ace", "novox.be", delivery.Want{FP: fpNew}), haveFP(t, "ace", "novox.be", fpNew, delivery.HealthOK), haveFP(t, "ace", "old.name", fpNew, delivery.HealthOK)) s := m.Snapshot() - if r := find(t, s, "ace", "novox.be"); r.Have != fpNew || r.Waits != "" { + if r := find(t, s, "ace", "novox.be"); r.Have != fpNew || r.Status != StatusLeftBehind { t.Errorf("the dotted module's row = %+v", r) } if r := find(t, s, "ace", "old.name"); r.Status != StatusLeftBehind { diff --git a/modules/mesh-delivery-view/manifest_test.go b/modules/mesh-delivery-view/manifest_test.go index 50985b4..898e46f 100644 --- a/modules/mesh-delivery-view/manifest_test.go +++ b/modules/mesh-delivery-view/manifest_test.go @@ -87,7 +87,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7 if m.Module != "mesh-delivery-view" { t.Errorf("module = %q", m.Module) } - want := []string{"mesh-build-go.pauses", "mesh-build-go.steps", "mesh-build-image.pauses", "mesh-build-image.steps", "mesh-build-node.pauses", "mesh-build-node.steps", "mesh-config.resolved", "mesh-controller.assignments", "mesh-controller.builds", "mesh-controller.composed", "mesh-delivery-assigner.asks", "mesh-delivery-assigner.differences", "mesh-delivery-assigner.wants", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.heard", "mesh-delivery-indexer.modules", "mesh-delivery-indexer.taken", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "node-engine.have"} // build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9 + want := []string{"mesh-build-go.pauses", "mesh-build-go.steps", "mesh-build-image.pauses", "mesh-build-image.steps", "mesh-build-node.pauses", "mesh-build-node.steps", "mesh-config.resolved", "mesh-controller.builds", "mesh-controller.composed", "mesh-delivery-assigner.asks", "mesh-delivery-assigner.differences", "mesh-delivery-assigner.wants", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.heard", "mesh-delivery-indexer.modules", "mesh-delivery-indexer.taken", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "node-engine.have"} // build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9 got := slices.Clone(m.Reads) slices.Sort(got) if !reflect.DeepEqual(got, want) { diff --git a/modules/mesh-delivery-view/module.json b/modules/mesh-delivery-view/module.json index 475f6ca..fee8fe8 100644 --- a/modules/mesh-delivery-view/module.json +++ b/modules/mesh-delivery-view/module.json @@ -30,7 +30,6 @@ { "name": "reach", "history": 1 } ], "reads": [ - "mesh-controller.assignments", "mesh-controller.builds", "mesh-build-go.pauses", "mesh-build-go.steps",