474b858c0b
Storage §8 decision table (stop + enable --now on idle triggers; active mounts untouched), daemon leg at startup with per-running-guest visibility verify, guest-hook post-start leg (root, direct systemctl, non-fatal). Red-proofs: always-rearm table FAIL; unwired hook FAIL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
161 lines
6.9 KiB
Go
161 lines
6.9 KiB
Go
package storage
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// The §8 decision table, encoded exactly (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 1).
|
||
func TestNetReassertAction_Table(t *testing.T) {
|
||
cases := []struct {
|
||
fstype string
|
||
want string
|
||
}{
|
||
{"nfs4", NetReassertSkipActive}, // real mount — inherited by fresh namespaces
|
||
{"nfs", NetReassertSkipActive}, // real mount
|
||
{"cifs", NetReassertSkipActive}, // real mount
|
||
{"autofs", NetReassertRearmed}, // idle trigger — NOT inherited, re-arm to propagate
|
||
{"", NetReassertSkipNone}, // nothing at the path — removed/orphan, owned elsewhere
|
||
{"ext4", NetReassertSkipNone}, // a local fs at the path is not a network state we own
|
||
{"tmpfs", NetReassertSkipNone}, //
|
||
}
|
||
for _, c := range cases {
|
||
if got := netReassertAction(c.fstype); got != c.want {
|
||
t.Errorf("netReassertAction(%q) = %q, want %q", c.fstype, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// installNetUnitFile writes a rendered network .mount unit straight into unitDir (bypassing the
|
||
// runner-mediated install — the reassert only READS unit files).
|
||
func installNetUnitFile(t *testing.T, unitDir string, spec NetworkMountSpec) (where, automountUnit string) {
|
||
t.Helper()
|
||
where = spec.Where()
|
||
mountUnit, err := UnitNameForMount(where)
|
||
if err != nil {
|
||
t.Fatalf("unit name: %v", err)
|
||
}
|
||
if err := os.WriteFile(filepath.Join(unitDir, mountUnit), []byte(renderNetworkMountUnit(spec)), 0o644); err != nil {
|
||
t.Fatalf("write unit: %v", err)
|
||
}
|
||
return where, strings.TrimSuffix(mountUnit, ".mount") + ".automount"
|
||
}
|
||
|
||
func netReassertOps(t *testing.T, unitDir string, mounts []Mount) (*SudoHostOps, *recordingRunner) {
|
||
t.Helper()
|
||
rr := &recordingRunner{}
|
||
ops := NewSudoHostOps(SudoHostOpsConfig{
|
||
Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: t.TempDir(),
|
||
Host: &fakeHostReader{mounts: mounts}, Logger: quietLogger(),
|
||
})
|
||
return ops, rr
|
||
}
|
||
|
||
// An idle trigger (autofs at the mountpoint) must be re-armed with EXACTLY the granted verbs:
|
||
// `systemctl stop -- <unit>.automount` then `systemctl enable --now -- <unit>.automount`.
|
||
func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
|
||
if runtime.GOOS == "windows" {
|
||
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
|
||
}
|
||
unitDir := t.TempDir()
|
||
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
|
||
where, autoUnit := installNetUnitFile(t, unitDir, spec)
|
||
ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})
|
||
|
||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||
if len(results) != 1 || results[0].Action != NetReassertRearmed || results[0].Err != nil {
|
||
t.Fatalf("want one rearmed result, got %+v", results)
|
||
}
|
||
if results[0].Where != where || results[0].Name != "media" {
|
||
t.Fatalf("result identity wrong: %+v", results[0])
|
||
}
|
||
if len(rr.calls) != 2 {
|
||
t.Fatalf("want exactly stop + enable --now, got %d calls: %v", len(rr.calls), rr.calls)
|
||
}
|
||
stop, enable := strings.Join(rr.calls[0], " "), strings.Join(rr.calls[1], " ")
|
||
if !strings.Contains(stop, "systemctl stop -- "+autoUnit) {
|
||
t.Errorf("first call must be `systemctl stop -- %s`, got: %s", autoUnit, stop)
|
||
}
|
||
if !strings.Contains(enable, "systemctl enable --now -- "+autoUnit) {
|
||
t.Errorf("second call must be `systemctl enable --now -- %s`, got: %s", autoUnit, enable)
|
||
}
|
||
}
|
||
|
||
// An ACTIVE real mount must not be touched — stopping the automount of a live mount would churn it.
|
||
// (Red-proof companion: a naive always-rearm implementation fails this with 2 recorded calls.)
|
||
func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) {
|
||
if runtime.GOOS == "windows" {
|
||
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
|
||
}
|
||
unitDir := t.TempDir()
|
||
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
|
||
where, _ := installNetUnitFile(t, unitDir, spec)
|
||
ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "nfs4"}})
|
||
|
||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||
if len(results) != 1 || results[0].Action != NetReassertSkipActive {
|
||
t.Fatalf("want one skip-active result, got %+v", results)
|
||
}
|
||
if len(rr.calls) != 0 {
|
||
t.Fatalf("an actively-mounted share must trigger ZERO systemctl calls, got: %v", rr.calls)
|
||
}
|
||
}
|
||
|
||
// Neither a mount nor an armed trigger → skip (removed/orphan state, owned by add/remove flows).
|
||
func TestReassertNetworkAutomounts_NoTriggerSkips(t *testing.T) {
|
||
if runtime.GOOS == "windows" {
|
||
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
|
||
}
|
||
unitDir := t.TempDir()
|
||
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
|
||
installNetUnitFile(t, unitDir, spec)
|
||
ops, rr := netReassertOps(t, unitDir, nil) // nothing at the mountpoint
|
||
|
||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||
if len(results) != 1 || results[0].Action != NetReassertSkipNone {
|
||
t.Fatalf("want one skip-none result, got %+v", results)
|
||
}
|
||
if len(rr.calls) != 0 {
|
||
t.Fatalf("a unit with no trigger must not be acted on, got: %v", rr.calls)
|
||
}
|
||
}
|
||
|
||
// Idempotency: two consecutive passes over an idle trigger both succeed with the same action and no
|
||
// error (re-arming a fresh trigger is harmless — same end state).
|
||
func TestReassertNetworkAutomounts_Idempotent(t *testing.T) {
|
||
if runtime.GOOS == "windows" {
|
||
t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server")
|
||
}
|
||
unitDir := t.TempDir()
|
||
spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000}
|
||
where, _ := installNetUnitFile(t, unitDir, spec)
|
||
ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})
|
||
|
||
first := ops.ReassertNetworkAutomounts(context.Background())
|
||
second := ops.ReassertNetworkAutomounts(context.Background())
|
||
if first[0].Action != NetReassertRearmed || second[0].Action != NetReassertRearmed {
|
||
t.Fatalf("both passes must re-arm: first=%+v second=%+v", first, second)
|
||
}
|
||
if first[0].Err != nil || second[0].Err != nil {
|
||
t.Fatalf("idempotent passes must not error: first=%v second=%v", first[0].Err, second[0].Err)
|
||
}
|
||
if len(rr.calls) != 4 {
|
||
t.Fatalf("two passes = 2×(stop+enable), got %d: %v", len(rr.calls), rr.calls)
|
||
}
|
||
}
|
||
|
||
// Zero configured network units → empty pass, zero commands.
|
||
func TestReassertNetworkAutomounts_NoUnitsNoOp(t *testing.T) {
|
||
ops, rr := netReassertOps(t, t.TempDir(), nil)
|
||
if results := ops.ReassertNetworkAutomounts(context.Background()); len(results) != 0 {
|
||
t.Fatalf("no units must yield no results, got %+v", results)
|
||
}
|
||
if len(rr.calls) != 0 {
|
||
t.Fatalf("no units must construct zero commands, got: %v", rr.calls)
|
||
}
|
||
}
|