Files
mesh-controller/internal/catalogue/runtime_health_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

130 lines
5.5 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
"github.com/novox/mesh-host/validate"
)
// novox/hq ADR 0366: a module that runs as its own account declares its runtime's health under one manifest
// key, `runtime-health`, in the form a resource's `health` takes; the controller puts it on the process it
// writes for that runtime, and a tool check there stands beside the node-engine's judgement that the runtime
// is alive. A module that runs as no account of its own is refused the key.
// ownAccountManifest is a module of its own account with nothing long-running but its own runtime, given
// runtime-health (or none, when health is nil), and running as no account when runsAs is false.
func ownAccountManifest(t *testing.T, runsAs bool, health any) []byte {
t.Helper()
m := map[string]any{"module": "keeper", "tools": []any{"keeper_status", "keeper_health"},
"own-secrets": map[string]any{"broker": map[string]any{"path": "/var/lib/keeper/broker"}},
"resources": []any{map[string]any{"id": "account", "type": "user", "name": "keeper"}}}
if runsAs {
m["runs-as"], m["secrets-owner"] = "keeper", "keeper"
}
if health != nil {
m["runtime-health"] = health
}
raw, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
return raw
}
func TestAModuleOfItsOwnAccountDeclaresItsRuntimesHealthByItsOwnTool(t *testing.T) {
m, err := ParseManifest(ownAccountManifest(t, true, map[string]any{"kind": "tool", "tool": "keeper_health"}))
if err != nil {
t.Fatalf("a tool check on its own runtime was refused: %v", err)
}
if got := HealthChecks(m); len(got) != 1 || got[0] != "keeper.keeper_health" {
t.Errorf("the engine is granted %v, want keeper.keeper_health", got)
}
if r, has := OwnRuntimeHealth(m); !has || r["id"] != "keeper."+OwnRuntimeID() {
t.Errorf("its runtime's health reads as %v %v", r, has)
}
}
func TestRuntimeHealthIsRefusedWithoutAnAccountOfItsOwn(t *testing.T) {
_, err := ParseManifest(ownAccountManifest(t, false, map[string]any{"kind": "tool", "tool": "keeper_health"}))
if err == nil || !strings.Contains(err.Error(), "runtime-health") || !strings.Contains(err.Error(), "runs-as") {
t.Errorf("runtime-health without runs-as: %v", err)
}
}
func TestARuntimeHealthIsHeldToTheHealthRules(t *testing.T) {
for _, c := range []struct {
health any
says string
}{
{map[string]any{"kind": "tool", "tool": "keeper_restart"}, "does not serve"},
{map[string]any{"kind": "tool", "tool": "keeper_health", "interval": "2s"}, "no more often"},
{map[string]any{"kind": "exec", "command": "true"}, "only a container"},
{map[string]any{"kind": "tool", "tool": "keeper_health", "port": 80}, "names a port"},
{"tool", "is an object"},
} {
_, err := ParseManifest(ownAccountManifest(t, true, c.health))
if err == nil || !strings.Contains(err.Error(), c.says) {
t.Errorf("%v: %v, want it refused saying %q", c.health, err, c.says)
}
}
}
// The runtime-health is composed onto <module>.own-runtime, in the words the node-engine takes; to an engine
// that does not read health it is not sent, as for every resource.
func TestARuntimeHealthIsComposedOntoTheModulesOwnRuntime(t *testing.T) {
goRuntime := Manifest{Module: RuntimeModule, Version: "1",
OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}},
Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go",
System: "arch", From: "cmd/node-tools"}}}}
goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle,
Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}})
if err != nil {
t.Fatal(err)
}
keeper := aToolsModule(t, "keeper", "tools/index.js")
keeper.Tools = []string{"keeper_health"}
keeper.RunsAs, keeper.SecretsOwner = "keeper", "keeper"
keeper.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/keeper/broker"}}
keeper.RuntimeHealth = map[string]any{"kind": "tool", "tool": "keeper_health", "interval": "60s", "timeout": "10s",
"looks": 2.0}
for _, reads := range []bool{true, false} {
with := Rendering{ArtifactStore: "anchor.internal:5101", ReadsHealth: reads,
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed"}, "keeper": {"broker": "own"}}}
out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{keeper, goRuntime}}.Declaration(with)
if err != nil {
t.Fatal(err)
}
own := fileNamed(out, "keeper."+OwnRuntimeID())
if own == nil {
t.Fatalf("no own runtime: %v", ids(out))
}
h, has := own[HealthField].(map[string]any)
if !reads {
if has {
t.Errorf("health sent to an engine that does not read it: %v", h)
}
continue
}
if !has || h["kind"] != "tool" || h["tool"] != "keeper_health" || h["interval"] != "1m0s" ||
h["timeout"] != "10s" || h["looks"] != 2 || h["grace"] != "1m0s" {
t.Errorf("its own runtime's health: %v", own[HealthField])
}
if machine := fileNamed(out, RuntimeModule+"."+RuntimeProcessID()); machine[HealthField] != nil {
t.Errorf("the machine's runtime was given the module's health: %v", machine[HealthField])
}
body, err := json.Marshal(map[string]any{"declaration": validate.Version, "resources": out})
if err != nil {
t.Fatal(err)
}
if problems := validate.Declaration(body); len(problems) > 0 {
t.Errorf("composed into something the node-engine refuses: %v", problems)
}
}
// The catalogue's own manifest is not written into.
if _, ok := keeper.RuntimeHealth.(map[string]any)["grace"]; ok {
t.Error("composing wrote the defaults into the catalogue's manifest")
}
}