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"}) } 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]string{"postgres-database": "archive"}, }) 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]string{"postgres-database": "somewhere-else"}, }) 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]string{"postgres-database": "archive"}, }) if err == nil { t.Fatal("the only database was used although another was chosen") } if !strings.Contains(err.Error(), "only anchor 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 TestItSaysItCarriesNoCredential(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. 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, "no credential") { t.Fatalf("the file does not say what it does not carry: %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) } }