Files
mesh-controller/internal/catalogue/brokered_test.go
T
jschoubben cf84117638 A pin names the module as well as the node; a node that answers twice is refused
A provider is a (node, module) pair (design 23), and the pin — the one way a
consumer names its provider — named only the node. Two modules on one node
can both answer a provision (public-acme and step-ca both offer acme-ca on
novox), and then the resolver, given a pin naming that node, took the last
provider listed: a coin flip. The same ambiguity beside the consumer was
settled by a map walk — random per plan — which is how novox's own
route-proxy got its issuer (novox/hq #258).

- `pin <node> <provision> <from-node> <module>`: both halves, always. The
  console gains `pin` and `unpin`. The provider may be on the consumer's own
  node, since two modules beside it can both answer.
- The resolver refuses ambiguity instead of picking, across machines and
  beside the consumer alike, naming every candidate as node/module and the
  form of the pin that settles it. A plain capability that grants nothing
  and serves nothing (three shells beside an editor) is not a choice to put
  to anybody and stays as it was.
- provision_pin gains a nullable module (0050); records made before are
  completed where the node they name answers once, and left for a person
  where it answers twice (0051).
- The provider of something already satisfied is looked for among what was
  assigned, not only what the walk has reached — a consumer reached before
  the provider beside it no longer loses its binding.
- The start-time check that every declared verb is runnable samples each
  verb's required arguments from its schema instead of three guessed keys.

Live consequence: a node that has two providers of one bound provision
assigned (novox: acme-ca) resolves only once pinned —
`pin novox acme-ca novox public-acme`.
2026-10-01 17:23:31 +02:00

333 lines
13 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
)
// Where the answer to a requirement is allowed to live.
//
// A shell, a display server and a private network have to be on the machine that needs them. A
// database does not — it runs somewhere and is reached over the network. Both were written
// `requires`, so both were answered the same way, and the second answer was to install PostgreSQL
// on every machine that runs a web application.
// onNetwork is a set of providers, all of them reachable. Written as a helper because a machine
// that cannot reach the one answering its requirement is its own case, tested separately.
// reachable is this machine, on the private network.
func reachable() Node {
n := workstation()
n.At = "workstation.internal"
return n
}
func onNetwork(nodes ...string) map[string][]Provider {
out := make([]Provider, 0, len(nodes))
for _, n := range nodes {
out = append(out, Provider{Node: n, At: n + ".internal", Module: "postgres"})
}
return map[string][]Provider{"postgres-database": out}
}
func brokeredShelf() map[string]Manifest {
return shelf(
Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database")},
Manifest{Module: "meshboard", Version: "1", Requires: []string{"postgres-database"}},
)
}
func TestADatabaseIsNotInstalledOnEveryMachineThatUsesOne(t *testing.T) {
// The fault this whole distinction exists for.
got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{Offered: onNetwork("anchor")})
if err != nil {
t.Fatal(err)
}
if have := strings.Join(names(got), " "); strings.Contains(have, "postgres") {
t.Fatalf("using a database installed one here: %s", have)
}
}
func TestWhatAMachineTakesFromElsewhereIsRecorded(t *testing.T) {
// It is the only part of a node's set that stops working when a *different* machine goes
// away, and it is where a credential will have to be handed back.
got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{Offered: onNetwork("anchor")})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 {
t.Fatalf("got %v", got.Needs)
}
if got.Needs[0].Name != "postgres-database" || got.Needs[0].From != "anchor" {
t.Fatalf("got %v", got.Needs[0])
}
if got.Needs[0].For != "meshboard" {
t.Fatalf("it does not say what wanted it: %v", got.Needs[0])
}
}
func TestNothingInTheMeshProvidingItIsRefusedWithSomewhereToPutIt(t *testing.T) {
// Refused rather than installed here. Choosing a machine to put a database on is a decision
// with consequences, and nothing resolving a web application should make it silently.
_, err := Resolve(brokeredShelf(), []string{"meshboard"}, workstation(), World{})
if err == nil {
t.Fatal("a database was found in a mesh that has none")
}
if !strings.Contains(err.Error(), "assign") || !strings.Contains(err.Error(), "postgres") {
t.Fatalf("the refusal does not say what to do: %v", err)
}
}
func TestTwoNodesProvidingItIsRefusedRatherThanPicked(t *testing.T) {
// Same rule as everywhere else. Picking one would be a guess about which database a person
// meant, and the wrong guess is somebody's data in the wrong place.
_, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{Offered: onNetwork("anchor", "archive")})
if err == nil {
t.Fatal("one of two databases was picked silently")
}
for _, want := range []string{"anchor", "archive", "pin"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("the refusal does not name %s: %v", want, err)
}
}
}
func TestSayingWhichOneSettlesIt(t *testing.T) {
got, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{
Offered: onNetwork("anchor", "archive"),
Pinned: map[string]Chosen{"postgres-database": {Node: "archive", Module: "postgres"}},
})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
t.Fatalf("the choice was not taken: %v", got.Needs)
}
}
func TestBeingPointedAtAMachineThatDoesNotProvideItIsRefused(t *testing.T) {
// Rather than falling back to one that does. A fallback would quietly move somebody's data to
// a machine they did not choose, which is the whole reason the question is asked.
_, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{
Offered: onNetwork("anchor", "archive"),
Pinned: map[string]Chosen{"postgres-database": {Node: "somewhere-else", Module: "postgres"}},
})
if err == nil {
t.Fatal("a machine was silently given a different database from the one chosen")
}
if !strings.Contains(err.Error(), "somewhere-else") {
t.Fatalf("the refusal does not say what was chosen: %v", err)
}
}
func TestOneProviderDoesNotOverruleAChoice(t *testing.T) {
// A single answer is normally taken silently. Not when somebody said they wanted a different
// one -- that is the mesh overruling a person, which it does nowhere else.
_, err := Resolve(brokeredShelf(), []string{"meshboard"}, reachable(),
World{
Offered: onNetwork("anchor"),
Pinned: map[string]Chosen{"postgres-database": {Node: "archive", Module: "postgres"}},
})
if err == nil {
t.Fatal("the only database was used although another was chosen")
}
if !strings.Contains(err.Error(), "only anchor/postgres provides it") {
t.Fatalf("the refusal does not say what is available: %v", err)
}
}
func TestACatalogueThatDisagreesAboutScopeIsRefused(t *testing.T) {
// If one module says a database is local and another says it is anywhere, the same
// requirement means two things depending on which one happens to answer it.
_, err := Resolve(shelf(
Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database")},
Manifest{Module: "sqlite", Version: "1", Provides: Offers("postgres-database")},
Manifest{Module: "meshboard", Version: "1", Requires: []string{"postgres-database"}},
), []string{"meshboard"}, workstation(), World{})
if err == nil {
t.Fatal("a catalogue that disagrees about where a database lives was accepted")
}
if !strings.Contains(err.Error(), "two things") {
t.Fatalf("unhelpful refusal: %v", err)
}
}
func TestALocalRequirementIsStillAnsweredLocally(t *testing.T) {
// The change must not have made everything brokered. A shell is still installed here.
got, err := Resolve(shelf(
Manifest{Module: "zsh", Version: "1", Provides: Offers("shell")},
Manifest{Module: "tools", Version: "1", Requires: []string{"shell"}},
), []string{"tools"}, workstation(), World{})
if err != nil {
t.Fatal(err)
}
if have := strings.Join(names(got), " "); !strings.Contains(have, "zsh") {
t.Fatalf("a shell was not installed on the machine that needs one: %s", have)
}
}
func TestTheShortFormStillMeansHere(t *testing.T) {
// `"provides": ["shell"]` must keep meaning what it meant, or every existing manifest
// silently changes meaning.
m, err := ParseManifest([]byte(`{"module":"zsh","version":"1","provides":["shell"]}`))
if err != nil {
t.Fatal(err)
}
if len(m.Provides) != 1 || m.Provides[0].At() != ScopeNode {
t.Fatalf("a plain name is no longer node-scoped: %v", m.Provides)
}
}
func TestASiteScopedProvisionIsRefused(t *testing.T) {
// Meaningful for a claim — one DHCP server per segment — and not yet meaningful for a
// provision, because nothing knows how to reach "the one at my site".
_, err := ParseManifest([]byte(
`{"module":"dns","version":"1","provides":[{"name":"resolver","scope":"site"}]}`))
if err == nil {
t.Fatal("a site-scoped provision was accepted")
}
if !strings.Contains(err.Error(), "site") {
t.Fatalf("unhelpful refusal: %v", err)
}
}
// Being told, which is the difference between knowing and being able to.
func boundShelf() map[string]Manifest {
return shelf(
Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database"),
Serves: map[string]map[string]any{"postgres-database": {"port": 5432, "driver": "postgres"}}},
Manifest{Module: "meshboard", Version: "1", Requires: []string{"postgres-database"},
Binds: map[string]string{"postgres-database": "/etc/meshboard/database.json"}},
)
}
func binding(t *testing.T, out []map[string]any) map[string]any {
t.Helper()
for _, r := range out {
if r["path"] != "/etc/meshboard/database.json" {
continue
}
var parsed map[string]any
if err := json.Unmarshal([]byte(r["content"].(string)), &parsed); err != nil {
t.Fatalf("what the app is told is not readable: %v", err)
}
return parsed
}
t.Fatalf("the app was told nothing: %v", out)
return nil
}
func TestAnAppIsToldWhereItsDatabaseIs(t *testing.T) {
// Knowing it needs the anchor's database is useless to the program that needs it unless the
// program is told. This is the whole point of the field.
got, err := Resolve(boundShelf(), []string{"meshboard"}, reachable(),
World{Offered: map[string][]Provider{"postgres-database": {{Node: "anchor", At: "anchor.internal",
Serves: map[string]any{"port": 5432, "driver": "postgres"}}}}})
if err != nil {
t.Fatal(err)
}
told := binding(t, mustDeclare(t, got))
if told["from"] != "anchor" || told["at"] != "anchor.internal" {
t.Fatalf("it was not told where: %v", told)
}
serves, _ := told["serves"].(map[string]any)
if serves["port"] != float64(5432) || serves["driver"] != "postgres" {
t.Fatalf("it was not told how: %v", serves)
}
}
func TestItSaysWhereTheCredentialIsInstead(t *testing.T) {
// A missing field looks like a bug; a stated absence looks like a boundary. Somebody wiring
// this up must not spend an afternoon looking for the password field.
//
// It used to say only that the mesh had no way to issue one, which stopped being true when
// 021 was fixed. A stated absence is only useful while it is accurate — once it is not, it
// sends the reader somewhere there is nothing to find.
got, _ := Resolve(boundShelf(), []string{"meshboard"}, reachable(),
World{Offered: map[string][]Provider{"postgres-database": {{Node: "anchor", At: "anchor.internal"}}}})
told := binding(t, mustDeclare(t, got))
note, _ := told["generated"].(string)
if !strings.Contains(note, "not here") || !strings.Contains(note, "secrets") {
t.Fatalf("the file does not say where the credential is instead: %v", note)
}
for key := range told {
if strings.Contains(key, "password") || strings.Contains(key, "secret") {
t.Fatalf("something that looks like a credential appeared: %q", key)
}
}
}
func TestTwoMachinesThatCannotReachEachOtherAreRefused(t *testing.T) {
// An app on one machine and a database on another that share no private network is a mesh
// that reports itself configured and does not work. Said here rather than discovered as a
// connection timing out.
_, err := Resolve(boundShelf(), []string{"meshboard"}, workstation(), // not on the network
World{Offered: map[string][]Provider{"postgres-database": {{Node: "anchor", At: "anchor.internal"}}}})
if err == nil {
t.Fatal("an app was pointed at a database it has no path to")
}
if !strings.Contains(err.Error(), "private network") {
t.Fatalf("the refusal does not say what is wrong: %v", err)
}
if !strings.Contains(err.Error(), meshNetwork) {
t.Fatalf("the refusal does not say what to assign: %v", err)
}
}
func TestTheProviderBeingOffTheNetworkIsAlsoRefused(t *testing.T) {
// Both directions, because the failure is identical from either end and the remedy differs.
_, err := Resolve(boundShelf(), []string{"meshboard"}, reachable(),
World{Offered: map[string][]Provider{"postgres-database": {{Node: "anchor"}}}})
if err == nil {
t.Fatal("an app was pointed at a database that is not on the private network")
}
if !strings.Contains(err.Error(), "anchor") {
t.Fatalf("the refusal does not name the unreachable end: %v", err)
}
}
func TestBindingSomethingAnsweredHereWritesNothing(t *testing.T) {
// A file saying "it is on this node" is a fact nobody needs and one more thing to keep true.
got, err := Resolve(shelf(
Manifest{Module: "zsh", Version: "1", Provides: Offers("shell")},
Manifest{Module: "tools", Version: "1", Requires: []string{"shell"},
Binds: map[string]string{"shell": "/etc/tools/shell.json"}},
), []string{"tools"}, reachable(), World{})
if err != nil {
t.Fatal(err)
}
for _, r := range mustDeclare(t, got) {
if r["path"] == "/etc/tools/shell.json" {
t.Fatalf("a binding was written for something on this machine: %v", r)
}
}
}
func TestBindingWhatYouDoNotRequireIsRefused(t *testing.T) {
_, err := ParseManifest([]byte(`{"module":"app","version":"1",
"binds":{"postgres-database":"/etc/app/db.json"}}`))
if err == nil {
t.Fatal("a module was told about something it never asked for")
}
if !strings.Contains(err.Error(), "does not require") {
t.Fatalf("unhelpful refusal: %v", err)
}
}
func TestServingWhatYouDoNotProvideIsRefused(t *testing.T) {
_, err := ParseManifest([]byte(`{"module":"app","version":"1",
"serves":{"postgres-database":{"port":5432}}}`))
if err == nil {
t.Fatal("a module served something it does not provide")
}
if !strings.Contains(err.Error(), "does not provide") {
t.Fatalf("unhelpful refusal: %v", err)
}
}