gitea's own code moves out of its runtime container (mesh-catalog, to-be 38 WP4c waves 2-3), so the three tests that composed the forge from the catalogue beside this checkout resolve its build as the code bundle, compose it beside the node's runtime, and read the forge's address from the words the runtime hands the module rather than from a sidecar's env.
312 lines
14 KiB
Go
312 lines
14 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"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 code, 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: "code", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "gitea/code/blobs/" + bundleDigest, Digest: bundleDigest,
|
|
}})
|
|
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, theRuntime(t)}, 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{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"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"])
|
|
}
|
|
// The forge's own code runs in the node's runtime (novox/hq ADR 0198), given its words there.
|
|
runtime := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
if runtime == nil {
|
|
t.Fatalf("the node's runtime is not in the declaration: %v", ids(out))
|
|
}
|
|
env, _ := runtime["env"].(map[string]string)
|
|
var given map[string]map[string]string
|
|
if err := json.Unmarshal([]byte(env[RuntimeToolEnv]), &given); err != nil {
|
|
t.Fatalf("the runtime's %s is not JSON: %q", RuntimeToolEnv, env[RuntimeToolEnv])
|
|
}
|
|
if given["gitea"]["MESH_GITEA_URL"] != "http://127.0.0.1:2999" {
|
|
t.Fatalf("the forge's code dials %v while the machine publishes the forge on 2999 — "+
|
|
"whatever reads it dials a dead port", given["gitea"]["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: "code", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "gitea/code/blobs/" + bundleDigest, Digest: bundleDigest,
|
|
}})
|
|
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, theRuntime(t)}, 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{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"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"])
|
|
}
|
|
}
|