controllerswap: F1 verify hardening — reject crash-looping no-healthcheck image v0.47.0
controllerHealthy reads RestartCount (running&&rc>0 -> not ok) + signals needsDwell for no-healthcheck; verify requires verifyDwell(=3) consecutive ok polls for a no-healthcheck image (real healthcheck trusted immediately). Closes the F1 hole (alpine crash-loop passed the point-in-time check). Red-proof + dwell + real-image tests. No sudoers/orchestration change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
This commit is contained in:
@@ -51,7 +51,11 @@ func (f *fakeDiskOps) Unmount(_ context.Context, where string) error {
|
||||
f.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiskOps) formatted() []string { f.mu.Lock(); defer f.mu.Unlock(); return append([]string(nil), f.formatCalls...) }
|
||||
func (f *fakeDiskOps) formatted() []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return append([]string(nil), f.formatCalls...)
|
||||
}
|
||||
|
||||
type fakeGate struct {
|
||||
mu sync.Mutex
|
||||
@@ -80,11 +84,11 @@ func (f fakeGuestList) ListLXC(context.Context) ([]proxmox.Guest, error) { retur
|
||||
// a different whole-disk (e.g. /dev/sdb1) classifies as user-data without touching the real host.
|
||||
type fakeHostReader struct{ mounts []storage.Mount }
|
||||
|
||||
func (f fakeHostReader) Mounts() ([]storage.Mount, error) { return f.mounts, nil }
|
||||
func (f fakeHostReader) ResolveUUID(string) (string, bool) { return "", false }
|
||||
func (f fakeHostReader) DeviceExists(string) bool { return true }
|
||||
func (f fakeHostReader) Rotational(string) (bool, bool) { return false, false }
|
||||
func (f fakeHostReader) Removable(string) (bool, bool) { return false, false }
|
||||
func (f fakeHostReader) Mounts() ([]storage.Mount, error) { return f.mounts, nil }
|
||||
func (f fakeHostReader) ResolveUUID(string) (string, bool) { return "", false }
|
||||
func (f fakeHostReader) DeviceExists(string) bool { return true }
|
||||
func (f fakeHostReader) Rotational(string) (bool, bool) { return false, false }
|
||||
func (f fakeHostReader) Removable(string) (bool, bool) { return false, false }
|
||||
|
||||
// sysOnSDA is the default system-disk fixture (root on /dev/sda) used by the disk-server test helpers.
|
||||
func sysOnSDA() fakeHostReader {
|
||||
@@ -345,7 +349,7 @@ func TestEject_RoleGated(t *testing.T) {
|
||||
sv := fakeStorage{targets: []hub.StorageTarget{
|
||||
{Name: "bulk", Type: hub.StorageTypeUSB, BackingDevice: "/dev/sdb1", MountPath: "/mnt/bulk"}, // user-data
|
||||
{Name: "local", Type: "local", MountPath: "/var/lib/vz"}, // system (builtin dir)
|
||||
{Name: "felhom-pbs", Type: hub.StorageTypePBS, MountPath: "/mnt/pbs"}, // backup (PBS)
|
||||
{Name: "felhom-pbs", Type: hub.StorageTypePBS, MountPath: "/mnt/pbs"}, // backup (PBS)
|
||||
}}
|
||||
|
||||
// system mount → refused, no Unmount.
|
||||
@@ -438,7 +442,7 @@ func (f *fakeGuestAttacher) AttachDrive(_ context.Context, _ int, where string)
|
||||
return StablePathForRaw(where), nil
|
||||
}
|
||||
func (f *fakeGuestAttacher) GuestSeesMount(_ context.Context, _ int, _ string) bool { return true }
|
||||
func (f *fakeGuestAttacher) GuestBootID(_ context.Context, _ int) string { return "boot-1" }
|
||||
func (f *fakeGuestAttacher) GuestBootID(_ context.Context, _ int) string { return "boot-1" }
|
||||
func (f *fakeGuestAttacher) attachDriveCount() int {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
@@ -480,7 +484,11 @@ func (f *fakeGuestAttacher) RebootGuest(_ context.Context, vmid int) error {
|
||||
f.reboots = append(f.reboots, vmid)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeGuestAttacher) rebootCount() int { f.mu.Lock(); defer f.mu.Unlock(); return len(f.reboots) }
|
||||
func (f *fakeGuestAttacher) rebootCount() int {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return len(f.reboots)
|
||||
}
|
||||
|
||||
func newAttachServer(t *testing.T, ga GuestAttacher, mounts map[int]map[string]string) http.Handler {
|
||||
t.Helper()
|
||||
@@ -646,8 +654,10 @@ func (f *fakeGuestsCfg) GuestConfig(_ context.Context, vmid int) (proxmox.GuestC
|
||||
}
|
||||
return proxmox.GuestConfig{Extra: extra}, nil
|
||||
}
|
||||
func (f *fakeGuestsCfg) Snapshot(context.Context, int, string, string) (string, error) { return "", nil }
|
||||
func (f *fakeGuestsCfg) Rollback(context.Context, int, string) (string, error) { return "", nil }
|
||||
func (f *fakeGuestsCfg) Snapshot(context.Context, int, string, string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (f *fakeGuestsCfg) Rollback(context.Context, int, string) (string, error) { return "", nil }
|
||||
func (f *fakeGuestsCfg) WaitTask(context.Context, string, proxmox.WaitOptions) (proxmox.TaskStatus, error) {
|
||||
return proxmox.TaskStatus{ExitStatus: "OK"}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user