The controller-merges consumer (no max-deliver, one at a time) held the de-spiegel merge e25e1beb from 10-29 09:39: keepForgeMerge put an announcement with its Paths into delivery-merges (16 KiB values), got 10054, retried "not now" forever, and 167 merges queued behind it. Every five-minute hold outran the deaf bound, so core.controller.deaf flapped. Merges are mesh-delivery's: the gate merges on the Approve and the forge bridge says each merge on its stream FORGE (pull request, head, merge commit), kept a week. The controller's copy was a leftover, read only at a moved module's registration (ADR 0420 §7, ADR 0432). That read now finds the merge on FORGE; the consumer, its follow, its handler and its hold delays are removed, and every assertion removes the retired consumer from the bus. Kind fix for the watchdog: the event loop records when a message it held back is due again, and deafness counts from then, so no hold longer than the bound (on any consumer) reads as deafness. Quick fix: strip Paths from the kept announcement and add max-deliver with a dead letter. Thorough fix: remove the duplicate duty and make the deaf watchdog hold-aware. Chose thorough: the controller is being deprecated and the merges already have an owner; the hold-vs-deaf confusion is a kind, not one consumer. Research 072 piece: 03-domains (Control shrinks to placement and composition; the delivery parts own what a merge is) and milestone 3 (health signals stop sharing the controller's loop assumptions).
123 lines
4.9 KiB
Go
123 lines
4.9 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
// The move whole, against a real bus and the store (novox/hq ADR 0420 §7, issue 1025): mesh-delivery's word that it
|
|
// merged a pull request is published on the subject the controller consumes and kept in its bucket; the router's
|
|
// record of the ask is read back from the router's bucket by a direct get; and the build of a module from the
|
|
// repository that merge was made in is registered from there only when that record holds the operator's Approve of
|
|
// the merged head. Store-backed: skips without one.
|
|
func TestAModuleBuiltFromANewRepositoryIsRegisteredOnlyOnTheOperatorsApproveOnTheBus(t *testing.T) {
|
|
for _, c := range []struct {
|
|
what string
|
|
answer bool // the router holds the operator's Approve of the merged heads
|
|
moved bool
|
|
}{
|
|
{what: "the router holds the operator's Approve", answer: true, moved: true},
|
|
{what: "the router holds no record of the ask"},
|
|
} {
|
|
t.Run(c.what, func(t *testing.T) {
|
|
open := theCatalogue(t)
|
|
ctx := t.Context()
|
|
inv := open.inventory
|
|
// The module declaring the router's records, as the controller finds them.
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "operator-channel", Version: "1",
|
|
DefinesSeats: []catalogue.SeatDeclaration{{Name: broker.AsksSeat, Records: []string{"asks"}}}},
|
|
inventory.Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/operator-channel", BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
js, err := broker.Dial(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(js.Close)
|
|
if err := broker.AssertMeshStreams(js); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := js.EnsureControllerBuckets(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
api, err := jetstream.New(js.Conn())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
records, err := api.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: broker.BucketName("operator-channel", "asks")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
a := approvedMerge(t)
|
|
a.merge.Owner, a.merge.Repo, a.merge.Number = "novox", "mesh-host", 7
|
|
if c.answer {
|
|
record, _ := json.Marshal(map[string]any{"state": "closed", "ask": a.ask, "warrant": a.warrant})
|
|
if _, err := records.Put(ctx, sdkdelivery.TellerModule+"."+a.merge.AskID, record); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
server := link.ConnectNats(js, nil, noListener{})
|
|
if err := server.KeepsDeliveryMerges(keepingDeliveryMerges{js: server.JetStream, catalog: theDeliveryClaims}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The events consumer exists, from now on, before the word is published: Receive would make it too late.
|
|
if err := broker.AssertServingConsumers(js); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serving, stop := context.WithCancel(context.Background())
|
|
done := make(chan struct{})
|
|
go func() { defer close(done); _ = server.Serve(serving) }()
|
|
t.Cleanup(func() { stop(); <-done })
|
|
servingBus.Store(js)
|
|
t.Cleanup(func() { servingBus.Store(nil) })
|
|
|
|
// mesh-delivery says it merged the pull request, and the controller keeps what it said.
|
|
body, _ := json.Marshal(a.merge)
|
|
if _, err := js.Context().Publish(broker.DeliveryMergedSubjectOf(theDelivery), body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
becomes(t, 15*time.Second, "the merge being kept", func() bool {
|
|
_, kept, err := keptDeliveryMerge(ctx, "novox", "mesh-host", 7, a.merge.HeadSHA)
|
|
return err == nil && kept
|
|
})
|
|
|
|
// The forge bridge's merge of that pull request on its stream, and the build it asked for.
|
|
const commit = "c0ffee0123456789"
|
|
sayForgeMerge(ctx, t, js, "novox/mesh-host", 7, a.merge.HeadSHA, commit)
|
|
id := fmt.Sprintf("build-%d", time.Now().Add(time.Hour).UnixNano())
|
|
if err := inv.RecordBuildRequest(ctx, inventory.BuildRequest{ID: id, Repository: "novox/mesh-host", Seat: "git",
|
|
Path: "modules/sudo", Ref: "main", Commit: commit, For: "merge"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
moved := onTrunk(id, "novox/mesh-host", "git", "modules/sudo", map[string]any{"module": "sudo", "version": "2"})
|
|
moved.Commit = commit
|
|
_, _, err = takeIn(ctx, inv, moved)
|
|
src, _ := inv.SourceOf(ctx, "sudo")
|
|
if c.moved && (err != nil || src.Repository != "novox/mesh-host") {
|
|
t.Fatalf("the Approved move was not registered from its new repository: %v (built from %q)", err, src.Repository)
|
|
}
|
|
if !c.moved && (!errors.Is(err, errNotItsSource) || src.Repository != "novox/mesh-catalog") {
|
|
t.Fatalf("a move with no Approve was not refused as it is: %v (built from %q)", err, src.Repository)
|
|
}
|
|
})
|
|
}
|
|
}
|