`seat <name> --to <node>/<module>` makes one assignment the holder of a seat in the same write that removes the previous one. The row is new (migration 0039); without one, the resolver derives the holder as it always did — the sole eligible assignment, two refused — so nothing changes for a mesh that never hands a seat over. With one, the recorded assignment holds and any other whose module could hold the seat is eligible and silent: not refused, not holding. That is what lets the next holder run beside the current one until the switch (hq design 26, design 28 task 5.3, ADR 0131). Why: the controller finds its own bus through a seat, and the day that seat was left with nobody in it — because two eligible holders could not coexist and the old one's claim was taken away — the control plane looped for two hours while every service stayed up. A handover that is never empty in between is the fix, not a workaround for it. `CanHold` is the one judgement of whether a module may hold a seat — claims it at its scope, provides what it delivers, against the store's row — shared by registration and the handover so they cannot drift apart. The holding belongs to the assignment and goes when it does, so a seat never points at nothing running. Tests: the resolver with and without a record, on the same and another machine, under a former name; the store's row replaced not added, refused for an unassigned target, removed with its assignment; CanHold's four answers and that they follow the store. Full suite green against a real NATS and store.
83 lines
2.8 KiB
Go
83 lines
2.8 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/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)
|
|
}
|
|
}
|