package storage import "testing" // ── R-220 — A MOUNT FELHOM ITSELF MADE IS NOT "SOMETHING ELSE" ────────────────────────────────── // // Enrolment mounts a drive twice: at `/mnt/felhom-drives/` and at the raw `/mnt/` it // creates on the host. The host survives a guest rebuild; the controller's registry does not. So after // a rebuild the customer's own drives read as claimed-by-something-else, `attach` came back empty, and // the refusal told them to pick from the empty list. Measured three times live. // // The fence: a disk genuinely in use elsewhere must STILL be refused. These assert both directions. // ── SCENARIO E — the customer's own drive is offered again after a rebuild ─────────────────────── // // RED-PROOF: drop `&& !f.felhomOwnedMounts[n.mountpoint]` from classifyClaim — the pre-R-220 check — // and this FAILS with the drive refused and the list empty again. func TestClassifyClaim_R220_FelhomsOwnRawMountIsNotForeign(t *testing.T) { f := claimFacts{ device: "/dev/sdb", wholeDisk: "/dev/sdb", wholeDiskOK: true, nodes: []claimNode{{name: "sdb", fstype: "ext4", mountpoint: "/mnt/adatok"}}, // corroborated: the SAME device is also mounted at the managed path felhomOwnedMounts: map[string]bool{"/mnt/adatok": true}, } unclaimed, reason := classifyClaim(f) if !unclaimed { t.Fatalf("R-220 RETURNED: the customer's own drive is refused after a rebuild — %q", reason) } } // ── SCENARIO F — a genuinely foreign mount is STILL refused ────────────────────────────────────── // // RED-PROOF: over-widen the fix to exempt any /mnt/* path (or to skip the mountpoint check entirely) // and this FAILS — a disk another system is using would be offered for formatting. func TestClassifyClaim_R220_ForeignMountIsStillRefused(t *testing.T) { for _, mp := range []string{"/srv/data", "/media/photos", "/mnt/someone-elses-disk", "/var/lib/other"} { f := claimFacts{ device: "/dev/sdb", wholeDisk: "/dev/sdb", wholeDiskOK: true, nodes: []claimNode{{name: "sdb", fstype: "ext4", mountpoint: mp}}, felhomOwnedMounts: nil, // nothing corroborated it as ours } unclaimed, reason := classifyClaim(f) if unclaimed { t.Fatalf("THE FENCE BROKE: a disk mounted at %s was offered for formatting", mp) } if reason == "" { t.Fatalf("a refusal must carry a reason (%s)", mp) } } } // The corroboration itself: it must require BOTH mounts of the SAME device, and fail safe. func TestFelhomOwnedMounts_RequiresTheManagedCounterpart(t *testing.T) { nodes := []claimNode{{name: "sdb"}} t.Run("both mounts present -> the raw one is ours", func(t *testing.T) { src := func() ([][2]string, error) { return [][2]string{ {"/dev/sdb", "/mnt/adatok"}, {"/dev/sdb", "/mnt/felhom-drives/adatok"}, }, nil } got := felhomOwnedMounts("/dev/sdb", nodes, src) if !got["/mnt/adatok"] { t.Fatal("the raw enrolment mount was not recognised as Felhom's own") } }) t.Run("only the raw mount -> corroborates NOTHING", func(t *testing.T) { src := func() ([][2]string, error) { return [][2]string{{"/dev/sdb", "/mnt/adatok"}}, nil } if got := felhomOwnedMounts("/dev/sdb", nodes, src); len(got) != 0 { t.Fatalf("a lone /mnt/ mount must corroborate nothing, got %v", got) } }) t.Run("a DIFFERENT device under the managed path does not vouch for this one", func(t *testing.T) { src := func() ([][2]string, error) { return [][2]string{ {"/dev/sdb", "/srv/data"}, {"/dev/sdc", "/mnt/felhom-drives/mentes"}, // someone else's, not sdb's }, nil } if got := felhomOwnedMounts("/dev/sdb", nodes, src); got["/srv/data"] { t.Fatal("another device's managed mount vouched for a foreign one") } }) t.Run("an unreadable mount table corroborates NOTHING (fail-safe)", func(t *testing.T) { src := func() ([][2]string, error) { return nil, errRead } if got := felhomOwnedMounts("/dev/sdb", nodes, src); len(got) != 0 { t.Fatalf("an unreadable mount table must corroborate nothing, got %v", got) } }) } var errRead = errNoMountTable{} type errNoMountTable struct{} func (errNoMountTable) Error() string { return "mount table unreadable" }