Files
felhom-agent/internal/storage/registry_known_test.go
T
admin 91f6a26490 v0.55.0: raw-device discovery + registry-sourced drive tracking (Impl-2a)
GET /disks/candidates enumerates host disks the Impl-1 unclaimed filter proves
free (init/attach split). RegistryKnownTargets sources the watchdog's known-drive
set from the intent registry + Felhom .mount units (not Observe/PVE storages) —
decouples drive health from PVE storage (closes the registry-only false-detach
class); Observe kept for real PVE storages + a deduped /disks union. Idempotent
existing-drive migration at start. Tests + red-proof (Observe misses a
registry-only drive; registry provider tracks it). go build/vet/test clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:33:49 +02:00

99 lines
4.3 KiB
Go

package storage
import (
"context"
"os"
"path/filepath"
"testing"
)
// fakeIntent is a minimal IntentReader for the registry tests.
type fakeIntent struct{ m map[string]DriveIntent }
func (f *fakeIntent) Get(durableID string) DriveIntent { return f.m[durableID] }
func writeUnit(t *testing.T, dir, name, content string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
t.Fatalf("write unit %s: %v", name, err)
}
}
func TestRegistryKnownTargets(t *testing.T) {
dir := t.TempDir()
writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"}))
writeUnit(t, dir, "mnt-b.mount", renderMountUnit(MountSpec{Name: "felhom-b", UUID: "UUID-B", Where: "/mnt/felhom-b", FSType: "ext4"}))
writeUnit(t, dir, "other.mount", "[Mount]\nWhat=/dev/x\nWhere=/mnt/x\n") // not one of ours → ignored
// felhom-a enrolled, felhom-b has NO intent (new).
intent := &fakeIntent{m: map[string]DriveIntent{"uuid:UUID-A": IntentEnrolled}}
r := NewRegistryKnownTargets(dir, intent, quietLogger())
got, err := r.Known(context.Background())
if err != nil {
t.Fatalf("Known: %v", err)
}
if len(got) != 1 || got[0].Name != "felhom-a" {
t.Fatalf("want only felhom-a (enrolled); new felhom-b excluded, non-felhom ignored — got %+v", got)
}
kt := got[0]
if kt.DurableID != "uuid:UUID-A" || kt.UUID != "UUID-A" || !kt.MountBacked || kt.MountPath != "/mnt/felhom-a" {
t.Fatalf("KnownTarget fields wrong: %+v", kt)
}
// An EJECTED drive is still tracked (known) — the watchdog's IntentReader gate prevents remount.
intent.m["uuid:UUID-B"] = IntentEjected
got2, _ := r.Known(context.Background())
if len(got2) != 2 {
t.Fatalf("ejected drive must still be a Known target: %+v", got2)
}
}
// The decoupling red-proof: with NO PVE storage for a drive, the OLD Observe-based Known() misses it,
// while the registry+units provider tracks it. This is exactly the 3b-fix class the decoupling closes.
func TestRegistryVsObserve_RedProof(t *testing.T) {
dir := t.TempDir()
writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"}))
intent := &fakeIntent{m: map[string]DriveIntent{"uuid:UUID-A": IntentEnrolled}}
// OLD path: Observer with an API that has NO storages → Known() is empty (drive invisible).
obs := NewObserver(&fakeStorageAPI{node: "n"}, &fakeHostReader{}, nil, quietLogger())
oldKnown, _ := obs.Known(context.Background())
if len(oldKnown) != 0 {
t.Fatalf("precondition: Observe-based Known should be empty with no PVE storage, got %+v", oldKnown)
}
// NEW path: the registry provider tracks the drive from unit + intent, no PVE storage needed.
newKnown, _ := NewRegistryKnownTargets(dir, intent, quietLogger()).Known(context.Background())
if len(newKnown) != 1 || newKnown[0].Name != "felhom-a" {
t.Fatalf("registry provider must track the registry-only drive: %+v", newKnown)
}
}
func TestReconcileExistingDrives_Idempotent(t *testing.T) {
dir := t.TempDir()
writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"}))
store, err := OpenIntentStore(filepath.Join(t.TempDir(), "intents.json"))
if err != nil {
t.Fatalf("intent store: %v", err)
}
mounts := []Mount{{Device: "/dev/disk/by-uuid/UUID-A", MountPoint: "/mnt/felhom-a"}}
// First reconcile: an un-recorded mounted drive → becomes enrolled.
ReconcileExistingDrives(dir, mounts, store, quietLogger())
if store.Get("uuid:UUID-A") != IntentEnrolled {
t.Fatalf("reconcile must record the mounted drive enrolled, got %q", store.Get("uuid:UUID-A"))
}
// Idempotent: a re-run doesn't change/downgrade it.
ReconcileExistingDrives(dir, mounts, store, quietLogger())
if store.Get("uuid:UUID-A") != IntentEnrolled {
t.Fatalf("re-run must be a no-op, got %q", store.Get("uuid:UUID-A"))
}
// An UNMOUNTED drive is NOT auto-enrolled by the migration.
writeUnit(t, dir, "mnt-c.mount", renderMountUnit(MountSpec{Name: "felhom-c", UUID: "UUID-C", Where: "/mnt/felhom-c", FSType: "ext4"}))
ReconcileExistingDrives(dir, mounts, store, quietLogger())
if store.Get("uuid:UUID-C") != IntentNew {
t.Fatalf("unmounted drive must NOT be migrated, got %q", store.Get("uuid:UUID-C"))
}
}