Files
mesh-host/cmd/mesh-host/witnessing_test.go
T
jochen 0c405b70cc Roll a core build back by a witness that is not the new build (hq to-be 45 Phase 4)
The launcher trusted a counter only a by-hand reconcile ever cleared and a
known-good nothing in the daemon wrote, so no machine could roll its host back;
the controller and the node tools were replaced in place with nothing kept.

- The launcher runs a delivered host that is not known-good on trial: one that
  crashes, stops for nothing, or does not report within ten minutes goes back
  to known-good, once per version, recorded in rolled-back. The host proves
  itself when the mesh takes a report under its own build, says every standing
  verdict on its reports, never stands aside for a rolled-back version, and
  restarts its service once when its launcher was replaced on disk.
- The engine keeps the controller's and the node tools' previous build beside
  the new one and judges the new one: the lease taken by the controller it
  started (read-only direct get of mesh-controller_lease/holder), or this
  machine's runtime answering $SRV.PING.node-tools.<node>, within sixty seconds
  of time it could ask. Not healthy: the previous restored, once, said. Proved:
  the previous deleted. A build declared not-reversible is never rolled back.
- Retire never removes a version newer than the running one.
2026-10-06 18:23:56 +02:00

94 lines
3.4 KiB
Go

package main
import (
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/novox/mesh-host/internal/link"
"github.com/novox/mesh-host/internal/upgrade"
)
// The first account the mesh takes from this build, about a declaration, proves it to the launcher;
// nothing else does — a refusal, a report about no declaration, another build's report (to-be 45 §8).
func TestOnlyAnAccountUnderThisBuildProvesIt(t *testing.T) {
state := filepath.Join(t.TempDir(), "state.json")
t.Setenv("MESH_HOST_LIBEXEC", t.TempDir())
var said []string
p := &proving{statePath: state, version: "2.0", say: func(s string) { said = append(said, s) }}
for _, r := range []link.Report{
{Host: "2.0", Refused: "no"},
{Host: "2.0"},
{Host: "1.0", Declared: "abc"},
} {
p.heard(r)
if !unproved(state, "2.0") {
t.Fatalf("%+v proved this build", r)
}
}
p.heard(link.Report{Host: "2.0", Declared: "abc"})
if unproved(state, "2.0") {
t.Fatalf("an account of a declaration under this build did not prove it (%v)", said)
}
if got, _ := upgrade.ReadKnownGood(upgrade.KnownGoodPath(state)); got != "2.0" {
t.Fatalf("known-good is %q", got)
}
}
// Every verdict that stands is said, and a reconcile that has one says it unasked.
func TestAStandingRollbackIsSaidOnEveryReport(t *testing.T) {
state := filepath.Join(t.TempDir(), "state.json")
line := "2.0\t1.0\t1759744800\trolled-back\tit did not report within 600s of starting\n"
if err := os.WriteFile(upgrade.RolledBackPath(state), []byte(line), 0o644); err != nil {
t.Fatal(err)
}
got := standingRollbacks(state)
if len(got) == 0 || got[0].Component != link.ComponentEngine || got[0].From != "2.0" || got[0].To != "1.0" ||
got[0].Outcome != link.RolledBack {
t.Fatalf("what the report says is %+v", got)
}
if !worthSaying(link.Report{Rollbacks: got}) {
t.Fatal("a reconcile with a rollback standing does not say it")
}
if adoptionFingerprint(link.Report{Rollbacks: got}) == adoptionFingerprint(link.Report{}) {
t.Fatal("a rollback reached or ended is not news to the reconcile")
}
}
// The launcher running this host is known to have been replaced only when it certainly was: a shell
// reading the launcher, from a file renamed over. Asked of a real process, not a model of one.
func TestAReplacedLauncherIsSeen(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "launch")
if err := os.WriteFile(path, []byte("#!/bin/sh\nsleep 5\necho done\n"), 0o755); err != nil {
t.Fatal(err)
}
shell := exec.Command("sh", path)
if err := shell.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = shell.Process.Kill(); _ = shell.Wait() })
time.Sleep(200 * time.Millisecond)
if launcherReplaced(shell.Process.Pid, "/proc", path) {
t.Fatal("a launcher still on disk reads as replaced")
}
// Delivered as the mesh writes a file: a new one renamed over it.
if err := os.WriteFile(path+".new", []byte("#!/bin/sh\necho new\n"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Rename(path+".new", path); err != nil {
t.Fatal(err)
}
if !launcherReplaced(shell.Process.Pid, "/proc", path) {
fds, _ := os.ReadDir(filepath.Join("/proc", strconv.Itoa(shell.Process.Pid), "fd"))
t.Skipf("this shell does not keep its script open (%d fds), so a replaced launcher cannot be seen here", len(fds))
}
if launcherReplaced(shell.Process.Pid, "/proc", filepath.Join(dir, "other")) {
t.Fatal("a process reading another script reads as this launcher")
}
}