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.
136 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|