Files
mesh-host/internal/bootstrap/bus_adoption_test.go
T
jochen cdcfcd9380
mesh/merge-gate head cdcfcd938016 merged into trunk 130c5d98a601 — pass: the change touches no module of the mesh's graph
mesh/repo-check head cdcfcd938016 merged into trunk 130c5d98a601 — pass: its merge-check.sh passed
mesh/delivery delivered
Adopt genesis's bus through the bus's planned step, its streams snapshotted first (hq issue 590, ADR 0333 §3)
The broker step sent the nats module with a plain push, and the controller refuses a push that would
replace the bus: that is a planned step, bus upgrade, which snapshots the streams first and checks
them after (ADR 0236). Genesis stopped at step 18.

The step now snapshots every stream with the module's own snapshot program (no backup holder exists
yet to be asked), asks bus upgrade with that snapshot and why, says the one restart, and waits until
the bus runs the module's image, answers, and the controller ends the step done. The bundle's bus runs
the release the module's image is built on (2.11.17, by the same digest, where it ran 2.10.29), so the
step changes no server release and is said reversible.
2026-10-13 11:47:10 +02:00

136 lines
5.3 KiB
Go

package bootstrap
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"time"
)
// Defends novox/hq issue 590 at its broker step (ADR 0333 §3, ADR 0236): the adoption replaces the bus's
// image, and the mesh replaces its bus only as a planned step. A plain push was refused — "sending anchor
// would replace the bus … a planned step: `bus upgrade --why …`" — and genesis stopped at step 18.
func TestTheBrokerStepAdoptsTheBusThroughItsPlannedStep(t *testing.T) {
catalogue := aCatalogue(t, map[string]string{BusModule: aBusModule(theModulesLayout)})
rewritten, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
bundleImage := containerNamed(rewritten.Declaration, "mesh-broker-nats").Image
upgraded := false
var credential []byte
runtime := &asked{}
agrees := aMeshThatAgrees(nil)
runtime.answer = func(name string, args []string) (string, error) {
joined := strings.Join(args, " ")
switch {
case name == "sh" && strings.Contains(joined, "mesh-nats-snapshot"):
credential, _ = os.ReadFile(busSnapshotCredential) // as the program reads it
case strings.Contains(joined, "bus upgrade --why"):
upgraded = true
case strings.Contains(joined, "inspect --format {{.Config.Image}} mesh-broker-nats"):
if upgraded {
return theModulesImage + "\n", nil
}
return bundleImage + "\n", nil
}
return agrees(name, args)
}
_, fetch := aBusThatAdopts()
o := installing(t, catalogue)
o.CatalogSource = Source{Repository: "git://forge/mesh-catalog.git"}
o.Wait = 5 * time.Second
said, err := busStepWith(t, o, runtime, fetch)
if err != nil {
t.Fatal(err)
}
snapshot, upgrade := -1, -1
for i, c := range runtime.commands {
switch {
case strings.HasPrefix(c, "sh -c") && strings.Contains(c, "mesh-nats-snapshot"):
snapshot = i
for _, want := range []string{"127.0.0.1:5000/nats/server@sha256:" + strings.Repeat("4", 64),
"snapshot --server tls://127.0.0.1:5671 --credential - < " + busSnapshotCredential,
"> " + busSnapshotFile + ".partial && mv " + busSnapshotFile + ".partial " + busSnapshotFile} {
if !strings.Contains(c, want) {
t.Errorf("the snapshot is not taken with %q: %s", want, c)
}
}
case strings.Contains(c, "/mesh-controller bus upgrade"):
upgrade = i
if want := "bus upgrade --why " + adoptionWhy + " --reversible --snapshot-taken " + busSnapshotFile; !strings.HasSuffix(c, want) {
t.Errorf("the planned step is asked as %q; want it to end %q", c, want)
}
case strings.HasSuffix(c, "/mesh-controller push anchor"):
t.Errorf("the bus's module was sent by a plain push, which the mesh refuses for the bus: %v", runtime.commands)
}
}
if snapshot < 0 || upgrade < 0 || snapshot > upgrade {
t.Fatalf("the streams were not snapshotted before the bus's planned step (snapshot %d, upgrade %d): %v",
snapshot, upgrade, runtime.commands)
}
var cred map[string]string
if err := json.Unmarshal(credential, &cred); err != nil {
t.Fatalf("the snapshot program was given no credential: %v", err)
}
fingerprint, _ := certificateFingerprint(busCertificateFile)
if cred["user"] != "controller" || cred["password"] != "BUS-PW" || cred["fingerprint"] != fingerprint ||
cred["url"] != "tls://127.0.0.1:5671" {
t.Errorf("the snapshot's credential is %v", cred)
}
if _, err := os.Stat(busSnapshotCredential); !os.IsNotExist(err) {
t.Error("the snapshot's credential was left on the machine")
}
text := strings.Join(said, "\n")
for _, want := range []string{"snapshotted every stream", "restarting mesh-broker-nats — once",
"bus answers mesh-broker-nats", "bus step done last step 1", "adopted mesh-broker-nats"} {
if !strings.Contains(text, want) {
t.Errorf("the step did not say %q:\n%s", want, text)
}
}
}
// A planned step the controller ends failed is the broker step failing, with the controller's words.
func TestABusStepThatEndsFailedIsRefused(t *testing.T) {
aGenesisBusOnDisk(t)
runtime := &asked{answer: func(name string, args []string) (string, error) {
return "last step 1: not known → 53e51864 on anchor by installer (x): failed at 2026-10-13 11:59; snapshot s\n" +
" stream KV_x has no messages\n", nil
}}
control := controlPlane{container: "mesh-controller", run: runtime.run, timeout: time.Second}
err := waitForTheBusStep(context.Background(), Options{}, control, quietly)
if err == nil || !strings.Contains(err.Error(), "failed at") || !strings.Contains(err.Error(), "KV_x") {
t.Fatalf("a failed bus step was not refused with its words: %v", err)
}
}
// The bundle's bus runs the release the module's image is built on, pinned by the same digest, so the
// adoption changes no server release and is reversible as the step says (ADR 0333 §3).
func TestTheBundlesBusRunsTheReleaseTheModulesImageIsBuiltOn(t *testing.T) {
var m struct {
Build struct {
On []struct{ Arg, Image string } `json:"on"`
} `json:"build"`
}
if err := json.Unmarshal(theCataloguesModule(t, BusModule), &m); err != nil {
t.Fatal(err)
}
base := ""
for _, on := range m.Build.On {
if on.Arg == "NATS_BASE" {
base = on.Image
}
}
rewritten, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
if got := containerNamed(rewritten.Declaration, "mesh-broker-nats").Image; got != base {
t.Errorf("the bundle raises the bus on %s; the nats module's image is built on %s", got, base)
}
}