Files
mesh-controller/internal/inventory/holdings_test.go
T
mesh-tools af267cb557 Take the catalogue from mesh-sdk in internal/inventory and internal/link, and keep the tests that read this repository's own manifest, Dockerfile and Makefile in internal/ownmanifest (issue 1381, 5 of 6 import swaps)
The three tests of the controller's own module.json (process, claimed seat, addresses), the Dockerfile and Makefile pin test, the build-agent registry test and the manifest-in-use seat test move with their old package, as tests of mesh-sdk's exported API; a moved test is exempt from failing first.
2026-10-20 08:27:10 +00:00

177 lines
6.5 KiB
Go

//go:build integration
package inventory
import (
"context"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// A seat's holder is a row, changed as one act (novox/hq ADR 0131). What these pin is the shape of
// that row's life: it needs an assignment to point at, it is replaced rather than added to, and it
// goes when the assignment does — so a seat never points at something that is not running anywhere.
func twoBrokersOnTwoNodes(t *testing.T) (*Inventory, context.Context) {
t.Helper()
old := catalogue.Manifest{Module: "old-broker", Version: "1",
Provides: []catalogue.Offer{{Name: "mesh-bus", Scope: catalogue.ScopeMesh}},
Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}}
new := catalogue.Manifest{Module: "new-broker", Version: "1",
Provides: []catalogue.Offer{{Name: "mesh-bus", Scope: catalogue.ScopeMesh}},
Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}}
inv, ctx := aMeshWith(t, old, new)
// The holding references the seat's row, which `migrate` seeds on a real mesh.
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
for _, n := range []string{"anchor", "laptop"} {
if _, err := inv.AddNode(ctx, n); err != nil {
t.Fatal(err)
}
}
if _, err := inv.Assign(ctx, "anchor", "old-broker"); err != nil {
t.Fatal(err)
}
if _, err := inv.Assign(ctx, "laptop", "new-broker"); err != nil {
t.Fatal(err)
}
return inv, ctx
}
func TestAHandoverIsOneRowReplacedNotOneAdded(t *testing.T) {
inv, ctx := twoBrokersOnTwoNodes(t)
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "old-broker"); err != nil {
t.Fatal(err)
}
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "laptop", "new-broker"); err != nil {
t.Fatal(err)
}
held, err := inv.Holdings(ctx)
if err != nil {
t.Fatal(err)
}
if len(held) != 1 || held[0].Node != "laptop" || held[0].Module != "new-broker" || held[0].Scope != catalogue.ScopeMesh {
t.Fatalf("after a handover the seat is not held by exactly the new holder: %+v", held)
}
}
func TestASeatCannotBeHandedToSomethingNotAssigned(t *testing.T) {
inv, ctx := twoBrokersOnTwoNodes(t)
// new-broker is assigned to laptop, not anchor.
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "new-broker"); err == nil {
t.Fatal("a seat was handed to a module not assigned where it was named")
}
}
func TestUnassigningTheHolderTakesTheHoldingWithIt(t *testing.T) {
inv, ctx := twoBrokersOnTwoNodes(t)
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "laptop", "new-broker"); err != nil {
t.Fatal(err)
}
if err := inv.Unassign(ctx, "laptop", "new-broker"); err != nil {
t.Fatal(err)
}
held, err := inv.Holdings(ctx)
if err != nil {
t.Fatal(err)
}
if len(held) != 0 {
t.Fatalf("the holding outlived the assignment it pointed at: %+v", held)
}
}
func TestAMachinesMembershipIsOneRowReplacedAndGoesWithTheMachine(t *testing.T) {
inv, ctx := twoBrokersOnTwoNodes(t)
if err := inv.PutBusMembership(ctx, "anchor", "first"); err != nil {
t.Fatal(err)
}
if err := inv.PutBusMembership(ctx, "anchor", "second"); err != nil {
t.Fatal(err)
}
got, err := inv.BusMemberships(ctx)
if err != nil {
t.Fatal(err)
}
if got["anchor"] != "second" || len(got) != 1 {
t.Fatalf("a re-told membership did not replace the first: %v", got)
}
}
// A replicated seat has several holders on record (novox/hq ADR 0223): each is added beside the
// others, adding one twice changes nothing, a handover still leaves exactly one, and unassigning one
// takes only its own row.
func TestAReplicatedSeatHasSeveralHoldersOnRecord(t *testing.T) {
resolver := catalogue.Manifest{Module: "resolver", Version: "1",
Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}},
Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}}
inv, ctx := aMeshWith(t, resolver)
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
t.Fatal(err)
}
for _, n := range []string{"anchor", "home"} {
if _, err := inv.AddNode(ctx, n); err != nil {
t.Fatal(err)
}
if _, err := inv.Assign(ctx, n, "resolver"); err != nil {
t.Fatal(err)
}
}
if err := inv.HoldSeat(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "anchor", "resolver"); err != nil {
t.Fatal(err)
}
for range 2 {
if err := inv.AddSeatHolder(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil {
t.Fatal(err)
}
}
held, err := inv.Holdings(ctx)
if err != nil {
t.Fatal(err)
}
if len(held) != 2 || held[0].Node != "anchor" || held[1].Node != "home" {
t.Fatalf("the two holders are not both on record, once each: %+v", held)
}
if err := inv.Unassign(ctx, "home", "resolver"); err != nil {
t.Fatal(err)
}
if held, _ = inv.Holdings(ctx); len(held) != 1 || held[0].Node != "anchor" {
t.Fatalf("unassigning one holder took more or less than its own row: %+v", held)
}
if _, err := inv.Assign(ctx, "home", "resolver"); err != nil {
t.Fatal(err)
}
if err := inv.AddSeatHolder(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil {
t.Fatal(err)
}
if err := inv.HoldSeat(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil {
t.Fatal(err)
}
if held, _ = inv.Holdings(ctx); len(held) != 1 || held[0].Node != "home" {
t.Fatalf("a handover left other holders on record: %+v", held)
}
}
// A pass is a compare-and-set: it moves the record only while the holder it was read with is still on it.
func TestPassingASeatMovesTheRecordOnceAndOnlyFromTheHolderRead(t *testing.T) {
inv, ctx := twoBrokersOnTwoNodes(t)
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "old-broker"); err != nil {
t.Fatal(err)
}
from := catalogue.Held{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "anchor", Module: "old-broker"}
to := catalogue.Held{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "laptop", Module: "new-broker"}
passed, err := inv.PassSeat(ctx, "mesh-broker", catalogue.ScopeMesh, from, to)
if err != nil || !passed {
t.Fatalf("the seat was not passed: %v %v", passed, err)
}
held, _ := inv.Holdings(ctx)
if len(held) != 1 || held[0].Node != "laptop" || held[0].Module != "new-broker" {
t.Fatalf("the record does not name the new holder alone: %+v", held)
}
if passed, err = inv.PassSeat(ctx, "mesh-broker", catalogue.ScopeMesh, from, to); err != nil || passed {
t.Fatalf("a second pass from the same read moved the record again: %v %v", passed, err)
}
}