Files
mesh-host/internal/bootstrap/bus_adoption_test.go
T
jschoubben 8c0bb9d453
mesh/repo-check head 8c0bb9d4531d merged into trunk 4065b45ae84f beside mesh-catalog#453@e0bf87c2 — pass: its merge-check.sh passed
mesh/merge-gate head 8c0bb9d4531d merged into trunk 4065b45ae84f — pass: group fix/1873-system-state-dirs composed: every machine composes with the change …
mesh/delivery delivered
mesh/delivery-group group fix/1873-system-state-dirs delivered: every member is delivered
mesh/check failed: unit: github.com/novox/mesh-host/internal/bootstrap.TestGenesisInstallsTheCataloguesBuildAgent: builder_test.go:180: mesh-controlle…
Read the bus's release from the nats module's pinned server image, not a NATS_BASE build argument (issue 1875)
The nats module takes its server as an upstream artifact, pinned by digest
(issue 1641), and builds no image on a NATS_BASE argument any more. The test of
the bundle's bus read that argument, found none, and failed every mesh-host check
against the catalogue's main. Bootstrap's own code never read it: the bundle's
bus image comes from the bundle, so genesis was not affected. The test now
compares the bundle's image with the module's server by repository and digest.
2026-10-22 21:46:34 +02:00

143 lines
5.8 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 server is, pinned by the same digest, so the adoption changes no
// server release and is reversible as the step says (ADR 0333 §3). The module takes the server as an upstream
// artifact, its image reference pinned by digest (issue 1641); it no longer builds an image on a NATS_BASE argument,
// which this test read until issue 1875.
func TestTheBundlesBusRunsTheReleaseTheModulesImageIsBuiltOn(t *testing.T) {
var m struct {
Build struct {
Artifacts []struct{ Name, Kind, From string } `json:"artifacts"`
} `json:"build"`
}
if err := json.Unmarshal(theCataloguesModule(t, BusModule), &m); err != nil {
t.Fatal(err)
}
from := ""
for _, a := range m.Build.Artifacts {
if a.Name == "server" && a.Kind == "upstream" {
from = a.From
}
}
name, digest, pinned := strings.Cut(from, "@")
if !pinned || !strings.HasPrefix(digest, "sha256:") {
t.Fatalf("the nats module's server is no upstream image pinned by digest: %q", from)
}
repository, _, _ := strings.Cut(name, ":")
rewritten, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
if got, want := containerNamed(rewritten.Declaration, "mesh-broker-nats").Image, repository+"@"+digest; got != want {
t.Errorf("the bundle raises the bus on %s; the nats module's server is %s (%s)", got, want, from)
}
}