The three tests of the controller's own module.json (process, claimed seat, addresses), the Dockerfile and Makefile pin test, the build-agent registry test and the manifest-in-use seat test move with their old package, as tests of mesh-sdk's exported API; a moved test is exempt from failing first.
134 lines
5.5 KiB
Go
134 lines
5.5 KiB
Go
package ownmanifest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
. "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// novox/hq issue 213: the controller is a Go program and was the one piece of the mesh's own Go
|
|
// code still shipped as an image (ADR 0188 §1). Its own manifest, composed for the machine that runs
|
|
// it, is a Go bundle run by the host as a process — and no container.
|
|
func TestTheControllerIsAProcessAndNoContainer(t *testing.T) {
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := ParseManifest(raw)
|
|
if err != nil {
|
|
t.Fatalf("the controller's own manifest does not parse:\n%v", err)
|
|
}
|
|
if m.Build == nil || len(m.Build.Artifacts) != 1 {
|
|
t.Fatalf("the controller builds %+v; it is one bundle", m.Build)
|
|
}
|
|
a := m.Build.Artifacts[0]
|
|
if a.Kind != ArtifactBundle || a.Language != "go" || a.System == "" || BinaryOf(a) != "mesh-controller" {
|
|
t.Fatalf("the controller's artifact is %+v, not a Go bundle naming its system and binary", a)
|
|
}
|
|
for _, c := range m.Capabilities {
|
|
if c == "container-runtime" {
|
|
t.Error("the controller still requires a container runtime on its machine")
|
|
}
|
|
}
|
|
|
|
digest := "sha256:" + strings.Repeat("c", 64)
|
|
control, err := m.Resolve([]Built{{Name: a.Name, Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "mesh-controller/" + a.Name + "@" + digest, Digest: digest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The node's runtime does not launch it: it serves its seat's verbs itself.
|
|
if loads := control.Bundles[0].Loads; len(loads) != 0 {
|
|
t.Errorf("the node's runtime would launch the controller as a tools bundle: %v", loads)
|
|
}
|
|
|
|
needed := map[string]map[string]string{"mesh-controller": {}}
|
|
for name := range m.OwnSecrets {
|
|
needed["mesh-controller"][name] = "sealed-" + name
|
|
}
|
|
out, err := Resolution{Node: "anchor", Modules: []Manifest{control}}.Declaration(Rendering{
|
|
Needed: needed, ArtifactStore: "anchor.internal:5100",
|
|
Seats: map[string]map[int]int{"mesh-store": {5432: 6852}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("the controller does not compose: %v", err)
|
|
}
|
|
|
|
var process, step map[string]any
|
|
account, firstSecret := -1, -1
|
|
for i, r := range out {
|
|
switch {
|
|
case r["type"] == "container":
|
|
t.Errorf("the controller's declaration still runs a container: %v", r)
|
|
case r["id"] == "mesh-controller.controller":
|
|
process = r
|
|
case r["id"] == "mesh-controller.controller-prepare":
|
|
step = r
|
|
if process != nil {
|
|
t.Error("the controller's preparation is placed after the process it prepares for")
|
|
}
|
|
case r["type"] == "user" && r["name"] == "mesh-controller":
|
|
account = i
|
|
case strings.HasPrefix(fmt.Sprint(r["id"]), "mesh-controller.needs-") && firstSecret < 0:
|
|
firstSecret = i
|
|
}
|
|
}
|
|
if process == nil {
|
|
t.Fatalf("the controller's process is not in its declaration: %v", out)
|
|
}
|
|
if run, _ := json.Marshal(process["run"]); string(run) != `["./mesh-controller","serve"]` {
|
|
t.Errorf("the controller is run as %s, not its own bundle's binary", run)
|
|
}
|
|
if process["source"] != "anchor.internal:5100/mesh-controller/"+a.Name+"@"+digest || process["digest"] != digest {
|
|
t.Errorf("the controller's bundle is fetched from %v (%v)", process["source"], process["digest"])
|
|
}
|
|
// The user: an account the host declares, which owns what the process reads.
|
|
if process["user"] != "mesh-controller" || account < 0 {
|
|
t.Errorf("the controller runs as %v, and the account declared is at %d", process["user"], account)
|
|
}
|
|
if firstSecret >= 0 && account > firstSecret {
|
|
t.Error("the controller's secrets are written before the account they belong to exists")
|
|
}
|
|
for _, r := range out {
|
|
if strings.HasPrefix(fmt.Sprint(r["id"]), "mesh-controller.needs-") && r["owner"] != "mesh-controller" {
|
|
t.Errorf("%v belongs to %v, which the controller's process cannot read", r["id"], r["owner"])
|
|
}
|
|
}
|
|
if dir := fileNamed(out, "mesh-controller.mesh-state"); dir == nil || dir["owner"] != "mesh-controller" {
|
|
t.Errorf("the controller's state directory is not its account's to enter: %v", dir)
|
|
}
|
|
// Each mount became a path the process reads: nothing it is told is a path inside a container.
|
|
state := fmt.Sprint(fileNamed(out, "mesh-controller.mesh-state")["path"])
|
|
env, _ := process["env"].(map[string]any)
|
|
for key, value := range env {
|
|
v := fmt.Sprint(value)
|
|
if strings.HasPrefix(v, "/run/secrets") || strings.HasPrefix(v, "/broker-tls") {
|
|
t.Errorf("%s=%s is a path inside the container the controller no longer runs in", key, v)
|
|
}
|
|
if strings.HasSuffix(key, "_FILE") && !strings.HasPrefix(v, state+"/") {
|
|
t.Errorf("%s=%s is not one of the files the mesh places for it", key, v)
|
|
}
|
|
}
|
|
if env["MESH_BROKER_CERTIFICATE"] != "/var/lib/mesh-broker-tls/tls.crt" {
|
|
t.Errorf("the controller reads the broker's certificate from %v", env["MESH_BROKER_CERTIFICATE"])
|
|
}
|
|
if env["MESH_STORE_INVENTORY_PORT"] != "6852" {
|
|
t.Errorf("the controller is told the store is on %v; the node put it on 6852", env["MESH_STORE_INVENTORY_PORT"])
|
|
}
|
|
// The handover: the container it ran as goes only once this is running.
|
|
if got, _ := json.Marshal(process["replaces"]); string(got) != `["mesh-controller.server"]` {
|
|
t.Errorf("the controller's process replaces %s, not the container it ran as", got)
|
|
}
|
|
// And its state is prepared first, by the same program as the same account.
|
|
if step == nil || step["run-once"] != true || step["user"] != "mesh-controller" {
|
|
t.Fatalf("the controller's preparation is %v", step)
|
|
}
|
|
if run, _ := json.Marshal(step["run"]); string(run) != `["./mesh-controller","prepare"]` {
|
|
t.Errorf("the controller's preparation runs %s", run)
|
|
}
|
|
}
|