package resolution import ( "encoding/json" "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) // 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() mcat.Node { n := workstation() n.At = "workstation.internal" return n } func onNetwork(nodes ...string) map[string][]mcat.Provider { out := make([]mcat.Provider, 0, len(nodes)) for _, n := range nodes { out = append(out, mcat.Provider{Node: n, At: n + ".internal", Module: "postgres"}) } return map[string][]mcat.Provider{"postgres-database": out} } func brokeredShelf() map[string]mcat.Manifest { return shelf(mcat.Manifest{Module: "postgres", Version: "1", Provides: mcat.FromAnywhere("postgres-database")}, mcat.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(), mcat.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(), mcat.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(), mcat.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(), mcat.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(), mcat.World{ Offered: onNetwork("anchor", "archive"), Pinned: map[string]mcat.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(), mcat.World{ Offered: onNetwork("anchor", "archive"), Pinned: map[string]mcat.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(), mcat.World{ Offered: onNetwork("anchor"), Pinned: map[string]mcat.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(mcat.Manifest{Module: "postgres", Version: "1", Provides: mcat.FromAnywhere("postgres-database")}, mcat.Manifest{Module: "sqlite", Version: "1", Provides: mcat.Offers("postgres-database")}, mcat.Manifest{Module: "meshboard", Version: "1", Requires: []string{"postgres-database"}}), []string{"meshboard"}, workstation(), mcat.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(mcat.Manifest{Module: "zsh", Version: "1", Provides: mcat.Offers("shell")}, mcat.Manifest{Module: "tools", Version: "1", Requires: []string{"shell"}}), []string{"tools"}, workstation(), mcat.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 boundShelf() map[string]mcat.Manifest { return shelf(mcat.Manifest{Module: "postgres", Version: "1", Provides: mcat.FromAnywhere("postgres-database"), Serves: map[string]map[string]any{"postgres-database": {"port": 5432, "driver": "postgres"}}}, mcat.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(), mcat.World{Offered: map[string][]mcat.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(), mcat.World{Offered: map[string][]mcat.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(), mcat. // not on the network World{Offered: map[string][]mcat.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(), mcat.World{Offered: map[string][]mcat.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(mcat.Manifest{Module: "zsh", Version: "1", Provides: mcat.Offers("shell")}, mcat.Manifest{Module: "tools", Version: "1", Requires: []string{"shell"}, Binds: map[string]string{"shell": "/etc/tools/shell.json"}}, ), []string{"tools"}, reachable(), mcat.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) } } }