diff --git a/modules/mesh-delivery-assigner/internal/assign/wants.go b/modules/mesh-delivery-assigner/internal/assign/wants.go index 732bb2d..7583257 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants.go @@ -204,7 +204,7 @@ func Wants(m Module, n Nodes, wants map[string]delivery.Want, lastHealthy map[st next.Backup, next.Words = before.Backup, before.Words // the answer to a failed backup stays while its target stays (ADR 0539 §16) } running, ran := n.Haves[node][m.Name] - if need, wait := m.mustAsk(node, before, had, ran, running.FP, d); wait { + if need, wait := m.mustAsk(node, before, had, ran, running.FP, running.Composed == "", d); wait { if need != nil { out.Record = append(out.Record, *need) } @@ -270,10 +270,16 @@ func short(fp string) string { // for the operator's hand (ADR 0539 §14, §15). A module the node never ran (it has no want and no have), a move judged neither, a module that is not of // policy record and a want that stays are written as any want; so is a revision of the bus module's composition at the // same fingerprint judged neither (a user list only, §7). -func (m Module) mustAsk(node string, before delivery.Want, had, ran bool, running string, d delivery.ComposedDigest) (*RecordNeed, bool) { +func (m Module) mustAsk(node string, before delivery.Want, had, ran bool, running string, haveNamesNoComposition bool, d delivery.ComposedDigest) (*RecordNeed, bool) { if (!had && !ran) || before.Removed || (before.FP == m.FP && before.Composed == d.Digest) { return nil, false } + // Right after the switch no node has a readable want, and a have from the old node-engine names a fingerprint and no composition + // (ADR 0539 §9, §11): a node running this fingerprint is given its want at it with no hold, whatever the controller could not judge, + // since the node agent applies nothing when the composition equals the declaration it kept. A new fingerprint still waits. + if !had && ran && running == m.FP && haveNamesNoComposition { + return nil, false + } // The fingerprint the bus ran before: its want's, or with no readable want the one its have names (ADR 0539 §15 bars a new one). prev := before.FP if !had { diff --git a/modules/mesh-delivery-assigner/internal/assign/wants_test.go b/modules/mesh-delivery-assigner/internal/assign/wants_test.go index 901d917..85fd0a1 100644 --- a/modules/mesh-delivery-assigner/internal/assign/wants_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/wants_test.go @@ -305,9 +305,10 @@ func TestAMoveJudgedNeitherOrNeverRunOrNotRecordedIsWrittenAtOnce(t *testing.T) if out := Wants(m, n, nil, nil); out.Wants["ace"].FP != fNew || len(out.Record) != 0 { t.Errorf("a node that never ran it: %+v", out) } - // but one that runs it with no want yet (the switch, or a want lost) is asked like any other + // but one that runs an older fingerprint with no want yet is asked like any other (one running F is not: N1 below) m, n, _ = movingTo(delivery.JudgedCannot) recording(&m) + n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} if out := Wants(m, n, nil, nil); out.Wants["ace"].FP == fNew || len(out.Record) == 0 { t.Errorf("a node that runs it with no want: %+v", out) } @@ -453,3 +454,40 @@ func TestAUserListOnlyRevisionOfTheBusIsWrittenAtOnceBeforeTheRecordHold(t *test t.Errorf("a module that is not the bus was written at once: %+v", out.Wants["ace"]) } } + +// Review round 2, N1 (ADR 0539 §9, §11): right after the switch no node has a readable want and every have comes from the old node-engine +// with a fingerprint and no composition, so every move is judged cannot-judge. A node running F with such a have gets its want at F +// with no record hold, for a record module and for the bus alike (the node agent applies nothing when the composition equals the +// declaration it kept); a new fingerprint still waits for its ask. +func TestAHaveFromBeforeTheSwitchGetsItsWantWithNoRecordHold(t *testing.T) { + for name, bus := range map[string]bool{"a record module": false, "the bus": true} { + m, n, _ := movingTo(delivery.JudgedCannot) + recording(&m) + m.Nodes = []string{"ace"} + if bus { + m.Manifest.Provides = []catalogue.Offer{{Name: "mesh-bus"}} + } + n.Haves["ace"]["mail"] = delivery.Have{FP: fNew, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} // no composition named + out := Wants(m, n, nil, nil) + if w := out.Wants["ace"]; w.FP != fNew || w.Composed != dig(fNew) { + t.Errorf("%s: ace's want = %+v, want F naming its composition", name, w) + } + if len(out.Record) != 0 { + t.Errorf("%s: asked at the switch: %+v", name, out.Record) + } + // a have that names a composition is judged as before + n.Haves["ace"]["mail"] = delivery.Have{FP: fNew, Composed: dig(fOld), Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + if out := Wants(m, n, nil, nil); out.Wants["ace"].FP == fNew && !bus && len(out.Record) == 0 { + t.Errorf("%s: a have naming a composition skipped the hold: %+v", name, out) + } + // a new fingerprint still asks (the bus is barred instead) + n.Haves["ace"]["mail"] = delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK} + out = Wants(m, n, nil, nil) + if w, ok := out.Wants["ace"]; ok { + t.Errorf("%s: a new fingerprint was written with no yes: %+v", name, w) + } + if !bus && len(out.Record) == 0 { + t.Errorf("%s: a new fingerprint was not asked", name) + } + } +}