controller v0.68.0: storage lifecycle on intermediary model (H2/H3/M1/M3 + boot-id)
H2 decommission UI button (migrate / anyway); H3 one-click re-enroll of a
decommissioned drive; M1 default reassignment (auto-promote + block-if-none);
M3 migrate re-asserts 2775 setgid on userdata dirs; deterministic guest-reboot
recreate via agent boot_id (replaces the timed sample). Fixes the {path}/{where}
H1 JS bug. Non-hollow tests + companions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
|
||||
func TestAgentWhere(t *testing.T) {
|
||||
@@ -20,6 +21,64 @@ func TestAgentWhere(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestShouldRecreateOnBoot pins the deterministic boot-id recreate decision.
|
||||
//
|
||||
// COMPANION GUARD: the OLD timed-sample (`stackStartedRecently || State∈{exited,restarting,unhealthy}`)
|
||||
// MISSED a healthy-but-stale Running app sampled minutes after boot — the first case below would be
|
||||
// `false` under it. The boot-id path recreates it (it came back on this boot, drive present). It still
|
||||
// respects a cleanly user-Stopped app and never touches absent-drive / SSD / not-deployed apps.
|
||||
func TestShouldRecreateOnBoot(t *testing.T) {
|
||||
present := map[string]bool{"/mnt/felhom-drives/felhom-flash": true}
|
||||
cases := []struct {
|
||||
name string
|
||||
deployed bool
|
||||
hdd string
|
||||
state stacks.ContainerState
|
||||
want bool
|
||||
}{
|
||||
{"healthy-but-stale (old sample MISSED this)", true, "/mnt/felhom-drives/felhom-flash", stacks.StateRunning, true},
|
||||
{"exited", true, "/mnt/felhom-drives/felhom-flash", stacks.StateExited, true},
|
||||
{"unhealthy", true, "/mnt/felhom-drives/felhom-flash", stacks.StateUnhealthy, true},
|
||||
{"user-stopped (respected)", true, "/mnt/felhom-drives/felhom-flash", stacks.StateStopped, false},
|
||||
{"not-deployed", true, "/mnt/felhom-drives/felhom-flash", stacks.StateNotDeployed, false},
|
||||
{"drive absent (gate handles)", true, "/mnt/felhom-drives/felhom-usb", stacks.StateRunning, false},
|
||||
{"SSD path never", true, "/mnt/sys_drive/felhom-data", stacks.StateRunning, false},
|
||||
{"app.yaml not deployed", false, "/mnt/felhom-drives/felhom-flash", stacks.StateRunning, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := shouldRecreateOnBoot(c.deployed, c.hdd, c.state, present); got != c.want {
|
||||
t.Errorf("%s: shouldRecreateOnBoot = %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultPromotionTarget pins M1 (never leave zero default).
|
||||
//
|
||||
// COMPANION GUARD: the pre-fix decommission blanked the default and promoted nothing — equivalent to this
|
||||
// always returning ("", false). The "promote another" + "block when only drive" cases below fail that.
|
||||
func TestDefaultPromotionTarget(t *testing.T) {
|
||||
flash := "/mnt/felhom-drives/felhom-flash"
|
||||
usb := "/mnt/felhom-drives/felhom-usb"
|
||||
// decommissioning a NON-default → no action.
|
||||
paths := []settings.StoragePath{{Path: flash, IsDefault: true, Schedulable: true}, {Path: usb, Schedulable: true}}
|
||||
if tgt, blk := defaultPromotionTarget(paths, usb, ""); tgt != "" || blk {
|
||||
t.Fatalf("non-default decommission: got (%q,%v), want (\"\",false)", tgt, blk)
|
||||
}
|
||||
// decommissioning the DEFAULT with another usable → promote it.
|
||||
if tgt, blk := defaultPromotionTarget(paths, flash, ""); tgt != usb || blk {
|
||||
t.Fatalf("default decommission: got (%q,%v), want (%q,false)", tgt, blk, usb)
|
||||
}
|
||||
// prefer the migrate target when valid.
|
||||
if tgt, _ := defaultPromotionTarget(paths, flash, usb); tgt != usb {
|
||||
t.Fatalf("should prefer migrate target %q, got %q", usb, tgt)
|
||||
}
|
||||
// the ONLY usable drive (other is decommissioned) → BLOCK.
|
||||
only := []settings.StoragePath{{Path: flash, IsDefault: true, Schedulable: true}, {Path: usb, Decommissioned: true}}
|
||||
if tgt, blk := defaultPromotionTarget(only, flash, ""); tgt != "" || !blk {
|
||||
t.Fatalf("only-drive decommission: got (%q,%v), want (\"\",true)", tgt, blk)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStablePathForName(t *testing.T) {
|
||||
if got := stablePathForName("felhom-usb"); got != "/mnt/felhom-drives/felhom-usb" {
|
||||
t.Errorf("stablePathForName = %q", got)
|
||||
|
||||
Reference in New Issue
Block a user