package catalogue import ( "encoding/json" "strings" "testing" ) // The other half of an edge. // // `requires` says a thing must be there. It never said what to do with it — a web application // requiring a reverse proxy has to say *which name, which port*, and there was nowhere to put // that. The two modules that needed it most, the proxy and the VPN, went round the outside and // opened a connection to the control plane's database, which is why every node held a credential // to it permanently. func published(module, host string, port int) Manifest { return Manifest{Module: module, Version: "1", Contributes: map[string]map[string]any{ "reverse-proxy": {"host": host, "port": port}, }} } func proxy() Manifest { return Manifest{Module: "traefik", Version: "1", Provides: Offers("reverse-proxy"), Receives: map[string]string{"reverse-proxy": "/etc/traefik/mesh.json"}, Resources: []map[string]any{ {"id": "up", "type": "service", "unit": "traefik", "state": "running", "restart-on": []any{ReceivedID("reverse-proxy")}}, }} } // received digs the delivered contributions back out of a declaration. func received(t *testing.T, out []map[string]any) []Contribution { t.Helper() for _, r := range out { if r["path"] != "/etc/traefik/mesh.json" { continue } body := r["content"].(string) var parsed struct { Requirement string `json:"requirement"` Given []Contribution `json:"given"` } if err := json.Unmarshal([]byte(body), &parsed); err != nil { t.Fatalf("the file the proxy is given is not readable: %v\n%s", err, body) } if parsed.Requirement != "reverse-proxy" { t.Fatalf("the file does not say what it is about: %q", parsed.Requirement) } return parsed.Given } t.Fatalf("the provider was given no file at all: %v", out) return nil } func TestTheFileTheProviderGetsIsMachineReadable(t *testing.T) { // It is written for a program, and the first version put a `//` header above the JSON — a // file that says "do not edit" to a person and fails to parse for the thing meant to read it. got, _ := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{}) for _, r := range mustDeclare(t, got) { if r["path"] != "/etc/traefik/mesh.json" { continue } var any map[string]any if err := json.Unmarshal([]byte(r["content"].(string)), &any); err != nil { t.Fatalf("the file is not parseable: %v\n%s", err, r["content"]) } if note, _ := any["generated"].(string); !strings.Contains(note, "do not edit") { // Inside the document rather than above it, so it reaches a person without // breaking the parse. t.Fatalf("the file does not say it is generated: %v", any["generated"]) } return } t.Fatal("no received file") } func TestAModuleTellsItsProviderWhatItNeeds(t *testing.T) { got, err := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{}) if err != nil { t.Fatal(err) } out, err := got.Declaration(Rendering{}) if err != nil { t.Fatal(err) } given := received(t, out) if len(given) != 1 || given[0].From != "board" { t.Fatalf("the proxy was not told about board: %v", given) } if given[0].Values["host"] != "board" || given[0].Values["port"] != float64(8080) { t.Fatalf("the contribution did not survive: %v", given[0].Values) } } func TestContributingToSomethingIsRequiringIt(t *testing.T) { // Asking to be published means a publisher must exist. A module that had to say both would // eventually say one, and the failure would be a machine where nothing serves the route. got, err := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{}) if err != nil { t.Fatal(err) } if !strings.Contains(strings.Join(names(got), " "), "traefik") { t.Fatalf("contributing to reverse-proxy did not bring one in: %v", names(got)) } } func TestNothingToContributeToIsRefused(t *testing.T) { _, err := Resolve(shelf(published("board", "board", 8080)), []string{"board"}, workstation(), World{}) if err == nil { t.Fatal("a module was published through a proxy that does not exist") } if !strings.Contains(err.Error(), "reverse-proxy") { t.Fatalf("the refusal does not name what is missing: %v", err) } } func TestEveryPublisherOnTheMachineIsInOneFile(t *testing.T) { got, err := Resolve(shelf(proxy(), published("board", "board", 8080), published("archive", "archive", 9000), ), []string{"board", "archive"}, workstation(), World{}) if err != nil { t.Fatal(err) } given := received(t, mustDeclare(t, got)) if len(given) != 2 { t.Fatalf("the proxy was told about %d of 2: %v", len(given), given) } // Ordered by module, because this becomes a file and a file whose lines move about looks // changed when nothing changed — which would restart the proxy for ever. if given[0].From != "archive" || given[1].From != "board" { t.Fatalf("the order is not stable: %v", given) } } func TestAProviderWithNoConsumersStillGetsTheFile(t *testing.T) { // Empty rather than absent. A provider that finds no file cannot tell "nothing asked for me" // from "the mesh never wrote it", and those want completely different responses — the same // rule the host follows about a service that does not exist. got, err := Resolve(shelf(proxy()), []string{"traefik"}, workstation(), World{}) if err != nil { t.Fatal(err) } if given := received(t, mustDeclare(t, got)); len(given) != 0 { t.Fatalf("got %v", given) } } func TestTheProviderCanReloadWhenTheRoutesChange(t *testing.T) { // A proxy that got a new route and did not reload is a route that silently does not work. // The same fault the private network had when a peer list changed under a running interface, // which is why the received file has a name a module can point at. got, _ := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{}) out := mustDeclare(t, got) for _, r := range out { if r["type"] != "service" { continue } reflects, ok := r["restart-on"].([]any) if !ok || len(reflects) != 1 { t.Fatalf("the proxy does not reflect anything: %v", r) } if reflects[0] != "traefik."+ReceivedID("reverse-proxy") { t.Fatalf("it reflects %v, which is not the file it was given", reflects[0]) } return } t.Fatal("no service in the declaration") } func TestARouteCanBeSetPerMesh(t *testing.T) { // The hostname is exactly the kind of thing that differs between one mesh and the next. A // module whose route could not be set would have to be edited to be reused anywhere. got, _ := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{}) out, err := got.Declaration(Rendering{Settings: SettingsBy{ "board": {{From: "the mesh", Values: map[string]any{"host": "dashboard"}}}, }}) if err != nil { t.Fatal(err) } given := received(t, out) if given[0].Values["host"] != "dashboard" { t.Fatalf("the setting did not reach the route: %v", given[0].Values) } if given[0].Values["port"] != float64(8080) { t.Fatalf("setting the host dropped the port: %v", given[0].Values) } } func TestReceivingWhatYouDoNotProvideIsRefused(t *testing.T) { // It would create a file nobody ever writes to, on a machine where nothing asked for it. _, err := ParseManifest([]byte(`{"module":"traefik","version":"1", "receives":{"reverse-proxy":"/etc/traefik/mesh.json"}}`)) if err == nil { t.Fatal("a module received contributions to something it does not provide") } if !strings.Contains(err.Error(), "does not provide") { t.Fatalf("unhelpful refusal: %v", err) } } func TestAnEmptyContributionIsRefused(t *testing.T) { // Either a mistake or a requirement written the long way round, and both are better said. _, err := ParseManifest([]byte(`{"module":"board","version":"1", "contributes":{"reverse-proxy":{}}}`)) if err == nil { t.Fatal("a module contributed nothing and was accepted") } if !strings.Contains(err.Error(), "require it") { t.Fatalf("the refusal does not say what to do instead: %v", err) } } // A provision answered from anywhere in the mesh has consumers on other machines, and the // provider has to know who they are. Contributions were node-local until this, which meant the // one case that most needed them was the one they did not reach. func provider() Manifest { return Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database"), Receives: map[string]string{"postgres-database": "/var/lib/postgres/grants/mesh.json"}, Grants: map[string]string{"postgres-database": "/var/lib/postgres/grants"}, } } // oneGrant is a declaration with a single consumer on another machine. func oneGrant(t *testing.T) []map[string]any { t.Helper() got, err := Resolve(shelf(provider()), []string{"postgres"}, reachable(), World{}) if err != nil { t.Fatal(err) } out, err := got.Declaration(Rendering{Grants: []Grant{{ Provision: "postgres-database", Consumer: "workstation", From: "meshboard", Values: map[string]any{"name": "meshboard"}, Sealed: "c2VhbGVk", }}}) if err != nil { t.Fatal(err) } return out } func grantedTo(t *testing.T, out []map[string]any) []Contribution { t.Helper() for _, r := range out { if r["path"] != "/var/lib/postgres/grants/mesh.json" { continue } var parsed struct { Given []Contribution `json:"given"` } if err := json.Unmarshal([]byte(r["content"].(string)), &parsed); err != nil { t.Fatal(err) } return parsed.Given } t.Fatalf("the provider was given no manifest: %v", out) return nil } func TestAProviderIsToldAboutConsumersOnOtherMachines(t *testing.T) { // A database told to create a password and not who for can do nothing with it. given := grantedTo(t, oneGrant(t)) if len(given) != 1 { t.Fatalf("got %v", given) } if given[0].Node != "workstation" || given[0].From != "meshboard" { t.Fatalf("it does not say who asked: %v", given[0]) } if given[0].Values["name"] != "meshboard" { t.Fatalf("it does not say what was asked for: %v", given[0].Values) } } func TestTheManifestNamesTheFileRatherThanCarryingTheCredential(t *testing.T) { // The mesh discarded the value and could not put it here if it wanted to. What is here is // where to find it — and the readable half therefore stays readable. out := oneGrant(t) given := grantedTo(t, out) if given[0].Secret != "/var/lib/postgres/grants/workstation.secret" { t.Fatalf("the manifest does not name the credential's file: %q", given[0].Secret) } for _, r := range out { if r["path"] != "/var/lib/postgres/grants/mesh.json" { continue } if strings.Contains(r["content"].(string), "c2VhbGVk") { t.Fatal("the readable manifest carries the sealed credential") } } } func TestTheCredentialItselfLandsSealedBesideIt(t *testing.T) { for _, r := range oneGrant(t) { if r["path"] != "/var/lib/postgres/grants/workstation.secret" { continue } if r["sealed"] != "c2VhbGVk" { t.Fatalf("got %v", r) } if r["content"] != nil { t.Fatal("a credential was written in the clear") } return } t.Fatal("no credential file") } func TestAConsumersOwnContributionsAreStillThere(t *testing.T) { // Cross-node grants are merged in with this machine's own, because from the provider's side // they are the same thing — somebody wanting something — and a provider that had to read two // lists would read one of them. got, err := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, reachable(), World{}) if err != nil { t.Fatal(err) } given := received(t, mustDeclare(t, got)) if len(given) != 1 || given[0].Node != "" { t.Fatalf("a local contribution grew a node: %v", given) } } func TestAConsumerThatStoppedAskingIsWithdrawn(t *testing.T) { // Withdrawal is how a credential is taken away. The provisioner removes what nobody asks for, // and it can only do that if the mesh stops asking — a consumer that was unassigned would // otherwise keep a working login for ever, and nothing would say so. // // A grant with no asking module is exactly that state: the mesh still holds the secret, // because it is sealed and unusable to the mesh anyway, and the machine no longer wants it. got, err := Resolve(shelf(provider()), []string{"postgres"}, reachable(), World{}) if err != nil { t.Fatal(err) } out, err := got.Declaration(Rendering{Grants: []Grant{ {Provision: "postgres-database", Consumer: "still-here", From: "meshboard", Values: map[string]any{"name": "meshboard"}, Sealed: "c2VhbGVk"}, {Provision: "postgres-database", Consumer: "gone-away", Sealed: "c3RhbGU="}, }}) if err != nil { t.Fatal(err) } given := grantedTo(t, out) if len(given) != 1 || given[0].Node != "still-here" { t.Fatalf("the provider is still told about a machine that stopped asking: %v", given) } // And no credential is written for it either, or the provisioner would find a file for a // consumer its manifest does not mention and have to guess what that means. for _, r := range out { if path, _ := r["path"].(string); strings.Contains(path, "gone-away") { t.Fatalf("a withdrawn consumer's credential is still delivered: %v", r) } } }