Both branches changed the seat set from the same starting point, so every number collided and every `mesh-*` name existed twice. The trunk's numbers and names win: this branch's records became 0129/0130 and its migrations 0037/0038, and the hardcoded rename map gave way to the trunk's `seat_alias` table — a rename is a row now (ADR 0122), not a recompile. Three of my checks were wrong and the merge is what showed it: A seat with an empty protocol is a marker, not an incomplete declaration. Most node-scoped seats are markers — which module is this machine's packet filter — and refusing one refused most of the set, the showcase module included. A mistyped field name is already refused by the parser, so an empty protocol was written as one deliberately. A claim on a seat this manifest does not declare is not the parser's to judge. A module may hold a seat another module declared; that is the whole reason ADR 0126 has callers name the seat and not its provider. Whether the seat exists is a fact about the catalogue, so the refusal is at registration, where every declaration is in view. And a seat may share a name with the provision it delivers. `git`, the npm registry and the artifact store still do, because renaming a delivering seat cascades to every consumer requiring it, with a window where a holder stops resolving mid-flight. The trunk deferred exactly those three on purpose. Full suite green against a real NATS and store.
94 lines
3.0 KiB
Go
94 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// Whether a mesh could move its bus, read from a real store.
|
|
//
|
|
// The readiness reasoning has its own tests; this is about the gathering — that the question is
|
|
// answered from what the mesh actually holds, on a store with machines and modules in it, without
|
|
// writing anything.
|
|
|
|
func TestReadinessIsGatheredFromWhatTheMeshHolds(t *testing.T) {
|
|
inv := inventory.ForTest(t)
|
|
ctx := context.Background()
|
|
|
|
// A mesh mid-change: two machines, the bus module on one of them, a module that speaks and a
|
|
// module that never does.
|
|
for _, m := range []catalogue.Manifest{
|
|
{Module: "nats", Version: "1", BusUsers: "/var/lib/nats-module/conf/accounts.conf",
|
|
Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}},
|
|
{Module: "gitea", Version: "1", Tools: []string{"repo_create"}},
|
|
{Module: "wallpaper", Version: "1"},
|
|
} {
|
|
if err := inv.RegisterModule(ctx, m, inventory.Source{Repository: "/r"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, n := range []string{"anchor", "laptop"} {
|
|
if _, err := inv.AddNode(ctx, n); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, a := range [][2]string{{"anchor", "nats"}, {"anchor", "gitea"}, {"laptop", "wallpaper"}} {
|
|
if _, err := inv.Assign(ctx, a[0], a[1]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
state, err := readinessOf(ctx, inv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if state.Holder != "anchor" {
|
|
t.Errorf("the machine holding the bus reads as %q", state.Holder)
|
|
}
|
|
if len(state.Nodes) != 2 {
|
|
t.Errorf("machines read as %v", state.Nodes)
|
|
}
|
|
// **A module that never speaks is not counted as missing a credential.** A third of the catalogue
|
|
// never reaches the bus, and listing those would bury the ones that matter.
|
|
for _, m := range state.Modules {
|
|
if strings.HasSuffix(m, "/wallpaper") {
|
|
t.Errorf("a module that never speaks was counted: %v", state.Modules)
|
|
}
|
|
}
|
|
if len(state.Modules) != 2 {
|
|
t.Errorf("modules that speak read as %v; expected the bus module and the one with a tool",
|
|
state.Modules)
|
|
}
|
|
|
|
// Nothing has been minted, so it is not ready — and it says so about each machine by name.
|
|
why := notReadyOf(state)
|
|
if len(why) == 0 {
|
|
t.Fatal("a mesh where nothing has a credential was reported ready to move")
|
|
}
|
|
said := strings.Join(why, "\n")
|
|
for _, name := range []string{"anchor", "laptop"} {
|
|
if !strings.Contains(said, name) {
|
|
t.Errorf("the refusal does not name %s: %s", name, said)
|
|
}
|
|
}
|
|
|
|
// Mint for one machine and it drops out of the complaint, which is how somebody works through it.
|
|
if _, err := inv.MintBusPassword(ctx, inventory.BusUser{
|
|
Username: "node.laptop", Kind: inventory.BusNode, Node: "laptop",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state, err = readinessOf(ctx, inv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !state.Credentialled["laptop"] {
|
|
t.Error("a machine that was minted a credential still reads as having none")
|
|
}
|
|
}
|