Files
mesh-controller/internal/catalogue/foundation_manifests_test.go
T
jschoubben 0e413e3e7a Hold the forge's port to the same rule as every other provider's
The package registry was the one foundation port not resolved from what its
module serves. Nothing in the controller had to change for it — `ports` on the
forge moves its container, what it serves and what consumers are told, and the
builder's carried binding is settable like any other mergeable file — but
nothing said so, which is how it came to be special in the first place.

Two tests over the catalogue's own manifests: the forge's port is given on a
node and reaches what it serves, and the builder's carried binding takes the
port from the node while keeping who the binding is with.

novox/hq 04-ISSUES/085
2026-09-22 21:40:08 +02:00

181 lines
7.2 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)
}
if got := catalogueManifest(t, "lavinmq").Guards; !reflect.DeepEqual(got, []int{15672}) {
t.Errorf("lavinmq guards %v; the management 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). Two halves, because the forge is reached two ways: through what the
// module that serves it says it serves, and — for the genesis window, before any module provides
// `package-registry` at all — through the one binding the builder carries instead of resolving.
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, "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, "package-registry", nil)["port"]; got != float64(3000) {
t.Errorf("without a setting the forge serves %v, not the catalogue's port", got)
}
}
// bindingIn is the package binding the builder carries, as the machine would receive it.
func bindingIn(t *testing.T, m Manifest, layers []Layer) map[string]any {
t.Helper()
for _, r := range m.Resources {
if fmt.Sprint(r["id"]) != "package-binding" {
continue
}
settled, err := ApplySettings(r, layers)
if err != nil {
t.Fatalf("the builder's package binding refused %v: %v", layers, err)
}
if settled["merge"] != nil || settled["protected"] != nil {
t.Fatal("the host would be sent fields it does not know")
}
var out map[string]any
if err := json.Unmarshal([]byte(fmt.Sprint(settled["content"])), &out); err != nil {
t.Fatalf("the builder's package binding is not a binding: %v", err)
}
return out
}
t.Fatal("the builder carries no package binding")
return nil
}
func TestTheBuildersCarriedPackageBindingTakesThePortFromTheNode(t *testing.T) {
builder := catalogueManifest(t, "builder")
// Nothing set: the catalogue's own number, which is what a mesh raised on the defaults uses.
serves := bindingIn(t, builder, nil)["serves"].(map[string]any)
if serves["port"] != float64(3000) {
t.Fatalf("the builder's binding defaults to %v", serves["port"])
}
// Given a port, the binding dials it — and the rest of what the forge serves survives, because
// a setting is merged into the module's own values rather than replacing them.
moved := bindingIn(t, builder, []Layer{{From: "anchor",
Values: map[string]any{"serves": map[string]any{"port": float64(3100)}}}})
got := moved["serves"].(map[string]any)
if got["port"] != float64(3100) {
t.Errorf("the builder dials %v, not the port this node gave the package registry", got["port"])
}
if got["scheme"] != "http" || got["npm-path"] != "/api/packages/novox/npm/" {
t.Errorf("setting the port lost the rest of what the forge serves: %v", got)
}
if moved["as"] != "mesh-builder" || moved["from"] != "gitea" {
t.Errorf("setting the port changed who the binding is with: %v", moved)
}
}
func TestTheBuildersPackageBindingKeepsItsIdentity(t *testing.T) {
builder := catalogueManifest(t, "builder")
for _, key := range []string{"provision", "from", "as"} {
var refused error
for _, r := range builder.Resources {
if fmt.Sprint(r["id"]) != "package-binding" {
continue
}
_, refused = ApplySettings(r, []Layer{{From: "anchor",
Values: map[string]any{key: "something else"}}})
}
if refused == nil {
t.Errorf("%q can be set on the builder's package binding, which is not a port but who "+
"the binding is with", key)
}
}
}