`assign ace postgres` made the control plane's own store unresolvable: postgres's manifest claims mesh-store, nobody was ever recorded as its holder, and with two eligible assignments and nothing on record both claimed and both were refused — novox's included. ADR 0110 says the assignment holds, by a deliberate act; ADR 0131 gave the record its force but left a seat nobody handed over held by whichever assignment happened to be alone. Before acting on an assignment the controller now writes the derived answer down: every mesh-scoped seat the store knows, resolved to exactly one holder with nothing on record, gets that holder recorded — the same record `seat <name> --to <node>/<module>` makes by hand. The next assignment able to hold the seat then stands beside the holder, eligible and silent. A seat with two derived claimants is left for a person; a seat on record is never rewritten; seats the store does not list stay held by derivation as before. And the refusal, when it still happens, names the handover that records the holder. Verified: catalogue tests against the real catalogue; the cmd suite against a store (the only failure, TestConvergingPreviewsThenChanges…, fails identically on main).
111 lines
3.6 KiB
Go
111 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// A seat nobody ever handed over is held by whichever assignment happened to be alone — and the
|
|
// day a second module able to hold it is assigned, both claimed, both were refused, and the first
|
|
// one's machine stopped resolving (novox/hq 04-ISSUES/170). The mesh now writes the derived holder
|
|
// down before it acts, so the second assignment stands beside the holder on record.
|
|
|
|
func aSeatedStore() catalogue.Manifest {
|
|
return catalogue.Manifest{Module: "store", Version: "1",
|
|
Provides: []catalogue.Offer{{Name: "postgres-database", Scope: catalogue.ScopeMesh}},
|
|
Serves: map[string]map[string]any{"postgres-database": {"port": 5432}},
|
|
Claims: []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}}}
|
|
}
|
|
|
|
func TestASecondEligibleHolderStandsBesideTheOneHeldByBeingAlone(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
// Every deploy seeds the mesh's own seats; a record is a row against one of them.
|
|
if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
register(t, open, aSeatedStore())
|
|
|
|
if _, err := assign(ctx, open, "anchor", "store"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
said, err := assign(ctx, open, "laptop", "store")
|
|
if err != nil {
|
|
t.Fatalf("a second store, eligible for the seat, was refused:\n%s\n%v", said, err)
|
|
}
|
|
if strings.Contains(said, "cannot be worked out") {
|
|
t.Fatalf("assigning a second store unsettled the first one's machine:\n%s", said)
|
|
}
|
|
if !strings.Contains(said, "recorded store on anchor as the standing holder of mesh-store") {
|
|
t.Fatalf("the holder by derivation was not written down:\n%s", said)
|
|
}
|
|
|
|
holdings, err := open.inventory.Holdings(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var found bool
|
|
for _, h := range holdings {
|
|
if h.Claim == "mesh-store" {
|
|
found = true
|
|
if h.Node != "anchor" || h.Module != "store" {
|
|
t.Fatalf("mesh-store is recorded on %s/%s, not on the one that held it", h.Node, h.Module)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("mesh-store has no holder on record after assigning: %v", holdings)
|
|
}
|
|
|
|
// And the record decides from here: the anchor's plan holds the seat, the laptop's does not.
|
|
for node, holds := range map[string]bool{"anchor": true, "laptop": false} {
|
|
plan, _, err := planFor(ctx, open, node)
|
|
if err != nil {
|
|
t.Fatalf("%s no longer resolves: %v", node, err)
|
|
}
|
|
var claimed bool
|
|
for _, c := range plan.Claims {
|
|
if c.Claim == "mesh-store" {
|
|
claimed = true
|
|
}
|
|
}
|
|
if claimed != holds {
|
|
t.Fatalf("%s holds mesh-store: %v, want %v", node, claimed, holds)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAHolderOnRecordIsNotRewrittenByDerivation(t *testing.T) {
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
register(t, open, aSeatedStore())
|
|
for _, node := range []string{"anchor", "laptop"} {
|
|
if _, err := assign(ctx, open, node, "store"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// A person hands the seat to the laptop. From here the record decides, and what the mesh
|
|
// derives must never write over it.
|
|
if err := open.inventory.HoldSeat(ctx, "mesh-store", catalogue.ScopeMesh, "laptop", "store"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
said, err := recordDerivedHolders(ctx, open)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(said) != 0 {
|
|
t.Fatalf("a seat on record was written again from derivation: %v", said)
|
|
}
|
|
holdings, _ := open.inventory.Holdings(ctx)
|
|
for _, h := range holdings {
|
|
if h.Claim == "mesh-store" && h.Node != "laptop" {
|
|
t.Fatalf("the record moved to %s", h.Node)
|
|
}
|
|
}
|
|
}
|