package sentinel import ( "os" "path/filepath" "strings" "testing" ) func TestCollectUsesCommandOutputAndPkgSample(t *testing.T) { dir := fakeSentinelCommands(t) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) snap := Collect() if snap.Hostname != "titan-13" || snap.Kernel != "6.6.63" || snap.OSImage != "Metis OS" { t.Fatalf("unexpected snapshot: %+v", snap) } if snap.K3sVersion != "v1.31.5+k3s1" || snap.Containerd != "1.7.99" { t.Fatalf("unexpected runtime facts: %+v", snap) } if len(snap.PackageSample) != 4 || snap.PackageSample["k3s"] != "v1.31.5+k3s1" { t.Fatalf("unexpected package sample: %+v", snap.PackageSample) } if snap.USBScratch == nil || snap.USBScratch.Label != "titan-16-scratch" || !snap.USBScratch.MountHealthy || !snap.USBScratch.LabelHealthy || !snap.USBScratch.BindHealthy { t.Fatalf("unexpected usb scratch sample: %+v", snap.USBScratch) } } func TestCommandOutputUsesNsenterWhenRequested(t *testing.T) { dir := fakeSentinelCommands(t) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) t.Setenv("METIS_SENTINEL_NSENTER", "1") got, err := commandOutput("ignored", "arg") if err != nil { t.Fatalf("commandOutput: %v", err) } if strings.TrimSpace(string(got)) != "nsenter-ok" { t.Fatalf("unexpected nsenter output: %q", string(got)) } } func TestRunAndTrimAndPkgVersionFallbacks(t *testing.T) { dir := t.TempDir() write := func(name, body string) { path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte("#!/usr/bin/env bash\nset -eu\n"+body+"\n"), 0o755); err != nil { t.Fatalf("write %s: %v", name, err) } } write("cat", `printf 'ID=metis\n'`) write("rpm", `exit 1`) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) if got := runAndTrim("missing-command"); got != "" { t.Fatalf("runAndTrim missing command = %q", got) } if got := osRelease(); got != "" { t.Fatalf("osRelease without PRETTY_NAME = %q", got) } if got := pkgVersion("does-not-exist"); got != "" { t.Fatalf("pkgVersion fallback = %q", got) } } func TestCollectUSBScratchMissingAndMalformedConfig(t *testing.T) { cases := []struct { name string cat string }{ {name: "cat error", cat: `exit 1`}, {name: "empty config", cat: `printf ' '`}, {name: "malformed config", cat: `printf '{'`}, {name: "missing scratch config", cat: `printf '{}'`}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := fakeCollectorCommands(t, map[string]string{"cat": tc.cat}) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) if got := collectUSBScratch(); got != nil { t.Fatalf("expected nil USB scratch config, got %#v", got) } }) } } func TestCollectUSBScratchDeviceResolutionAndBindHealth(t *testing.T) { dir := fakeCollectorCommands(t, map[string]string{ "cat": `printf '%s\n' '{"usb_scratch":{"mountpoint":"/mnt/scratch","uuid":"u1","label":"scratch","fs":"xfs","bind_targets":["","/bad","/bind"]}}'`, "findmnt": `target="" for ((i=1; i<=$#; i++)); do if [[ "${!i}" == "-T" ]]; then j=$((i + 1)) target="${!j}" break fi done case "${target}" in /mnt/scratch) printf 'SOURCE="" TARGET="/mnt/scratch" FSTYPE="ext4"\n' ;; /bind) printf 'SOURCE="/mnt/scratch" TARGET="/bind" FSTYPE="none"\n' ;; *) exit 1 ;; esac`, "blkid": `case "${1:-}" in -U) printf '/dev/by-uuid/u1\n' ;; -L) printf '/dev/by-label/scratch\n' ;; -o) printf 'UUID=other\nLABEL=scratch\nTYPE=xfs\n' ;; esac`, }) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) scratch := collectUSBScratch() if scratch == nil { t.Fatal("expected USB scratch data") } if scratch.MountHealthy || scratch.UUIDHealthy || !scratch.LabelHealthy || scratch.BindHealthy { t.Fatalf("unexpected scratch health: %#v", scratch) } if len(scratch.BindTargets) != 3 || scratch.BindTargets[2].Path != "/bind" || !scratch.BindTargets[2].Healthy { t.Fatalf("unexpected bind target health: %#v", scratch.BindTargets) } } func TestCollectUSBScratchDefaultsAndParserEdges(t *testing.T) { dir := fakeCollectorCommands(t, map[string]string{ "cat": `printf '%s\n' '{"usb_scratch":{"mountpoint":"/mnt/scratch"}}'`, "findmnt": `printf 'SOURCE="/dev/sdz1" TARGET="/mnt/scratch" FSTYPE="ext4"\n'`, "blkid": `exit 1`, }) t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) scratch := collectUSBScratch() if scratch == nil || !scratch.MountHealthy || !scratch.BindHealthy || scratch.FS != "ext4" { t.Fatalf("unexpected default scratch health: %#v", scratch) } if source, fsType, mounted := mountInfo(""); source != "" || fsType != "" || mounted { t.Fatalf("empty mountInfo = %q %q %v", source, fsType, mounted) } if bindHealthy("", "/mnt/scratch") || bindHealthy("/bind", "") { t.Fatal("empty bind inputs should be unhealthy") } if got := resolveDeviceByUUID(""); got != "" { t.Fatalf("empty UUID resolved to %q", got) } if got := resolveDeviceByLabel(""); got != "" { t.Fatalf("empty label resolved to %q", got) } if got := blkidExport(""); len(got) != 0 { t.Fatalf("empty blkid export = %#v", got) } values := parseKeyValues(`BROKEN A="b" C=d`) if values["A"] != "b" || values["C"] != "d" { t.Fatalf("parseKeyValues = %#v", values) } } func fakeCollectorCommands(t *testing.T, scripts map[string]string) string { t.Helper() dir := t.TempDir() for name, body := range scripts { path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte("#!/usr/bin/env bash\nset -eu\n"+body+"\n"), 0o755); err != nil { t.Fatalf("write %s: %v", name, err) } } return dir } func fakeSentinelCommands(t *testing.T) string { t.Helper() dir := t.TempDir() write := func(name, body string) { path := filepath.Join(dir, name) if err := os.WriteFile(path, []byte("#!/usr/bin/env bash\nset -eu\n"+body+"\n"), 0o755); err != nil { t.Fatalf("write %s: %v", name, err) } } write("hostname", `printf 'titan-13\n'`) write("uname", `printf '6.6.63\n'`) write("k3s", `printf 'v1.31.5+k3s1\n'`) write("containerd", `printf '1.7.99\n'`) write("cat", `case "${1:-}" in /etc/os-release) printf 'PRETTY_NAME="Metis OS"\n' ;; /etc/metis/node.json) printf '%s\n' '{"usb_scratch":{"mountpoint":"/mnt/scratch","label":"titan-16-scratch","fs":"ext4","bind_targets":["/var/lib/rancher","/var/log"]}}' ;; *) printf 'PRETTY_NAME="Metis OS"\n' ;; esac`) write("findmnt", `target="" for ((i=1; i<=$#; i++)); do if [[ "${!i}" == "-T" ]]; then j=$((i + 1)) target="${!j}" break fi done case "${target}" in /mnt/scratch) printf 'SOURCE="/dev/sdz1" TARGET="/mnt/scratch" FSTYPE="ext4"\n' ;; /var/lib/rancher) printf 'SOURCE="/mnt/scratch" TARGET="/var/lib/rancher" FSTYPE="none"\n' ;; /var/log) printf 'SOURCE="/mnt/scratch" TARGET="/var/log" FSTYPE="none"\n' ;; *) exit 1 ;; esac`) write("blkid", `case "${1:-}" in -U) printf '/dev/sdz1\n' ;; -L) printf '/dev/sdz1\n' ;; -o) printf 'UUID=titan-16-uuid\nLABEL=titan-16-scratch\nTYPE=ext4\n' ;; esac`) write("dpkg-query", `case "${@: -1}" in containerd) printf '1.7.99\n' ;; k3s) printf 'v1.31.5+k3s1\n' ;; nvidia-container-toolkit) printf '1.16.2\n' ;; linux-image-raspi) printf '6.6.63\n' ;; *) printf '1.0.0\n' ;; esac`) write("rpm", `printf '1.0.0\n'`) write("nsenter", `printf 'nsenter-ok\n'`) return dir }