The view shows the controller's judgement of the move beside each want (issue 1906 D7, ADR 0539 §14)
This commit is contained in:
@@ -139,7 +139,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
}
|
||||
model.SetWriter(kvWriter{ctx: ctx, js: js})
|
||||
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.AssignerAsksBucket, delivery.ViewAnswersBucket} {
|
||||
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} {
|
||||
go follow(ctx, js, bucket, model, logger)
|
||||
}
|
||||
select {
|
||||
|
||||
@@ -225,7 +225,13 @@ func wantText(s Snapshot) [][]string {
|
||||
if w.Rule != 0 {
|
||||
words = fmt.Sprintf("rule %d: %s", w.Rule, w.Words)
|
||||
}
|
||||
out = append(out, []string{w.Node, w.Module, want, have, w.State, words})
|
||||
judged := w.Judgement
|
||||
if judged == "" {
|
||||
judged = "none"
|
||||
} else if w.Judged != "" {
|
||||
words = strings.TrimSpace(words + " " + w.Judged)
|
||||
}
|
||||
out = append(out, []string{w.Node, w.Module, want, have, w.State, judged, words})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -257,7 +263,7 @@ func backlogText(s Snapshot) [][]string {
|
||||
|
||||
var backlogHead = []string{"LANGUAGE", "KIND", "WAITING", "HELD", "OLDEST WAITING"}
|
||||
|
||||
var wantHead = []string{"NODE", "MODULE", "WANT", "HAVE", "STATE", "WORDS"}
|
||||
var wantHead = []string{"NODE", "MODULE", "WANT", "HAVE", "STATE", "MOVE JUDGED", "WORDS"}
|
||||
|
||||
var head = []string{"NODE", "MODULE", "ASSIGNED", "HAVE", "PHASE", "HEALTH", "SINCE", "DETAIL", "WAITS"}
|
||||
|
||||
|
||||
@@ -100,6 +100,10 @@ type WantRow struct {
|
||||
Newest string `json:"newest,omitempty"`
|
||||
Rule int `json:"rule,omitempty"`
|
||||
Words string `json:"words,omitempty"`
|
||||
// Judgement is the controller's judgement of the move to the composition the want names (recreates, moves-data, neither or
|
||||
// cannot-judge), and Judged its words; empty when the controller wrote none (ADR 0539 §14).
|
||||
Judgement string `json:"judgement,omitempty"`
|
||||
Judged string `json:"judged,omitempty"`
|
||||
}
|
||||
|
||||
// The states of a source revision (ADR 0429 §1).
|
||||
@@ -206,23 +210,24 @@ 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
|
||||
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
|
||||
keys map[string]keyEntry // the builder's build keys, by <module>.<fingerprint>
|
||||
asks map[string]askRecord // the gate's asks, by key
|
||||
wants map[string]wantEntry // the assigner's wants, by <node>.<module>
|
||||
source *sourceEntry // the gate's source key
|
||||
aasks map[string]assignerAskRecord // the assigner's asks (hold, restore), by key
|
||||
chosen map[string]delivery.ViewAnswer // the answers the view wrote, by ask key
|
||||
writer Writer
|
||||
backlog *Backlog
|
||||
answers map[string]string // by key: the router id of the asking the teller's answer is for ("" when unreadable)
|
||||
newest map[string]string // module -> id
|
||||
haves map[string]haveEntry
|
||||
nodes map[string]nodeEntry
|
||||
mu sync.Mutex
|
||||
assigns map[string]assignment
|
||||
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
|
||||
keys map[string]keyEntry // the builder's build keys, by <module>.<fingerprint>
|
||||
asks map[string]askRecord // the gate's asks, by key
|
||||
wants map[string]wantEntry // the assigner's wants, by <node>.<module>
|
||||
source *sourceEntry // the gate's source key
|
||||
composed map[string]composedEntry // the controller's digests and judgements, by <node>.<module>.<hex>
|
||||
aasks map[string]assignerAskRecord // the assigner's asks (hold, restore), by key
|
||||
chosen map[string]delivery.ViewAnswer // the answers the view wrote, by ask key
|
||||
writer Writer
|
||||
backlog *Backlog
|
||||
answers map[string]string // by key: the router id of the asking the teller's answer is for ("" when unreadable)
|
||||
newest map[string]string // module -> id
|
||||
haves map[string]haveEntry
|
||||
nodes map[string]nodeEntry
|
||||
// the watchman's buckets, by key
|
||||
presence map[string]presenceEntry
|
||||
stuck map[string]stuckEntry
|
||||
@@ -290,6 +295,11 @@ type wantEntry struct {
|
||||
err string
|
||||
}
|
||||
|
||||
type composedEntry struct {
|
||||
value delivery.ComposedDigest
|
||||
err string
|
||||
}
|
||||
|
||||
type sourceEntry struct {
|
||||
revision uint64
|
||||
err string
|
||||
@@ -324,7 +334,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{}, aasks: map[string]assignerAskRecord{}, chosen: map[string]delivery.ViewAnswer{}, answers: map[string]string{}, newest: map[string]string{},
|
||||
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{}, composed: map[string]composedEntry{}, aasks: map[string]assignerAskRecord{}, chosen: map[string]delivery.ViewAnswer{}, answers: map[string]string{}, newest: map[string]string{},
|
||||
haves: map[string]haveEntry{}, nodes: map[string]nodeEntry{},
|
||||
presence: map[string]presenceEntry{}, stuck: map[string]stuckEntry{}}
|
||||
}
|
||||
@@ -431,6 +441,13 @@ func (m *Model) Apply(e Entry) {
|
||||
}
|
||||
w, err := delivery.DecodeWant(e.Value)
|
||||
m.wants[key] = wantEntry{value: w, err: errText(err)}
|
||||
case delivery.ControllerComposedBucket:
|
||||
if e.Deleted {
|
||||
delete(m.composed, e.Key)
|
||||
return
|
||||
}
|
||||
d, err := delivery.DecodeComposedDigest(e.Value)
|
||||
m.composed[e.Key] = composedEntry{value: d, err: errText(err)}
|
||||
case delivery.SourceBucket:
|
||||
if e.Key != delivery.SourceMainKey {
|
||||
return
|
||||
@@ -903,6 +920,11 @@ func (m *Model) wantRows() []WantRow {
|
||||
if has && h.err == "" {
|
||||
row.Have, row.Phase, row.Health = h.have.FP, string(h.have.Phase), string(h.have.Health)
|
||||
}
|
||||
if ckey, err := delivery.ComposedKey(node, module, v.FP); err == nil && !v.Removed {
|
||||
if c, ok := m.composed[ckey]; ok && c.err == "" {
|
||||
row.Judgement, row.Judged = string(c.value.Judgement), c.value.Words
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case v.Removed:
|
||||
row.State = WantRemoved
|
||||
|
||||
@@ -130,7 +130,29 @@ func TestTheWantsPageSaysWantsAgainstHaves(t *testing.T) {
|
||||
m := New()
|
||||
fill(m, wantE(t, "ace", "web", delivery.Want{FP: fpNew}), haveFP(t, "ace", "web", fpOld, delivery.HealthOK))
|
||||
text := wantText(m.Snapshot())
|
||||
if len(text) != 1 || text[0][0] != "ace" || text[0][1] != "web" || text[0][2] != fpNew || text[0][3] != fpOld || text[0][4] != WantPending {
|
||||
if len(text) != 1 || text[0][0] != "ace" || text[0][1] != "web" || text[0][2] != fpNew || text[0][3] != fpOld || text[0][4] != WantPending || text[0][5] != "none" {
|
||||
t.Errorf("text = %v", text)
|
||||
}
|
||||
}
|
||||
|
||||
// 1906/D7 (ADR 0539 §14): the controller's judgement of the move a want names is shown beside it, with its words.
|
||||
func TestTheJudgementOfTheMoveIsShownBesideTheWant(t *testing.T) {
|
||||
m := New()
|
||||
key, err := delivery.ComposedKey("ace", "web", fpNew)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, err := delivery.EncodeComposedDigest(delivery.ComposedDigest{Digest: "sha256:" + strings.Repeat("c", 64), Judgement: delivery.JudgedCannot, Words: "the composition it runs is gone"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fill(m, wantE(t, "ace", "web", delivery.Want{FP: fpNew}), Entry{Bucket: delivery.ControllerComposedBucket, Key: key, Value: v, At: t0})
|
||||
rows := m.Snapshot().Wants
|
||||
if len(rows) != 1 || rows[0].Judgement != string(delivery.JudgedCannot) || !strings.Contains(rows[0].Judged, "gone") {
|
||||
t.Fatalf("rows = %+v", rows)
|
||||
}
|
||||
text := wantText(m.Snapshot())
|
||||
if len(text[0]) != 7 || text[0][5] != "cannot-judge" {
|
||||
t.Errorf("text = %v", text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
|
||||
if m.Module != "mesh-delivery-view" {
|
||||
t.Errorf("module = %q", m.Module)
|
||||
}
|
||||
want := []string{"mesh-controller.assignments", "mesh-controller.builds", "mesh-delivery-assigner.asks", "mesh-delivery-assigner.differences", "mesh-delivery-assigner.wants", "mesh-delivery-builder.builds", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.modules", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "node-engine.have"} // 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9
|
||||
want := []string{"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-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.modules", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "node-engine.have"} // 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9
|
||||
got := slices.Clone(m.Reads)
|
||||
slices.Sort(got)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"reads": [
|
||||
"mesh-controller.assignments",
|
||||
"mesh-controller.builds",
|
||||
"mesh-controller.composed",
|
||||
"mesh-delivery-assigner.asks",
|
||||
"mesh-delivery-assigner.differences",
|
||||
"mesh-delivery-assigner.wants",
|
||||
|
||||
Reference in New Issue
Block a user