Files
mesh-host/internal/bootstrap/preflight_test.go
T
jschoubben 121367319d Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo
becomes mesh-controller, the seat the-controller, and the store+broker pair the
foundation (embedded base bundles, default template and example lock renamed with
their go:embed directives). No behaviour change — a pure vocabulary rename.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-16 18:40:40 +02:00

152 lines
6.0 KiB
Go

package bootstrap
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/novox/mesh-host/internal/declaration"
)
// An installed package is not a capability (novox/hq 04-ISSUES/007). The daemon is asked, and a
// machine where it does not answer is refused before anything is loaded, written or applied.
//
// The refusal has to be plain, because the person reading it is standing in front of a machine
// that will not work: it says what was asked, what came back, that re-running is safe, and names
// the record that explains why an installed docker is not enough.
func TestPreflightRefusesPlainlyWhenTheRuntimeDoesNotAnswer(t *testing.T) {
silent := func(context.Context, string, ...string) (string, error) {
return "", errors.New("Cannot connect to the Docker daemon at unix:///var/run/docker.sock")
}
// No wait, so this is one attempt: what is being tested is the refusal, not the patience.
err := waitForRuntime(context.Background(), silent, time.Second, 0, func(string) {})
if err == nil {
t.Fatal("a machine whose container runtime does not answer was accepted")
}
for _, wanted := range []string{
"no container runtime that answers",
"Cannot connect to the Docker daemon",
"04-ISSUES/007",
"idempotent",
} {
if !strings.Contains(err.Error(), wanted) {
t.Errorf("the refusal does not mention %q:\n%v", wanted, err)
}
}
}
// And a runtime that is merely slow to start is waited for rather than refused.
//
// A socket-activated daemon queued behind the network is not absent, it is a few seconds away.
// Refusing on the first attempt would make a correct bootstrap fail for being observed too early —
// and `docker load` against such a daemon blocks silently rather than failing, which is how one
// became a 35-minute silence (04-ISSUES/024).
func TestARuntimeThatIsStillStartingIsWaitedFor(t *testing.T) {
previous := runtimeAskEvery
runtimeAskEvery = time.Millisecond
defer func() { runtimeAskEvery = previous }()
attempts := 0
slow := func(context.Context, string, ...string) (string, error) {
attempts++
if attempts < 3 {
return "", errors.New("Cannot connect to the Docker daemon")
}
return "27.0.3\n", nil
}
var said []string
if err := waitForRuntime(context.Background(), slow, time.Second, time.Second,
func(line string) { said = append(said, line) }); err != nil {
t.Fatalf("a runtime that answered on the third ask was refused: %v", err)
}
if attempts != 3 {
t.Errorf("the runtime was asked %d time(s)", attempts)
}
if !strings.Contains(strings.Join(said, "\n"), "27.0.3") {
t.Errorf("the version the daemon reported was not said back: %v", said)
}
}
// What has to be reachable is what the bundle actually names, not "the internet".
//
// The mesh's own image is carried and nothing serves it, so asking a registry about it would be
// asking a question with no answer — which is the whole point of naming an image by the digest of
// its own configuration.
func TestOnlyTheRegistriesTheBundleNamesAreAskedAbout(t *testing.T) {
parsed, err := declaration.ParseFileTrusted([]byte(`{"declaration":1,"resources":[
{"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` +
strings.Repeat("7", 64) + `"},
{"id":"broker","type":"container","name":"mesh-broker","image":"192.0.2.250:5000/lavinmq@sha256:` +
strings.Repeat("8", 64) + `"},
{"id":"control-plane","type":"container","name":"mesh-controller","image":"` + held + `"}
]}`))
if err != nil {
t.Fatal(err)
}
got := registriesIn(parsed)
want := []string{DefaultRegistry, "192.0.2.250:5000"}
if len(got) != len(want) {
t.Fatalf("asked about %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("asked about %v, want %v", got, want)
}
}
}
// And the control plane's slot is excluded whatever is in it.
//
// Found on the first real run of this installer. A template names the control plane by SOMETHING
// and step 3 replaces it with the id of the carried image, so whatever was there is never pulled —
// but preflight was reading that slot like any other and dialling it. The lab's template still
// carried the address of a registry the lab no longer raises, so a correct install timed out in
// preflight against a machine with a perfectly good network.
func TestTheControlPlanesOwnRegistryIsNeverAskedAbout(t *testing.T) {
parsed, err := declaration.ParseFileTrusted([]byte(`{"declaration":1,"resources":[
{"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` +
strings.Repeat("7", 64) + `"},
{"id":"control-plane","type":"container","name":"mesh-controller","image":"192.0.2.250:5000/mesh-controller@sha256:` +
strings.Repeat("8", 64) + `"}
]}`))
if err != nil {
t.Fatal(err)
}
got := registriesIn(parsed)
if len(got) != 1 || got[0] != DefaultRegistry {
t.Fatalf("asked about %v; the control plane's own reference is about to be replaced and "+
"must not be reached for", got)
}
}
func TestWhereAnImageWouldBeFetchedFrom(t *testing.T) {
// The container runtime's own rule: the part before the first slash is a registry host if it
// has a dot, a port, or is localhost. Getting this wrong means dialling a hostname that is
// really the first half of a repository name, and refusing a machine that is fine.
for _, c := range []struct {
reference string
host string
served bool
}{
{"postgres@sha256:" + strings.Repeat("a", 64), DefaultRegistry, true},
{"cloudamqp/lavinmq@sha256:" + strings.Repeat("a", 64), DefaultRegistry, true},
{"192.0.2.250:5000/postgres@sha256:" + strings.Repeat("a", 64), "192.0.2.250:5000", true},
{"localhost/mesh-controller@sha256:" + strings.Repeat("a", 64), "localhost:443", true},
{"registry.example.com/a/b@sha256:" + strings.Repeat("a", 64), "registry.example.com:443", true},
// Held by this machine. Nothing serves it, and nothing can.
{"sha256:" + strings.Repeat("a", 64), "", false},
{"", "", false},
} {
host, served := registryOf(c.reference)
if host != c.host || served != c.served {
t.Errorf("%q → (%q, %v), want (%q, %v)", c.reference, host, served, c.host, c.served)
}
}
}