agent v0.85.0 WIP: F12/F11/F10/F9/F2/F1 boot-recovery plane + appliance self-heal (pre-build)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
This commit is contained in:
@@ -9,23 +9,23 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The §8 decision table, encoded exactly (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 1).
|
||||
func TestNetReassertAction_Table(t *testing.T) {
|
||||
// The §8 decision table, encoded exactly (CAMPAIGN-3 F11: fstype-driven, automount state IGNORED).
|
||||
func TestNetReassertClassify_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}, //
|
||||
{"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
|
||||
{"", NetReassertRearmed}, // F10: a failed/disarmed automount leaves NO mount entry — re-arm
|
||||
{"ext4", NetReassertSkipForeign}, // a foreign local fs at the path is not ours to churn
|
||||
{"tmpfs", NetReassertSkipForeign}, //
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := netReassertAction(c.fstype); got != c.want {
|
||||
t.Errorf("netReassertAction(%q) = %q, want %q", c.fstype, got, c.want)
|
||||
if got := netReassertClassify(c.fstype); got != c.want {
|
||||
t.Errorf("netReassertClassify(%q) = %q, want %q", c.fstype, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,18 +45,26 @@ func installNetUnitFile(t *testing.T, unitDir string, spec NetworkMountSpec) (wh
|
||||
return where, strings.TrimSuffix(mountUnit, ".mount") + ".automount"
|
||||
}
|
||||
|
||||
func netReassertOps(t *testing.T, unitDir string, mounts []Mount) (*SudoHostOps, *recordingRunner) {
|
||||
// netReassertOps builds ops with a hermetic unitFailed seam (default: nothing failed — no shelling out
|
||||
// to a real systemctl is-failed). failedUnits, if set, marks specific unit names as failed.
|
||||
func netReassertOps(t *testing.T, unitDir string, mounts []Mount, failedUnits ...string) (*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(),
|
||||
})
|
||||
failed := map[string]bool{}
|
||||
for _, u := range failedUnits {
|
||||
failed[u] = true
|
||||
}
|
||||
ops.unitFailed = func(_ context.Context, unit string) bool { return failed[unit] }
|
||||
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`.
|
||||
// `systemctl stop -- <unit>.automount` then `systemctl enable --now -- <unit>.automount`. No
|
||||
// reset-failed when nothing is failed.
|
||||
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")
|
||||
@@ -70,11 +78,8 @@ func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
|
||||
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)
|
||||
t.Fatalf("want exactly stop + enable --now (no reset-failed when clean), 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) {
|
||||
@@ -85,8 +90,44 @@ func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// F10: a failed/start-limited automount (the campaign's unexport→idle-timeout→access×5 sequence leaves
|
||||
// NO mount entry, fstype "") must be reset-failed FIRST, then re-armed — verdict reset-failed+rearmed.
|
||||
// Companion to the campaign's reboots #2–#4: the pre-0.85 code returned skip-none here and left it dead.
|
||||
func TestReassertNetworkAutomounts_ResetsFailedThenRearms(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)
|
||||
// Nothing mounted (fstype "" — a start-limited automount), and the automount unit is in failed state.
|
||||
ops, rr := netReassertOps(t, unitDir, nil, autoUnit)
|
||||
|
||||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||||
if len(results) != 1 || results[0].Action != NetReassertResetRearmed || results[0].Err != nil {
|
||||
t.Fatalf("want one reset-failed+rearmed result, got %+v", results)
|
||||
}
|
||||
// reset-failed <automount>, then stop + enable --now.
|
||||
var sawReset, sawEnable bool
|
||||
for _, c := range rr.calls {
|
||||
j := strings.Join(c, " ")
|
||||
if strings.Contains(j, "reset-failed -- "+autoUnit) {
|
||||
sawReset = true
|
||||
}
|
||||
if strings.Contains(j, "enable --now -- "+autoUnit) {
|
||||
sawEnable = true
|
||||
}
|
||||
}
|
||||
if !sawReset {
|
||||
t.Errorf("a failed unit must be reset-failed before re-arm (F10); calls: %v", rr.calls)
|
||||
}
|
||||
if !sawEnable {
|
||||
t.Errorf("the trigger must still be re-armed after reset-failed; calls: %v", rr.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.)
|
||||
// (Red-proof companion: a naive always-rearm implementation fails this with 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")
|
||||
@@ -105,27 +146,81 @@ func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Neither a mount nor an armed trigger → skip (removed/orphan state, owned by add/remove flows).
|
||||
func TestReassertNetworkAutomounts_NoTriggerSkips(t *testing.T) {
|
||||
// The automount unit's own state is IGNORED (F11 red-proof): even though a mutant that consulted
|
||||
// `systemctl is-active <automount>` would see an armed trigger as "active" and skip it, the fstype at
|
||||
// the path is autofs (idle) so the correct code RE-ARMS. Encoded as: an idle trigger re-arms regardless
|
||||
// of failed/armed unit state — the decision is fstype only.
|
||||
func TestReassertNetworkAutomounts_IgnoresAutomountUnitState(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
|
||||
|
||||
where, _ := installNetUnitFile(t, unitDir, spec)
|
||||
// fstype autofs (idle-armed): the correct decision is re-arm, NOT skip — a state-of-the-automount
|
||||
// check would mis-skip an armed trigger (which always reports "active").
|
||||
ops, _ := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}})
|
||||
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)
|
||||
if len(results) != 1 || (results[0].Action != NetReassertRearmed && results[0].Action != NetReassertResetRearmed) {
|
||||
t.Fatalf("an idle (autofs) trigger must re-arm regardless of automount unit state, got %+v", results)
|
||||
}
|
||||
}
|
||||
|
||||
// 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).
|
||||
// A foreign filesystem at the path (ext4/tmpfs) is skipped — re-arming over it would fail (busy).
|
||||
func TestReassertNetworkAutomounts_ForeignFSSkipped(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: "ext4"}})
|
||||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||||
if len(results) != 1 || results[0].Action != NetReassertSkipForeign {
|
||||
t.Fatalf("want one skip-foreign result, got %+v", results)
|
||||
}
|
||||
if len(rr.calls) != 0 {
|
||||
t.Fatalf("a foreign fs at the path must trigger zero systemctl calls, got: %v", rr.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// F9: the pass returns exactly one verdict per installed unit — including a failed one. An
|
||||
// empty-looking sweep over N shares is structurally impossible (the campaign's silent zero-line sweep).
|
||||
func TestReassertNetworkAutomounts_VerdictPerUnit(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()
|
||||
specs := []NetworkMountSpec{
|
||||
{Name: "alpha", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/a", MappedUID: 1000, MappedGID: 1000},
|
||||
{Name: "beta", Protocol: ProtocolNFS, Server: "10.0.0.6", Export: "/srv/b", MappedUID: 1000, MappedGID: 1000},
|
||||
{Name: "gamma", Protocol: ProtocolNFS, Server: "10.0.0.7", Export: "/srv/c", MappedUID: 1000, MappedGID: 1000},
|
||||
}
|
||||
for _, s := range specs {
|
||||
installNetUnitFile(t, unitDir, s)
|
||||
}
|
||||
// alpha active (skip-active), beta idle (rearm), gamma failed-and-unmounted (reset-failed+rearm).
|
||||
gammaAuto := func() string {
|
||||
u, _ := UnitNameForMount(specs[2].Where())
|
||||
return strings.TrimSuffix(u, ".mount") + ".automount"
|
||||
}()
|
||||
ops, _ := netReassertOps(t, unitDir, []Mount{
|
||||
{MountPoint: specs[0].Where(), FSType: "nfs4"},
|
||||
{MountPoint: specs[1].Where(), FSType: "autofs"},
|
||||
}, gammaAuto)
|
||||
|
||||
results := ops.ReassertNetworkAutomounts(context.Background())
|
||||
if len(results) != len(specs) {
|
||||
t.Fatalf("verdict count must equal unit count (%d), got %d: %+v", len(specs), len(results), results)
|
||||
}
|
||||
for _, r := range results {
|
||||
if r.Action == "" {
|
||||
t.Errorf("every share must carry a verdict (F9), got empty for %s", r.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Idempotency: two consecutive passes over an idle trigger both re-arm with the same action, no error.
|
||||
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")
|
||||
@@ -140,9 +235,6 @@ func TestReassertNetworkAutomounts_Idempotent(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user