package catalogue import ( "fmt" "os" "reflect" "strings" "testing" ) // The catalogue's foundation modules as they are, parsed by the real parser (novox/hq ADR 0100): // the store and the broker say which of their ports the mesh guards on an adopted node, and the // filter module loads its table through a unit of its own whose stop deletes only that table. func catalogueManifest(t *testing.T, module string) Manifest { t.Helper() raw, err := os.ReadFile("../../../mesh-catalog/modules/" + module + "/module.json") if err != nil { t.Skipf("the catalogue is not beside this checkout: %v", err) } m, err := ParseManifest(raw) if err != nil { t.Fatalf("%s does not parse:\n%v", module, err) } return m } func TestTheStoreAndTheBrokerSayWhatTheMeshGuards(t *testing.T) { if got := catalogueManifest(t, "postgres").Guards; !reflect.DeepEqual(got, []int{5432}) { t.Errorf("postgres guards %v; the store's port must be refused from outside", got) } // The bus's monitoring port, not its client port: a node reaches the bus, nobody outside // reads its state (novox/hq ADR 0131 — the broker that guarded 15672 has left the catalogue). if got := catalogueManifest(t, "nats").Guards; !reflect.DeepEqual(got, []int{8222}) { t.Errorf("nats guards %v; the monitoring port must be refused from outside", got) } } func TestTheFilterModuleNeverFlushesTheRuleset(t *testing.T) { m := catalogueManifest(t, "nftables") var unit, stock, load map[string]any for _, r := range m.Resources { switch r["id"] { case "unit": unit = r case "stock-unit-stop": stock = r case "load": load = r } } if load == nil || load["unit"] != "mesh-filter.service" { t.Fatalf("the filter is not loaded by its own unit: %v", load) } content, _ := unit["content"].(string) if unit == nil || unit["path"] != "/etc/systemd/system/mesh-filter.service" { t.Fatalf("the filter's unit is not written: %v", unit) } if strings.Contains(content, "flush") { t.Fatalf("stopping the filter flushes the whole ruleset — the runtime's and the found "+ "firewall's with it:\n%s", content) } if !strings.Contains(content, "ExecStop=nft delete table inet mesh\n") || !strings.Contains(content, "ExecStart=nft -f "+m.Filtering.Into+"\n") { t.Fatalf("the unit does not load the computed rule set and delete only its own table:\n%s", content) } // A node converged before the filter had its own unit still has the stock nftables.service // enabled, whose stop flushes the whole ruleset: a drop-in makes it delete only the mesh's // table, and the load is restarted on it so the host reloads units and the drop-in is read. if stock == nil || stock["path"] != "/etc/systemd/system/nftables.service.d/mesh.conf" || !strings.HasSuffix(fmt.Sprint(stock["content"]), "[Service]\nExecStop=\nExecStop=nft delete table inet mesh\n") { t.Fatalf("the stock unit's stop is not replaced with deleting the mesh's table: %v", stock) } // A changed rule set is RELOADED — ExecReload replaces the table in one `nft -f`, so the node // is never unfiltered — and only the units themselves restart it, which is the one change a // reload cannot carry. if !reflect.DeepEqual(load["reload-on"], []any{"filtering"}) { t.Fatalf("the filter is restarted rather than reloaded when its rules change, leaving the "+ "node unfiltered in between: %v", load) } if !reflect.DeepEqual(load["restart-on"], []any{"unit", "stock-unit-stop"}) { t.Fatalf("the filter is not restarted when its unit or the stock unit's drop-in changes: %v", load["restart-on"]) } } // The package registry's port is the node's, like every other foundation port (novox/hq // 04-ISSUES/085, ADR 0100). The forge is reached through what it says it serves, and consumers — // the builder among them — are told that, rather than carrying a number of their own. func TestTheForgesPortIsGivenLikeAnyOtherProvidersPort(t *testing.T) { forge := catalogueManifest(t, "gitea") // The catalogue's number is a default and the node's setting moves it. given, err := GivenPorts(forge, []Layer{{From: "anchor", Values: map[string]any{PortsSetting: map[string]any{"3000": float64(3100)}}}}) if err != nil { t.Fatalf("the forge's port cannot be given on a node: %v", err) } if given[3000] != 3100 { t.Fatalf("the forge was given %v", given) } // And every consumer of the package registry is told where the machine actually put it, // because that is read from what the forge serves rather than written in the consumer. if got := ServedOn(forge, "npm-package-registry", given)["port"]; got != 3100 { t.Errorf("the package registry is served on %v, not the port this node gave it", got) } if got := ServedOn(forge, "npm-package-registry", nil)["port"]; got != float64(3000) { t.Errorf("without a setting the forge serves %v, not the catalogue's port", got) } // And so is where a repository on it is cloned from (novox/hq ADR 0111), for the same reason: // a build composes the URL from what the forge serves, so a given port is a followed port. if got := ServedOn(forge, "git", given)["port"]; got != 3100 { t.Errorf("git is served on %v, not the port this node gave the forge", got) } } // **The builder requires the registry the npm seat delivers, and carries no binding of its own.** // // It used to carry a hand-written binding because nothing provided a package registry to resolve // one from at genesis. The catalogue now requires it like any consumer, and ADR 0110 makes the // seat's holder the answer when more than one module provides it — so a carried copy would be a // second answer to the same question, free to drift from the first. Asserted gone, not merely // unused. func TestTheBuilderRequiresTheRegistryTheNpmSeatDelivers(t *testing.T) { builder := catalogueManifest(t, "builder") seat, _ := SeatNamed("npm-package-registry") var requires bool for _, r := range builder.Requires { requires = requires || r == seat.Delivers } if !requires { t.Fatalf("the builder does not require %q: %v", seat.Delivers, builder.Requires) } if builder.Binds[seat.Delivers] == "" { t.Errorf("the builder is not told where the registry is: binds %v", builder.Binds) } for _, r := range builder.Resources { if fmt.Sprint(r["id"]) == "package-binding" { t.Fatal("the builder carries its own package binding beside the one the mesh resolves") } } } // The forge holds the seats it answers for (novox/hq ADR 0110, 0111), parsed by the real parser — // which refuses a delivering seat claimed by a module that does not provide what it delivers. func TestTheForgeHoldsTheNpmAndGitSeats(t *testing.T) { forge := catalogueManifest(t, "gitea") holds := map[string]bool{} for _, c := range forge.Claims { holds[c.Name] = true } for _, seat := range []string{"npm-package-registry", "git"} { if !holds[seat] { t.Errorf("gitea does not claim the %s seat: %+v", seat, forge.Claims) } } git := ServedOn(forge, "git", nil) if git["scheme"] != "http" || git["port"] != float64(3000) { t.Errorf("gitea serves nothing a clone URL can be composed from: %v", git) } npm := ServedOn(forge, "npm-package-registry", nil) if npm["npm-path"] != "/api/packages/novox/npm/" { t.Errorf("gitea no longer says where its npm registry is: %v", npm) } } // **And the forge's own address follows it**, composed from the manifest in the catalogue beside // this checkout (novox/hq 04-ISSUES/088). // // The forge is reached a third way that neither test above covers: by its own sidecar, over the // machine's loopback, told where to go in its environment. The `2999:3000` mapping that lets the // forge go on binding 3000 does nothing for a caller dialling the machine — so a literal there is // wrong on every node whose assignment differs, and wrong for a second reason on a node given the // port (ADR 0100). Composed through the whole path, because what proves the placeholder resolves // in an `env` at all is a declaration, not a manifest. func TestTheForgesOwnAddressFollowsThePortTheNodeGaveIt(t *testing.T) { forge, err := catalogueManifest(t, "gitea").Resolve([]Built{{ Name: "runtime", Kind: ArtifactImage, Reference: "registry.example/gitea-runtime@sha256:" + strings.Repeat("a", 64), }}) if err != nil { t.Fatalf("the forge's manifest does not resolve against its own build: %v", err) } r := Resolution{Node: "anchor", Modules: []Manifest{forge}, Needs: []Needed{ {Name: "postgres-database", For: "gitea", From: "anchor", At: "127.0.0.1", Serves: map[string]any{"port": float64(5432)}, Sealed: "sealed-db"}, {Name: "route", For: "gitea", From: "anchor"}, {Name: "secret", For: "gitea", From: "anchor", Local: "internal-token", Sealed: "sealed-token"}, {Name: "secret", For: "gitea", From: "anchor", Local: "admin", Sealed: "sealed-admin"}, }} // The number this node was given for the forge — the one the machine it is about to run on // already publishes. out, err := r.Declaration(Rendering{ Needed: map[string]map[string]string{"gitea": {"broker": "sealed-broker"}}, Given: map[string]map[int]int{"gitea": {3000: 2999}}, }) if err != nil { t.Fatalf("the forge does not compose: %v", err) } // What the machine publishes, and what the forge's sidecar is told to dial: one number. server := fileNamed(out, "gitea.server") if server == nil { t.Fatalf("the forge's own container is not in the declaration: %v", out) } if published := fmt.Sprint(server["ports"]); !strings.Contains(published, "2999:3000") { t.Fatalf("the forge is not published on the port this node gave it: %v", server["ports"]) } runtime := fileNamed(out, "gitea.runtime") if runtime == nil { t.Fatalf("the forge's sidecar is not in the declaration: %v", out) } env, _ := runtime["env"].(map[string]any) if env["MESH_GITEA_URL"] != "http://127.0.0.1:2999" { t.Fatalf("the forge's sidecar dials %v while the machine publishes the forge on 2999 — "+ "whatever reads it dials a dead port", env["MESH_GITEA_URL"]) } } // gitea's own sshd is unmodified — the module's own internal port is 22, the number in // `listens`, the same convention every other module in the catalogue uses (its internal port, // not an invented identity). Composed from the manifest in the catalogue beside this checkout, // because what the mesh publishes is a fact about what the module actually writes. func declaredGiteaSsh(t *testing.T, given map[int]int) map[string]any { t.Helper() forge := catalogueManifest(t, "gitea") resolved, err := forge.Resolve([]Built{{ Name: "runtime", Kind: ArtifactImage, Reference: "registry.example/gitea-runtime@sha256:" + strings.Repeat("a", 64), }}) if err != nil { t.Fatalf("the forge's manifest does not resolve against its own build: %v", err) } r := Resolution{Node: "anchor", Modules: []Manifest{resolved}, Needs: []Needed{ {Name: "postgres-database", For: "gitea", From: "anchor", At: "127.0.0.1", Serves: map[string]any{"port": float64(5432)}, Sealed: "sealed-db"}, {Name: "route", For: "gitea", From: "anchor"}, {Name: "secret", For: "gitea", From: "anchor", Local: "internal-token", Sealed: "sealed-token"}, {Name: "secret", For: "gitea", From: "anchor", Local: "admin", Sealed: "sealed-admin"}, }} givenPorts := map[int]int{3000: 3000} for k, v := range given { givenPorts[k] = v } out, err := r.Declaration(Rendering{ Needed: map[string]map[string]string{"gitea": {"broker": "sealed-broker"}}, Ports: map[string]map[int]int{"gitea": givenPorts}, Given: map[string]map[int]int{"gitea": given}, }) if err != nil { t.Fatalf("the forge does not compose: %v", err) } server := fileNamed(out, "gitea.server") if server == nil { t.Fatalf("the forge's own container is not in the declaration: %v", out) } return server } // **The forge publishes ssh at the mesh's own fixed convention by default** (novox/hq ADR 0100). // // `222` is the mesh's own public convention for the forge's ssh, written directly in the // manifest's `ports` — every node the forge has run on used the same number, so it needs no // per-node setting to reach it. func TestTheForgesSshPortIsTheMeshsFixedConventionByDefault(t *testing.T) { forge := catalogueManifest(t, "gitea") // Nothing was given — no node moved this port — which is the ordinary answer: the mesh only // reports what a setting moved, and the manifest's own `222:22` needs no move to be reached. given, err := GivenPorts(forge, nil) if err != nil { t.Fatalf("the forge's ssh port cannot be given on a node: %v", err) } if len(given) != 0 { t.Fatalf("nothing moved the forge's ssh port, yet it was given %v", given) } server := declaredGiteaSsh(t, given) if published := fmt.Sprint(server["ports"]); !strings.Contains(published, "222:22") { t.Fatalf("the forge is published on %v, not its own fixed convention", server["ports"]) } } // **A node whose predecessor served git on a different number can still be told to leave it // there.** The setting names the port the module itself listens on — 22, gitea's own sshd, the // same number `listens` uses — not the mesh's own default machine-side number, so moving it does // not require guessing what the manifest happens to default to. func TestANodeMayGiveTheForgesSshPortADifferentNumber(t *testing.T) { forge := catalogueManifest(t, "gitea") given, err := GivenPorts(forge, []Layer{{From: "anchor", Values: map[string]any{PortsSetting: map[string]any{"22": float64(9022)}}}}) if err != nil { t.Fatalf("the forge's ssh port cannot be moved on a node: %v", err) } if want := map[int]int{22: 9022}; !reflect.DeepEqual(given, want) { t.Fatalf("the forge was given %v, and the setting named %v", given, want) } server := declaredGiteaSsh(t, given) if published := fmt.Sprint(server["ports"]); !strings.Contains(published, "9022:22") || strings.Contains(published, "222:22") { t.Fatalf("the forge is published on %v, not the port this node gave it", server["ports"]) } }