The view shows a row per module the catalogue's placement puts on a node, and reads the controller's assignments no more (issue 774, ADR 0549 §3)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 `<ask key>.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).
|
||||
|
||||
@@ -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 <module>.build.<id>
|
||||
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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
{ "name": "reach", "history": 1 }
|
||||
],
|
||||
"reads": [
|
||||
"mesh-controller.assignments",
|
||||
"mesh-controller.builds",
|
||||
"mesh-build-go.pauses",
|
||||
"mesh-build-go.steps",
|
||||
|
||||
Reference in New Issue
Block a user