diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index 47c923a..3575068 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -9,7 +9,6 @@ import ( "io" "os" "path/filepath" - "slices" "strings" "testing" "time" @@ -223,8 +222,8 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) { } } - // Review of L2 (D1): without the indexer's placement state it starts all the same, and once the bucket is made (the - // indexer and the assigner come in one delivery) it is read with no restart: a module only the placement names is + // Without the seat's placement state it starts all the same, and once the bucket is made (the controller makes + // it from the manifest) it is read with no restart: a module only the placement names is // said as placed by one source only. pb := startBed(t, delivery.PlacementBucket) pdone := pb.start(t) @@ -297,10 +296,6 @@ func (b *bed) manifest(t *testing.T, module, body string) string { // 1297/AC1: against the real bus, a fixture change touching one module on one machine is composed and answered within // seconds under the checker's key of the check, naming the machine composed and the mesh-sdk it composed with. func TestAChangeIsComposedOnTheBus(t *testing.T) { - // The placement answers' bound (ADR 0559 §5), shortened before the program starts and put back after it stops. - bound := assign.AnswerBound - t.Cleanup(func() { assign.AnswerBound = bound }) - assign.AnswerBound = 1500 * time.Millisecond b := startBed(t, "") trunk := b.manifest(t, "web", `{"module":"web","version":"1"}`) changed := b.manifest(t, "web", `{"module":"web","version":"2"}`) @@ -352,63 +347,6 @@ func TestAChangeIsComposedOnTheBus(t *testing.T) { } else if info, err := st.Info(b.ctx); err != nil || info.State.Msgs != 0 { t.Errorf("a group without a mesh-sdk member put %d asks on the Go build seat (%v)", info.State.Msgs, err) } - - // ADR 0559 §5 (issue 1126): a catalogue pull request that places g14 for the first time (the seed's case) is composed - // with its candidate placement, touches g14, and waits for g14's answer at its head; g14 answering the same passes it. - // A node that does not answer within the bound fails the check, named, at the bound's end with no other event; its later - // answer at the same head passes the same heads' composition. - record, _ := delivery.EncodeHaveNode(delivery.HaveNode{Record: &delivery.NodeRecord{Node: "g14"}}) - b.putJSON(t, delivery.HaveBucket, "g14.node", record) - placed := func(group string, number int, head string) (string, uint64) { - t.Helper() - value, err := delivery.EncodePlacementCandidate(delivery.PlacementCandidate{Group: group, - Pull: delivery.Head{Repository: delivery.CatalogueRepository, Number: number, Head: head}, - Placement: &delivery.Placement{Node: "g14", Modules: []delivery.PlacedModule{{Module: "web"}}}}) - if err != nil { - t.Fatal(err) - } - key, _ := delivery.PlacementCandidateKey(group, "g14") - rev, err := b.buckets[delivery.PlacementCandidatesBucket].Put(b.ctx, key, value) - if err != nil { - t.Fatal(err) - } - pull := []delivery.CandidatePull{{Head: delivery.Head{Repository: delivery.CatalogueRepository, Number: number, Head: head}, Modules: []string{}}} - hash, _ := delivery.HeadsHash([]delivery.Head{pull[0].Head}) - cand, err := delivery.EncodeCandidate(delivery.Candidate{Group: group, HeadsHash: hash, Pulls: pull, FPSet: delivery.FingerprintSet(pull)}) - if err != nil { - t.Fatal(err) - } - ckey, _ := delivery.CandidatesKey(group) - b.putJSON(t, delivery.CandidatesBucket, ckey, cand) - key, _ = delivery.CompositionKey(group, hash) - return key, rev - } - answer := func(group, head string, rev uint64) { - t.Helper() - have, err := delivery.EncodeHaveNode(delivery.HaveNode{Record: &delivery.NodeRecord{Node: "g14"}, - Candidates: map[string]delivery.CandidateAnswer{group: {Head: head, Revision: rev, Verdict: delivery.CandidateSame, Same: []string{"web"}}}}) - if err != nil { - t.Fatal(err) - } - b.putJSON(t, delivery.HaveBucket, "g14.node", have) - } - seedHead, lateHead := strings.Repeat("5", 40), strings.Repeat("6", 40) - seed, seedRev := placed("feat/seed", 20, seedHead) - waiting := b.verdict(t, seed, func(v delivery.CompositionVerdict) bool { return v.State == delivery.CompositionWaits }) - if !strings.Contains(waiting.Words, "g14") { - t.Errorf("the seed's composition waits saying %q, want g14 named", waiting.Words) - } - answer("feat/seed", seedHead, seedRev) - if v := b.verdict(t, seed, func(v delivery.CompositionVerdict) bool { return v.State == delivery.CompositionPass }); !slices.Contains(v.Machines, "g14") { - t.Errorf("the seed's composition = %+v, want g14 composed", v) - } - late, lateRev := placed("feat/late", 21, lateHead) - failed := b.verdict(t, late, func(v delivery.CompositionVerdict) bool { return v.State == delivery.CompositionFail }) - if failed.Machine != "g14" || !strings.Contains(failed.Words, delivery.CompositionUnanswered) { - t.Errorf("a node silent past its bound: %+v", failed) - } - answer("feat/late", lateHead, lateRev) - b.verdict(t, late, func(v delivery.CompositionVerdict) bool { return v.State == delivery.CompositionPass }) } // get waits for a key of one of the bed's buckets and answers its value. diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go index 393f5dd..40a9ef2 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go @@ -13,6 +13,7 @@ import ( "log" "os" "os/signal" + "slices" "sync" "syscall" "time" @@ -58,8 +59,6 @@ var readBuckets = []string{ delivery.CandidatesBucket, delivery.ManifestsBucket, delivery.ModulesBucket, - delivery.PlacementBucket, - delivery.PlacementCandidatesBucket, delivery.PresenceBucket, delivery.HaveBucket, delivery.StepsBucket(goBuildModule), @@ -69,14 +68,24 @@ var readBuckets = []string{ // their steps in its steps state (novox/hq ADR 0482 clause 4). const goBuildModule = "mesh-build-go" -// ownBuckets are the assigner's own states, as its manifest declares them. +// ownBuckets are the assigner's own states, as its manifest declares them: the seat mesh-placement's three among them +// (ADR 0567 §1), named after the assigner as every module state is. var ownBuckets = []string{delivery.AssignerAsksBucket, delivery.AssignerCompositionsBucket, delivery.AssignerDifferencesBucket, - delivery.AssignerPutsBucket, delivery.AssignerWantsBucket} + delivery.AssignerPutsBucket, delivery.AssignerWantsBucket, delivery.PlacementBucket, delivery.PlacementProposalsBucket, + delivery.PlacementCandidatesBucket} + +// seatBuckets are the seat's states, new with it: until the controller has made them from the manifest and the bus grants +// them, the assigner starts without them and binds each once the bus says it was created (issue 1790's kind). +var seatBuckets = []string{delivery.PlacementBucket, delivery.PlacementProposalsBucket, delivery.PlacementCandidatesBucket} // keptStates is what the assigner keeps and who may read it (ADR 0495 §2-§3): its wants by the watchman and the view // (and the node agent from release 6), its differences by the view, its compositions by the checker, its asks by the view -// and the teller, its puts by whoever follows one (ADR 0546). +// and the teller, its puts by whoever follows one (ADR 0546), and the seat mesh-placement's placement, proposals and +// candidates by the node agents, the grants part, mesh-config and the view (ADR 0567 §9). var keptStates = []announce.State{ + {Name: delivery.PlacementState, Readable: true}, + {Name: delivery.PlacementProposalsState, Readable: true}, + {Name: delivery.PlacementCandidatesState, Readable: true}, {Name: "asks", Readable: true}, {Name: "compositions", Readable: true}, {Name: "differences", Readable: true}, @@ -99,10 +108,10 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any logf("the bucket %s cannot be opened (%v): no put is taken further until the assigner starts again", name, err) continue } - if name == delivery.PlacementBucket && err != nil { - // The indexer's placement state comes with the second milestone (ADR 0549 §2): without it the controller's - // assignments alone name where modules run, as before any placement was read (ADR 0549 §5). - logf("the bucket %s cannot be opened (%v): the controller's assignments alone are read until the assigner starts again", name, err) + if slices.Contains(seatBuckets, name) && err != nil { + // The seat's states come in the same delivery as this assigner (ADR 0567 §1): without placement the controller's + // assignments alone name where modules run, as before any placement was written (ADR 0549 §5). + logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err) continue } if err != nil { @@ -159,6 +168,7 @@ var watched = []string{ delivery.CandidatesBucket, delivery.ModulesBucket, delivery.PlacementBucket, + delivery.PlacementProposalsBucket, delivery.PlacementCandidatesBucket, delivery.PresenceBucket, delivery.HaveBucket, @@ -256,7 +266,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, } started := 0 for _, name := range watched { - if kvs[name] == nil && (name == delivery.PlacementBucket || name == delivery.PlacementCandidatesBucket) { + if kvs[name] == nil && slices.Contains(seatBuckets, name) { continue // not on the bus at this start (open said so) } started++ @@ -271,22 +281,19 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, if err := watch(delivery.ManifestsBucket, jetstream.MetaOnly()); err != nil { return err } - // The indexer's placement state arrives with the same delivery as this assigner (ADR 0549 §2): when it is not on the - // bus at this start it is bound once the bus says it was created, and watched from then on, with no restart (issue - // 1790's kind). Until then the controller's assignments alone name where modules run (ADR 0549 §5). + // The seat's states arrive with the same delivery as this assigner (ADR 0567 §1): one not on the bus at this start is + // bound once the bus says it was created, and watched from then on, with no restart (issue 1790's kind). Until then the + // controller's assignments alone name where modules run (ADR 0549 §5). type appeared struct { name string kv jetstream.KeyValue } - placementAppeared := make(chan appeared, 2) - if kvs[delivery.PlacementBucket] == nil && js != nil { - process.AwaitBucket(ctx, js, nc, delivery.PlacementBucket, "the controller's assignments alone are read", - logger.Printf).Ready(func(kv jetstream.KeyValue) { placementAppeared <- appeared{delivery.PlacementBucket, kv} }) - } - // So are the placement candidates (ADR 0559 §3), with the same delivery as the indexer that writes them. - if kvs[delivery.PlacementCandidatesBucket] == nil && js != nil { - process.AwaitBucket(ctx, js, nc, delivery.PlacementCandidatesBucket, "no change's placement is composed", - logger.Printf).Ready(func(kv jetstream.KeyValue) { placementAppeared <- appeared{delivery.PlacementCandidatesBucket, kv} }) + placementAppeared := make(chan appeared, len(seatBuckets)) + for _, name := range seatBuckets { + if kvs[name] == nil && js != nil { + process.AwaitBucket(ctx, js, nc, name, "the seat mesh-placement waits for it", + logger.Printf).Ready(func(kv jetstream.KeyValue) { placementAppeared <- appeared{name, kv} }) + } } takePlacement := func(a appeared) error { kvs[a.name] = a.kv diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go index e87746a..c1f7202 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go @@ -482,9 +482,6 @@ func (p *passer) compositions(ctx context.Context) { } else { v = assign.Compose(*mesh, ch, p.sdk, time.Since(began)) } - if ch.Placement != nil && v.State != delivery.CompositionWaits { - v = p.answers(*mesh, ch, v) - } } if before, err := delivery.DecodeCompositionVerdict(written[out]); err == nil { before.Took, before.V = v.Took, 0 @@ -496,40 +493,6 @@ func (p *passer) compositions(ctx context.Context) { } } -// answers judges every placed node's answer to the change's placement into v (ADR 0559 §5). While a node has not answered -// within the bound the verdict waits, and the pass at the bound's end is asked (wakeAt), so a silent node is failed then, -// named, and a later answer of the same head is judged on its own have's change. -func (p *passer) answers(mesh assign.Mesh, ch assign.Change, v delivery.CompositionVerdict) delivery.CompositionVerdict { - trunkPlaced := map[string]delivery.Placement{} - for key, value := range p.w.read(delivery.PlacementBucket) { - node, err := delivery.ParsePlacementKey(key) - if err != nil { - continue - } - if pl, err := delivery.DecodePlacement(value); err == nil && pl.Node == node { - trunkPlaced[node] = pl - } - } - fps := map[string]string{} - for name, m := range p.w.trunk() { - if m.FP2 != "" { - fps[name] = m.FP2 - } else if m.FP != "" { - fps[name] = m.FP - } - } - intended, notes := assign.Intended(mesh.Input, trunkPlaced, ch.Placement, fps) - a := assign.JudgeAnswers(ch.Placement, assign.PlacedNodes(trunkPlaced, ch.Placement), p.w.candidateAnswers(ch.Group), intended, p.w.online(), p.clock()) - v = assign.WithAnswers(v, a) - if v.State != delivery.CompositionWaits { - v.Warnings = append(v.Warnings, notes...) - } - if a.Waits && p.wakeAt != nil { - p.wakeAt(a.Until) - } - return v -} - // put writes one key of the assigner's own bucket; a write that fails is said and made again at the next pass. func (p *passer) put(ctx context.Context, bucket, key string, encode func() ([]byte, error)) { value, err := encode() diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go index 1adc490..e584c94 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go @@ -853,7 +853,7 @@ func aPairOneSourceNamesAloneGetsNoWantAndOneCondition(t *testing.T) { p.sayPlacement(p.w.placementDiffers()) if !slices.Equal(got["g14"], []string{"a"}) || !frozen["g14.b"] || !frozen["g14.c"] || frozen["g14.a"] || len(said) != 2 || said[0].State != asks.StateRaised || !strings.Contains(said[0].Explanation, "the controller's assignments names b on g14") || - !strings.Contains(said[1].Explanation, "the catalogue's placement names c on g14") { + !strings.Contains(said[1].Explanation, "the seat mesh-placement's placement names c on g14") { t.Fatalf("one source alone: assigned %v, frozen %v, said %+v", got, frozen, said) } place("g14", "a", "b", "c") diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go index 6ebbe6a..e18c9c4 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go @@ -192,7 +192,7 @@ func (w *world) manifest(ctx context.Context, get getter, digest string) parsed return p } -// assigned is each node's modules both the controller's assignments and the indexer's placement name, in name order +// assigned is each node's modules both the controller's assignments and the seat mesh-placement's placement name, in name order // (ADR 0549 §5; the assignments alone before any placement was read), with every open assignment by node and module. func (w *world) assigned() (map[string][]string, map[string]assignment) { byAssignment, all := w.assignedByController() @@ -201,7 +201,7 @@ func (w *world) assigned() (map[string][]string, map[string]assignment) { return both, all } -// placementDiffers is every pair the controller's assignments and the indexer's placement do not both name. +// placementDiffers is every pair the controller's assignments and the seat's placement do not both name. func (w *world) placementDiffers() []assign.Pair { byAssignment, _ := w.assignedByController() placed, seeded := w.placed() @@ -209,7 +209,7 @@ func (w *world) placementDiffers() []assign.Pair { return differ } -// placed is each node's modules in the indexer's placement (ADR 0549 §2), and whether any placement key was read: a key +// placed is each node's modules in the seat's placement (ADR 0567 §1), and whether any placement key was read: a key // that does not read places nothing on its node and is said by the indexer's own read tool. func (w *world) placed() (map[string][]string, bool) { out := map[string][]string{} @@ -228,7 +228,7 @@ func (w *world) placed() (map[string][]string, bool) { return out, len(values) > 0 } -// placedNodes is every node the indexer's placement names (ADR 0549 §2): those compose their own modules (ADR 0547 §2), +// placedNodes is every node the seat's placement names (ADR 0567 §1): those compose their own modules (ADR 0547 §2), // so their wants name the build alone and the seventh rule does not hold them. func (w *world) placedNodes() map[string]bool { placed, _ := w.placed() @@ -302,7 +302,7 @@ func (w *world) mesh(ctx context.Context, manifests getter) assign.Mesh { return m } -// worldReader is what the assigner watched as frombus reads the bus: the indexer's placement and modules, each node's +// worldReader is what the assigner watched as frombus reads the bus: the seat's placement, the indexer's modules, each node's // record in its own have key, the manifests by digest; the check reads no config value, own secret or credential (the // composition it asks resolves, and stands in for what it does not carry). type worldReader struct { @@ -431,77 +431,9 @@ func (w *world) change(ctx context.Context, manifests getter, c delivery.Candida sort.Strings(ch.Twice[name]) } } - cp, waits := w.placementOf(ctx, manifests, c) - if waits != "" { - return ch, waits - } - ch.Placement = cp return ch, "" } -// placementOf is the change's own placement (ADR 0559 §5): the indexer's placement candidates of the group's catalogue -// pull request, with the input the nodes compose from with them laid over the trunk's placement. None when the group has -// no catalogue pull request or it changes no placement file. It waits while a key names another head than the group's. -func (w *world) placementOf(ctx context.Context, manifests getter, c delivery.Candidate) (*assign.ChangePlacement, string) { - var pull *delivery.CandidatePull - for i := range c.Pulls { - if delivery.SameRepository(c.Pulls[i].Repository, delivery.CatalogueRepository) { - pull = &c.Pulls[i] - } - } - if pull == nil { - return nil, "" - } - cp := &assign.ChangePlacement{Placements: map[string]*delivery.Placement{}, Revisions: map[string]uint64{}, Head: pull.Head.Head} - for key, value := range w.read(delivery.PlacementCandidatesBucket) { - group, node, err := delivery.ParsePlacementCandidateKey(key) - if err != nil || group != c.Group { - continue - } - pc, err := delivery.DecodePlacementCandidate(value) - if err != nil || pc.Pull.Number != pull.Number || !delivery.SameRepository(pc.Pull.Repository, pull.Repository) { - continue - } - if pc.Pull.Head != pull.Head.Head { - return nil, fmt.Sprintf("the indexer holds the placement candidates of %s#%d at %s, not yet at its head %s", pull.Repository, pull.Number, pc.Pull.Head, pull.Head.Head) - } - meta := w.metaOf(delivery.PlacementCandidatesBucket, key) - cp.Placements[node], cp.Revisions[node] = pc.Placement, meta.revision - if meta.at.After(cp.Written) { - cp.Written = meta.at - } - } - if len(cp.Placements) == 0 { - return nil, "" - } - in, err := frombus.Input(ctx, worldReader{w: w, manifests: manifests}, frombus.Mesh{}, frombus.Scope{Every: true, Placements: cp.Placements}) - if err != nil { - cp.Unread = err.Error() - } else { - cp.Input = &in - } - return cp, "" -} - -// candidateAnswers is, by node, each node's answer to the group's placement candidates in its `.node` have. -func (w *world) candidateAnswers(group string) map[string]*delivery.CandidateAnswer { - out := map[string]*delivery.CandidateAnswer{} - for key, value := range w.read(delivery.HaveBucket) { - node, _, isNode, err := delivery.ParseHaveKey(key) - if err != nil || !isNode { - continue - } - h, err := delivery.DecodeHaveNode(value) - if err != nil { - continue - } - if a, ok := h.Candidates[group]; ok { - out[node] = &a - } - } - return out -} - // online is, by node, whether the watchman holds the node online. func (w *world) online() map[string]bool { out := map[string]bool{} diff --git a/modules/mesh-delivery-assigner/go.mod b/modules/mesh-delivery-assigner/go.mod index 8da7a7d..8a3569a 100644 --- a/modules/mesh-delivery-assigner/go.mod +++ b/modules/mesh-delivery-assigner/go.mod @@ -20,3 +20,5 @@ require ( golang.org/x/sys v0.48.0 // indirect golang.org/x/time v0.15.0 // indirect ) + +replace git.novox.be/novox/mesh-sdk/go => /home/jochen/projects/novox/mesh-sdk/go diff --git a/modules/mesh-delivery-assigner/go.sum b/modules/mesh-delivery-assigner/go.sum index f6eae7d..98dc72c 100644 --- a/modules/mesh-delivery-assigner/go.sum +++ b/modules/mesh-delivery-assigner/go.sum @@ -1,7 +1,3 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027193820-8ad9372b1952 h1:TmveT3Bija643RGWkilvkzP/h8UWlVvHFZZtQfJK5oU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027193820-8ad9372b1952/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027201213-7a8e9f970648 h1:XoMsgg9kSgjQWa86NZM8h8OpxJ2kovxcMS2C0i6WSVU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027201213-7a8e9f970648/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-delivery-assigner/internal/assign/candidates.go b/modules/mesh-delivery-assigner/internal/assign/candidates.go deleted file mode 100644 index 67b7739..0000000 --- a/modules/mesh-delivery-assigner/internal/assign/candidates.go +++ /dev/null @@ -1,242 +0,0 @@ -package assign - -import ( - "fmt" - "reflect" - "sort" - "strings" - "time" - - "git.novox.be/novox/mesh-sdk/go/compose" - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// A change's own placement (novox/hq ADR 0559 §5, issue 1126): a catalogue pull request that changes placement files has -// the indexer's placement candidates, one key `.` for each node whose file at its head differs from the -// trunk's. The check composes the change with those placements laid over the trunk's (frombus, Scope.Placements), counts -// every such node among the machines it touches, and then reads each placed node's own answer in its `.node` -// have: it fails on what a node composes differently that the change does not intend, on a refusal, and on a node -// placed at the head or on the trunk that has not answered at the head within AnswerBound of the keys being written. - -// AnswerBound is how long the check waits for every placed node's answer after the candidate keys were written (ADR 0559 -// §5): a node asleep or away is named past it, never passed. A variable so a test on the bus can shorten it. -var AnswerBound = 10 * time.Minute - -// ChangePlacement is the change's own placement, read from the indexer's placement candidates of its group. -type ChangePlacement struct { - // Placements is by node the candidate placement; nil when the head removes the node's file. - Placements map[string]*delivery.Placement - // Revisions is by node the revision of its candidate key: the answer to it names that revision. - Revisions map[string]uint64 - // Head is the catalogue pull request's head the candidates were read at, and Written when the newest key was written. - Head string - Written time.Time - // Input is the trunk's composition input with Placements laid over it (frombus.Input, Scope.Placements), its catalogue - // the trunk's; nil with Unread saying why when it could not be read. - Input *compose.Input - Unread string -} - -// Nodes is every node the change's placement names, sorted. -func (p *ChangePlacement) Nodes() []string { - if p == nil { - return nil - } - out := make([]string, 0, len(p.Placements)) - for node := range p.Placements { - out = append(out, node) - } - sort.Strings(out) - return out -} - -// Intended is, by node the trunk places, the modules whose composition the change's placement changes: those it adds, -// removes or whose placed entry differs, and those whose composition (with the trunk's catalogue, the same stand-ins -// either side) differs between the trunk's input and the change's (delivery.CompareComposed), the node's own named -// "node". A node the trunk does not place yet has none (ADR 0559 §5: at the seed every node must answer the same). fps -// gives the build each module is composed at, the trunk's. A node whose compositions cannot be made either side keeps -// the modules its placement entries name, and is said in notes. -func Intended(trunk *compose.Input, trunkPlaced map[string]delivery.Placement, cp *ChangePlacement, fps map[string]string) (intended map[string]map[string]bool, notes []string) { - intended = map[string]map[string]bool{} - if cp == nil { - return intended, nil - } - for _, node := range cp.Nodes() { - before, placed := trunkPlaced[node] - if !placed { - continue - } - set := map[string]bool{} - after := cp.Placements[node] - var afterModules []delivery.PlacedModule - if after != nil { - afterModules = after.Modules - } - entries := map[string]delivery.PlacedModule{} - for _, m := range before.Modules { - entries[m.Module] = m - } - seen := map[string]bool{} - for _, m := range afterModules { - seen[m.Module] = true - if e, ok := entries[m.Module]; !ok || !reflect.DeepEqual(e, m) { - set[m.Module] = true - } - } - for name := range entries { - if !seen[name] { - set[name] = true - } - } - if trunk != nil && cp.Input != nil { - was, _, _, errB := compose.CompositionsShadow(*trunk, node, fps) - now, _, _, errA := compose.CompositionsShadow(*cp.Input, node, fps) - if errA == nil && errB == nil { - running := make([]delivery.Composed, 0, len(was)) - for _, c := range was { - running = append(running, c) - } - cmp := delivery.CompareComposed(now, running) - for _, d := range cmp.Differs { - set[d.Module] = true - } - for _, d := range cmp.Added { - set[d.Module] = true - } - for _, m := range cmp.Removed { - set[m] = true - } - } else { - notes = append(notes, fmt.Sprintf("what the change intends on %s is read from its placement file alone: its compositions cannot be made (trunk: %v; change: %v)", node, errB, errA)) - } - } - intended[node] = set - } - return intended, notes -} - -// Answered is what the placed nodes answered: the failures to name, the nodes that have not answered, and the notes. -type Answered struct { - Fails []string - Silent []string - Machine string - Waits bool - Until time.Time - Warnings []string -} - -// JudgeAnswers reads each placed node's answer to the change's placement (ADR 0559 §5). placed is every node placed at -// the head or on the trunk; answers is by node its HaveNode.Candidates entry for the group, nil when it has none; online -// says whether the watchman holds the node online. An answer counts when it names the candidates' head and, for a node -// with a key of its own, that key's revision. It fails on a refusal, on a module that differs, is added or is removed -// that intended does not name, and, from Written plus AnswerBound on, on every node with no answer that counts, named, -// asleep or not; before that the check waits for them. -func JudgeAnswers(cp *ChangePlacement, placed []string, answers map[string]*delivery.CandidateAnswer, intended map[string]map[string]bool, online map[string]bool, now time.Time) Answered { - var out Answered - if cp == nil { - return out - } - failAt := func(node, words string) { - if out.Machine == "" { - out.Machine = node - } - out.Fails = append(out.Fails, words) - } - for _, node := range placed { - a := answers[node] - rev, keyed := cp.Revisions[node] - if a == nil || a.Head != cp.Head || (keyed && a.Revision != rev) { - out.Silent = append(out.Silent, node) - continue - } - if a.Refused != "" { - failAt(node, fmt.Sprintf("%s cannot compose the candidate placement: %s", node, a.Refused)) - continue - } - for _, r := range a.Refusals { - failAt(node, fmt.Sprintf("%s refuses the candidate placement for %s: %s", node, r.Module, r.Why)) - } - want := intended[node] - for _, d := range a.Differs { - if !want[d.Module] { - failAt(node, fmt.Sprintf("%s composes %s differently with the candidate placement, which the change does not intend: %s", node, d.Module, d.Why)) - } - } - for _, d := range a.Added { - if !want[d.Module] { - failAt(node, fmt.Sprintf("%s would run %s with the candidate placement, which the change does not intend", node, d.Module)) - } - } - for _, m := range a.Removed { - if !want[m] { - failAt(node, fmt.Sprintf("%s would stop running %s with the candidate placement, which the change does not intend", node, m)) - } - } - } - if len(out.Silent) == 0 { - return out - } - out.Until = cp.Written.Add(AnswerBound) - named := make([]string, len(out.Silent)) - for i, node := range out.Silent { - named[i] = node - if !online[node] { - named[i] = node + " (offline)" - } - } - if now.Before(out.Until) { - out.Waits = true - out.Warnings = append(out.Warnings, fmt.Sprintf("waits for the answers of %s to the candidate placement at %s, until %s", - strings.Join(named, ", "), short(cp.Head), out.Until.UTC().Format(time.RFC3339))) - return out - } - for _, node := range out.Silent { - if out.Machine == "" { - out.Machine = node - } - } - out.Fails = append(out.Fails, fmt.Sprintf("%s %s (%s after the candidate keys were written at %s)", - strings.Join(named, ", "), delivery.CompositionUnanswered, AnswerBound, cp.Written.UTC().Format(time.RFC3339))) - return out -} - -// PlacedNodes is every node placed at the head or on the trunk: the trunk's placed nodes with the change's placement laid -// over them (a node whose file the head removes stays, as the trunk places it), sorted. -func PlacedNodes(trunkPlaced map[string]delivery.Placement, cp *ChangePlacement) []string { - set := map[string]bool{} - for node := range trunkPlaced { - set[node] = true - } - if cp != nil { - for node, p := range cp.Placements { - if p != nil { - set[node] = true - } - } - } - out := make([]string, 0, len(set)) - for node := range set { - out = append(out, node) - } - sort.Strings(out) - return out -} - -// WithAnswers is v with the placed nodes' answers judged in: a fail adds its failures (the words of nodes not heard end -// with delivery.CompositionUnanswered, so the checker takes a later composition of the same heads in when they answer), a -// wait makes a passing verdict wait (a composition that waits composed no machine), and a failing one stands. -func WithAnswers(v delivery.CompositionVerdict, a Answered) delivery.CompositionVerdict { - if len(a.Fails) > 0 { - if v.State == delivery.CompositionFail { - v.Words += "; " + strings.Join(a.Fails, "; ") - } else { - v.State, v.Words, v.Machine = delivery.CompositionFail, strings.Join(a.Fails, "; "), a.Machine - } - return v - } - if a.Waits && v.State == delivery.CompositionPass { - return delivery.CompositionVerdict{Group: v.Group, Heads: v.Heads, State: delivery.CompositionWaits, SDK: v.SDK, Took: v.Took, - Words: strings.Join(a.Warnings, "; ")} - } - return v -} diff --git a/modules/mesh-delivery-assigner/internal/assign/compose.go b/modules/mesh-delivery-assigner/internal/assign/compose.go index 6bde172..65c1481 100644 --- a/modules/mesh-delivery-assigner/internal/assign/compose.go +++ b/modules/mesh-delivery-assigner/internal/assign/compose.go @@ -59,15 +59,13 @@ type Change struct { // Members the head of each member by repository: what a composer at the group's mesh-sdk reads (issues 1622, 1623). Digests map[string]string Members map[string]string - // Placement is the change's own placement, read from the indexer's placement candidates of the group (ADR 0559 §5); - // nil when the group changes no placement file. - Placement *ChangePlacement } // Touched is the machines a change touches (ADR 0446 clause 3): those running a module the change moves, adds or // removes, those running a consumer of a provision such a module provides (before or after), and those running a // provider of a provision such a module requires after the change. A module the change adds that is assigned nowhere -// touches no machine. Every node whose placement the change changes is touched too (ADR 0559 §5, changing ADR 0446 §3). +// touches no machine. A change of placement is no change of a repository (ADR 0567 §4): the seat +// mesh-placement judges it. func Touched(m Mesh, c Change) []string { moved := map[string]bool{} for name := range c.Manifests { @@ -101,11 +99,6 @@ func Touched(m Mesh, c Change) []string { } } } - for _, node := range c.Placement.Nodes() { - if !slices.Contains(out, node) { - out = append(out, node) - } - } sort.Strings(out) return out } @@ -178,25 +171,6 @@ func Compose(m Mesh, c Change, sdk string, took time.Duration) delivery.Composit delete(changed, name) } after := input(changed, m.Assigned) - if c.Placement != nil { - // A change of placement is composed over the input the nodes compose from, its placement laid over the trunk's - // (frombus, ADR 0559 §5): the trunk as the nodes compose it where the catalogue places them, the change's - // manifests over the trunk's catalogue. - if m.Input != nil { - trunk = *m.Input - trunk.Catalogue = withOverlay(m.Input.Catalogue, m.Manifests) - } - if c.Placement.Input == nil { - failAt("", "the change's placement cannot be composed: the input with it cannot be read from the bus: "+c.Placement.Unread) - } else { - after = *c.Placement.Input - cat := withOverlay(c.Placement.Input.Catalogue, changed) - for _, name := range c.Removed { - delete(cat, name) - } - after.Catalogue = cat - } - } for _, node := range machines { _, before := compose.Resolve(trunk, node) _, now := compose.Resolve(after, node) diff --git a/modules/mesh-delivery-assigner/internal/assign/compose_test.go b/modules/mesh-delivery-assigner/internal/assign/compose_test.go index 4054e91..96c46ab 100644 --- a/modules/mesh-delivery-assigner/internal/assign/compose_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/compose_test.go @@ -4,7 +4,6 @@ import ( "slices" "strings" "testing" - "time" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" @@ -58,79 +57,6 @@ func TestAMachineTheChangeBreaksFails(t *testing.T) { if v.State != delivery.CompositionFail || v.Machine != "ace" || !strings.Contains(v.Words, "database") { t.Fatalf("composition = %+v, want a fail on ace naming the provision nothing provides", v) } - - // ADR 0559 §5 (issue 1126): a change of placement composes its placement laid over the trunk's input and fails naming - // the node it breaks: a module no manifest names placed on g14; ace, - // is no longer composed with it; a placement whose input cannot be read fails; a placement that breaks nothing passes, g14 touched. - m := meshOf() - placed := func(assigned map[string][]string) *ChangePlacement { - in := input(m.Manifests, assigned) - return &ChangePlacement{Placements: map[string]*delivery.Placement{"g14": {Node: "g14"}}, Input: &in} - } - for _, row := range []struct { - name string - cp *ChangePlacement - state string - machine string - words string - }{ - {"a placement that breaks g14", placed(map[string][]string{"ace": {"mail", "store"}, "g14": {"web", "ghost"}}), delivery.CompositionFail, "g14", "ghost"}, - {"a placement whose input is not read", &ChangePlacement{Placements: map[string]*delivery.Placement{"g14": nil}, Unread: "no record of g14"}, delivery.CompositionFail, "", "no record of g14"}, - {"a placement that breaks nothing", placed(map[string][]string{"ace": {"mail", "store"}, "g14": {"web"}}), delivery.CompositionPass, "", ""}, - } { - v := Compose(m, Change{Group: "g", Heads: "0123456789ab", Placement: row.cp}, "sdk", 0) - if v.State != row.state || v.Machine != row.machine || !strings.Contains(v.Words, row.words) || !slices.Contains(v.Machines, "g14") { - t.Errorf("%s: %+v", row.name, v) - } - } - - // ADR 0559 §5: the placed nodes' answers. ace is placed on the trunk and its candidate moves web there (intended); - // g14 is not placed on the trunk (the seed), so nothing is intended there; novox is placed and unchanged. - at := time.Date(2026, 10, 27, 2, 0, 0, 0, time.UTC) - cp := &ChangePlacement{Head: "h2", Written: at, Revisions: map[string]uint64{"ace": 7, "g14": 8}, - Placements: map[string]*delivery.Placement{"ace": {Node: "ace"}, "g14": {Node: "g14"}}} - intended := map[string]map[string]bool{"ace": {"web": true}} - same := func(rev uint64) *delivery.CandidateAnswer { - return &delivery.CandidateAnswer{Head: "h2", Revision: rev, Verdict: delivery.CandidateSame, Same: []string{"mail"}} - } - placedNodes := []string{"ace", "g14", "novox"} - for _, row := range []struct { - name string - answers map[string]*delivery.CandidateAnswer - now time.Time - state string - words string - }{ - {"all the same", map[string]*delivery.CandidateAnswer{"ace": same(7), "g14": same(8), "novox": same(0)}, at, delivery.CompositionPass, ""}, - {"a module added where intended", map[string]*delivery.CandidateAnswer{"ace": {Head: "h2", Revision: 7, Verdict: delivery.CandidateDiffers, - Added: []delivery.ModuleAdded{{Module: "web", Unissued: []string{"its own secret"}}}}, "g14": same(8), "novox": same(0)}, at, delivery.CompositionPass, ""}, - {"a module differing where not intended", map[string]*delivery.CandidateAnswer{"ace": {Head: "h2", Revision: 7, Verdict: delivery.CandidateDiffers, - Differs: []delivery.ModuleDiffers{{Module: "mail", Why: "the port differs"}}}, "g14": same(8), "novox": same(0)}, at, delivery.CompositionFail, "ace composes mail differently"}, - {"a node's first placement file with a module differing", map[string]*delivery.CandidateAnswer{"ace": same(7), "g14": {Head: "h2", Revision: 8, Verdict: delivery.CandidateDiffers, - Differs: []delivery.ModuleDiffers{{Module: "web", Why: "a binding differs"}}}, "novox": same(0)}, at, delivery.CompositionFail, "g14 composes web differently"}, - {"a refusal", map[string]*delivery.CandidateAnswer{"ace": same(7), "g14": {Head: "h2", Revision: 8, Verdict: delivery.CandidateRefused, - Refusals: []delivery.ModuleRefused{{Module: "web", Why: "no own secret"}}}, "novox": same(0)}, at, delivery.CompositionFail, "g14 refuses"}, - {"a node not answering, within its bound", map[string]*delivery.CandidateAnswer{"ace": same(7), "novox": same(0)}, at.Add(AnswerBound - time.Second), delivery.CompositionWaits, "g14 (offline)"}, - {"an answer of an earlier revision, past its bound", map[string]*delivery.CandidateAnswer{"ace": same(6), "g14": same(8), "novox": same(0)}, at.Add(AnswerBound), delivery.CompositionFail, "ace " + delivery.CompositionUnanswered}, - {"its later answer", map[string]*delivery.CandidateAnswer{"ace": same(7), "g14": same(8), "novox": same(0)}, at.Add(time.Hour), delivery.CompositionPass, ""}, - } { - pass := delivery.CompositionVerdict{Group: "g", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "sdk", Machines: []string{"ace", "g14"}} - v := WithAnswers(pass, JudgeAnswers(cp, placedNodes, row.answers, intended, map[string]bool{"ace": true, "novox": true}, row.now)) - if v.State != row.state || !strings.Contains(v.Words, row.words) { - t.Errorf("%s: %+v", row.name, v) - } - if _, err := delivery.EncodeCompositionVerdict(v); err != nil { - t.Errorf("%s: the verdict cannot be written: %v", row.name, err) - } - } - trunk := map[string]delivery.Placement{"ace": {Node: "ace", Modules: []delivery.PlacedModule{{Module: "mail"}}}, "novox": {Node: "novox"}} - if got := PlacedNodes(trunk, &ChangePlacement{Placements: map[string]*delivery.Placement{"novox": nil, "g14": {Node: "g14"}}}); !slices.Equal(got, []string{"ace", "g14", "novox"}) { - t.Errorf("placed at the head or on the trunk = %v", got) - } - got, _ := Intended(nil, trunk, &ChangePlacement{Placements: map[string]*delivery.Placement{"ace": {Node: "ace", Modules: []delivery.PlacedModule{{Module: "mail", Ports: map[string]int{"25": 2525}}, {Module: "web"}}}, "g14": {Node: "g14", Modules: []delivery.PlacedModule{{Module: "web"}}}}}, nil) - if !got["ace"]["mail"] || !got["ace"]["web"] || len(got["g14"]) != 0 { - t.Errorf("intended = %v, want ace's mail and web, nothing on g14 the trunk does not place", got) - } } // 1297/B3c: what was already broken on the trunk is said and fails nothing. @@ -183,10 +109,6 @@ func TestTouched(t *testing.T) { if got := Touched(m, Change{Manifests: map[string]catalogue.Manifest{"store": m.Manifests["store"]}}); !slices.Equal(got, []string{"ace", "g14"}) { t.Errorf("touched by store with a consumer on g14 = %v", got) } - // ADR 0559 §5: every node whose placement the change changes is touched, its file removed or not. - if got := Touched(m, Change{Placement: &ChangePlacement{Placements: map[string]*delivery.Placement{"novox": nil, "shanks": {Node: "shanks"}}}}); !slices.Equal(got, []string{"novox", "shanks"}) { - t.Errorf("touched by a placement = %v", got) - } c := Change{Group: "g", Heads: "0123456789ab", Manifests: map[string]catalogue.Manifest{"new": {Module: "new", Version: "1"}}} if v := Compose(m, c, "sdk", 0); len(v.Machines) != 0 || !hasWarning(v, "touches no machine") || v.State != delivery.CompositionPass { t.Errorf("an added module assigned nowhere: %+v", v) diff --git a/modules/mesh-delivery-assigner/internal/assign/composer.go b/modules/mesh-delivery-assigner/internal/assign/composer.go index 03b4d60..b2dac9e 100644 --- a/modules/mesh-delivery-assigner/internal/assign/composer.go +++ b/modules/mesh-delivery-assigner/internal/assign/composer.go @@ -50,17 +50,6 @@ func HeadJob(m Mesh, c Change) (delivery.ComposeJob, error) { fromClaims = len(in.Holdings) == 0 } changeCase := composer.Case{Name: CaseChange, Manifests: change} - if c.Placement != nil { - // The change's own placement, laid over the input's in the change case (ADR 0559 §5; composer.Case's Placements): - // a node the input does not hold yet is added bare, so the composer places it. - changeCase.Placements = c.Placement.Placements - in.Nodes = slices.Clone(in.Nodes) - for _, node := range c.Placement.Nodes() { - if !slices.ContainsFunc(in.Nodes, func(n compose.Node) bool { return n.Name == node }) { - in.Nodes = append(in.Nodes, compose.Node{Name: node}) - } - } - } req := composer.Request{V: composer.Version, Input: in, Depth: composer.DepthResolve, HoldingsFromClaims: fromClaims, Machines: Touched(m, c), Cases: []composer.Case{{Name: CaseTrunk, Manifests: trunk}, changeCase}} diff --git a/modules/mesh-delivery-assigner/internal/assign/composer_test.go b/modules/mesh-delivery-assigner/internal/assign/composer_test.go index a01cebd..2bc3258 100644 --- a/modules/mesh-delivery-assigner/internal/assign/composer_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/composer_test.go @@ -120,22 +120,6 @@ func TestAGroupWithAnSDKMemberAsksTheComposerAtItsHead(t *testing.T) { len(placed.Input.Holdings) != 1 || placed.HoldingsFromClaims { t.Errorf("the placed check's request = %+v, holdings from claims %v", placed.Input, placed.HoldingsFromClaims) } - - // ADR 0559 §5: a change of placement composes its own placement in the change case alone (composer.Case's Placements), - // a node the input does not hold yet added bare so the composer places it, and the node touched. - c.Placement = &ChangePlacement{Placements: map[string]*delivery.Placement{"novox": {Node: "novox"}}} - candJob, err := HeadJob(m, c) - if err != nil { - t.Fatal(err) - } - cand, err := composer.Decode(candJob.Request) - if err != nil { - t.Fatal(err) - } - if cand.Cases[0].Placements != nil || cand.Cases[1].Placements["novox"] == nil || len(cand.Input.Nodes) != 2 || cand.Input.Nodes[1].Name != "novox" || - !slices.Equal(cand.Machines, []string{"g14", "novox"}) || len(m.Input.Nodes) != 1 { - t.Errorf("the placement's request = cases %+v, nodes %+v, machines %v", cand.Cases, cand.Input.Nodes, cand.Machines) - } } // 1622/AC2, AC3: a catalogue member using a field only the SDK head reads passes with the head's composer, and the diff --git a/modules/mesh-delivery-assigner/internal/assign/placement.go b/modules/mesh-delivery-assigner/internal/assign/placement.go index 9856b12..d7eaa3c 100644 --- a/modules/mesh-delivery-assigner/internal/assign/placement.go +++ b/modules/mesh-delivery-assigner/internal/assign/placement.go @@ -9,21 +9,25 @@ import ( ) // Until ADR 0547's third milestone two sources name which module runs on which node: the controller's assignments and -// the indexer's placement, read from the catalogue (ADR 0549 §5). The assigner writes wants only for a pair both name: +// the seat mesh-placement's placement (ADR 0549 §5, ADR 0567 §8). The assigner writes wants only for a pair both name: // for a pair one source names alone it writes no want and touches none it wrote before, so nothing moves and nothing is -// removed on one source's word, and it raises a condition naming the pair until both agree. Before the first placement -// file reaches the indexer (no placement key at all) the controller's assignments are the one source, as before. +// removed on one source's word, and it raises a condition naming the pair until both agree. After an approved proposal +// the assigner keeps the controller in step itself, calling its assign, unassign, pin and unpin; a call refused or not +// yet answered leaves the pair differing, and the condition names the call (Pair.Call). Before the first placement key +// is written the controller's assignments are the one source, as before. // Pair is a module on a node that one source names and the other does not; Only says which: OnlyAssignments or // OnlyPlacement. type Pair struct { Node, Module, Only string + // Call is what became of the assigner's call of the controller's verb for the pair, in words, when it made one. + Call string } // The source that names a pair alone. const ( OnlyAssignments = "the controller's assignments" - OnlyPlacement = "the catalogue's placement" + OnlyPlacement = "the seat mesh-placement's placement" ) // Placed is, by node, the modules both sources name, in name order, and every pair one names alone, by node then @@ -79,7 +83,7 @@ func PlacementCondition(p Pair, raised bool) asks.Condition { subject := asks.Scope{Scope: "module", ID: p.Module, Machine: p.Node} if !raised { return asks.Condition{Key: key, Subject: subject, State: asks.StateCleared, - Resolved: fmt.Sprintf("the controller's assignments and the catalogue's placement agree on %s on %s.", p.Module, p.Node)} + Resolved: fmt.Sprintf("the controller's assignments and the seat's placement agree on %s on %s.", p.Module, p.Node)} } headline := fmt.Sprintf("%s on %s is placed by one source only", p.Module, p.Node) if n := len([]rune(headline)); n > asks.HeadlineLength { @@ -87,9 +91,12 @@ func PlacementCondition(p Pair, raised bool) asks.Condition { } var needs string if p.Only == OnlyAssignments { - needs = fmt.Sprintf("add %s to the catalogue's placement file of %s, or unassign it at the controller", p.Module, p.Node) + needs = fmt.Sprintf("propose placing %s on %s (mesh-placement.propose), or unassign it at the controller", p.Module, p.Node) } else { - needs = fmt.Sprintf("assign %s to %s at the controller, as the merged placement file says", p.Module, p.Node) + needs = fmt.Sprintf("assign %s to %s at the controller, as the approved placement says, or propose taking it off", p.Module, p.Node) + } + if p.Call != "" { + needs += "; the assigner's call: " + p.Call } return asks.Condition{Key: key, Severity: asks.SeverityUrgent, Subject: subject, State: asks.StateRaised, Summary: headline, Headline: headline, Needs: needs, diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index 9e5584f..07b0410 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -34,7 +34,7 @@ func TestManifest(t *testing.T) { slices.Sort(reads) want := []string{"mesh-build-go.steps", "mesh-controller.assignments", "mesh-controller.builds", "mesh-controller.composed", "mesh-controller.grants", "mesh-delivery-builder.builds", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.manifests", - "mesh-delivery-indexer.modules", "mesh-delivery-indexer.placement", "mesh-delivery-indexer.placement-candidates", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "node-engine.have"} + "mesh-delivery-indexer.modules", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "node-engine.have"} if !reflect.DeepEqual(reads, want) { t.Errorf("reads = %v, want exactly %v", reads, want) } @@ -42,7 +42,8 @@ func TestManifest(t *testing.T) { for _, s := range m.State { states[s.Name] = s.History } - if want := map[string]int{"asks": 5, "compositions": 5, "differences": 64, "puts": 20, "wants": 20}; !reflect.DeepEqual(states, want) { + if want := map[string]int{"asks": 5, "compositions": 5, "differences": 64, "puts": 20, "wants": 20, + "placement": 64, "proposals": 5, "candidates": 5}; !reflect.DeepEqual(states, want) { t.Errorf("state = %v, want %v", states, want) } if _, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 { diff --git a/modules/mesh-delivery-assigner/module.json b/modules/mesh-delivery-assigner/module.json index eb171e7..f22710b 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -12,8 +12,6 @@ "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", - "mesh-delivery-indexer.placement", - "mesh-delivery-indexer.placement-candidates", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "node-engine.have" @@ -36,6 +34,9 @@ "assigner_puts" ], "state": [ + { "name": "placement", "history": 64 }, + { "name": "proposals", "history": 5 }, + { "name": "candidates", "history": 5 }, { "name": "asks", "history": 5 }, { "name": "compositions", "history": 5 }, { "name": "differences", "history": 64 },