Files
mesh-catalog/modules/mesh-build-image/cmd/mesh-build-image/module_test.go
T

188 lines
7.2 KiB
Go

package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
// 1703a: a machine where buildah runs, the account holds ranges in both files and a namespace can be made is healthy,
// and the health names buildah and the ranges (ADR 0487 §2: proven, not declared).
func TestHealthIsHealthyWithBuildahRangesAndANamespace(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
a := m.Health(context.Background())
if a.State != health.StateHealthy || !a.Healthy {
t.Fatalf("health = %+v, want healthy", a)
}
for _, want := range []string{"buildah 1.45.1", "mesh-build-image holds 65536 subordinate uids and 65536 gids"} {
if !strings.Contains(a.Why, want) {
t.Errorf("health says %q, lacks %q", a.Why, want)
}
}
if _, err := os.Stat(m.Store + "/xdg"); err != nil {
t.Errorf("the runtime directory inside the store was not made: %v", err)
}
}
func TestHealthIsUnhealthyWithoutBuildah(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
return forgekey.Ran{Status: 127, Err: "exec: \"buildah\": executable file not found in $PATH"}
}
a := m.Health(context.Background())
if a.Healthy || !strings.Contains(a.Why, "buildah") {
t.Errorf("health = %+v, want unhealthy naming buildah", a)
}
}
// The ranges are read from the two files, one each: without one in /etc/subuid or in /etc/subgid the account has no ids to
// map, and the health says which file. And without /dev/net/tun (a build node on 2026-10-28: its running kernel's modules were
// gone, so tun could not be loaded) buildah's network for a RUN line cannot be made: the health says the node is not
// eligible, and the puller judges by 0 so its asks go to a node that can build them.
func TestHealthIsUnhealthyWithoutWhatABuildNeeds(t *testing.T) {
for _, c := range []struct {
name string
set func(*Module, string)
says []string
}{
{"subuid", func(m *Module, p string) { m.SubUIDFile = p }, []string{"/subuid", "mesh-build-image"}},
{"subgid", func(m *Module, p string) { m.SubGIDFile = p }, []string{"/subgid", "mesh-build-image"}},
} {
f := newFake(t)
m := moduleWith(t, f, newStates())
p := t.TempDir() + "/" + c.name
os.WriteFile(p, []byte("someone-else:100000:65536\n"), 0o644)
c.set(m, p)
a := m.Health(context.Background())
for _, want := range c.says {
if a.Healthy || !strings.Contains(a.Why, want) {
t.Errorf("%s: health = %+v, want unhealthy saying %q", c.name, a, want)
}
}
// Whatever the look refuses, the node is not eligible, with the same words.
if got := m.Eligible(); got != a.Why {
t.Errorf("%s: eligible says %q, the health %q", c.name, got, a.Why)
}
}
}
// 1126: a node that cannot build here now (no tun device) stays healthy, saying "not eligible" and why; the assigner reads
// unhealthy as a failed delivery and held the new build for every node. The eligibility, the status and the log say it too.
func TestANodeWithoutTunIsHealthyAndSaysItIsNotEligible(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
var said []string
m.Say = func(format string, a ...any) { said = append(said, fmt.Sprintf(format, a...)) }
m.Tun = t.TempDir() + "/dev/net/tun"
a := m.Health(context.Background())
if a.State != health.StateHealthy || !a.Healthy {
t.Fatalf("health = %+v, want healthy", a)
}
for _, want := range []string{"not eligible", "/dev/net/tun cannot be opened", "tun"} {
if !strings.Contains(a.Why, want) {
t.Errorf("health says %q, lacks %q", a.Why, want)
}
}
if got := m.Eligible(); !strings.Contains(got, "not eligible: ") {
t.Errorf("eligible says %q", got)
}
if s := m.Status(context.Background()); s["eligible"] != false || s["refusing"] != true {
t.Errorf("status = %v, want not eligible", s)
}
// One log line for the change, none for the same look again; one when it can build again.
m.Health(context.Background())
if len(said) != 1 || !strings.Contains(said[0], "takes no image build now") {
t.Errorf("said = %q, want one line for the change", said)
}
m.Tun = filepath.Join(t.TempDir(), "tun")
os.WriteFile(m.Tun, nil, 0o644)
if a := m.Health(context.Background()); !a.Healthy || strings.Contains(a.Why, "not eligible") {
t.Errorf("health = %+v, want plain healthy", a)
}
if len(said) != 2 || !strings.Contains(said[1], "eligible to build images again") {
t.Errorf("said = %q, want the return said", said)
}
// A step still ends refused with the cause named.
m.Tun = t.TempDir() + "/none"
if err := m.Check(context.Background()); err == nil || !strings.Contains(err.Error(), "not eligible") {
t.Errorf("check = %v", err)
}
}
// 1126: what cannot run stays unhealthy even beside a missing tun: buildah missing is not "not eligible".
func TestWithoutBuildahTheNodeIsUnhealthyNotIneligible(t *testing.T) {
m := moduleWith(t, newFake(t), newStates())
m.Tun = t.TempDir() + "/none"
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
return forgekey.Ran{Status: 127, Err: "exec: \"buildah\": executable file not found in $PATH"}
}
if a := m.Health(context.Background()); a.Healthy {
t.Errorf("health = %+v, want unhealthy", a)
}
}
func TestHealthIsUnhealthyWhenANamespaceCannotBeMade(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
run := m.Run
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
if len(args) >= 2 && args[len(args)-2] == "unshare" {
return forgekey.Ran{Status: 125, Stderr: "Error: cannot clone: Operation not permitted\n"}
}
return run(ctx, env, name, args...)
}
a := m.Health(context.Background())
if a.Healthy || !strings.Contains(a.Why, "user namespace") {
t.Errorf("health = %+v, want unhealthy naming the user namespace", a)
}
}
func TestSubRangeSumsTheAccountsLinesByNameOrUid(t *testing.T) {
p := t.TempDir() + "/subuid"
os.WriteFile(p, []byte("a:1:10\nmesh-build-image:100:65536\n1234:200000:100\nb:5:5\nbad line\n"), 0o644)
if n, err := SubRange(p, "mesh-build-image", "1234"); err != nil || n != 65636 {
t.Errorf("range = %d, %v, want 65636", n, err)
}
if n, _ := SubRange(p, "nobody", ""); n != 0 {
t.Errorf("nobody has %d", n)
}
}
// A step on a machine that cannot build ends refused with the cause named, and nothing else runs.
func TestAStepOnAMachineThatCannotBuildEndsRefused(t *testing.T) {
f := newFake(t)
states := newStates()
m := moduleWith(t, f, states)
m.SubGIDFile = "/nonexistent/subgid"
if _, err := tool(t, m, FetchTool)(map[string]any{"repository": "novox/mesh-catalog", "commit": commitSHA}); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepRefused || !strings.Contains(st.Reason, "/nonexistent/subgid") {
t.Errorf("step = %+v, want refused naming the file", st)
}
for _, l := range f.lines() {
if strings.HasPrefix(l, "git ") {
t.Errorf("a refused step ran %q", l)
}
}
}
func TestStatusSaysWhatTheMachineHas(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
s := m.Status(context.Background())
if s["refusing"] != false || s["buildah"] != "1.45.1" || s["subuids"] != 65536 || s["account"] != "mesh-build-image" {
t.Errorf("status = %v", s)
}
}