912f7c127e
gates / gates (push) Successful in 27s
- internal place: the record /var/lib/felhom-agent/internal-place.json -> mounted at /mnt/belso by fs-UUID like an enrolled drive (startup + 20 s tick), kind "internal" in GET /disks; never takes a household drive's /mnt/belso; eject/decommission/format refused (dm on the system disk). - configs/felhom-internal-place: the ruled size rule and the only lvcreate/mkfs of a system-disk volume (installer + the new signed op add_internal_place, operator key, no parameters). - partitions listed per free disk (empty / filesystem / in_use); a data-bearing part's wipe id binds disk id + part number + fs-UUID. - felhom-mkfs-guarded: Felhom's own sibling drive no longer blocks formatting the free part; an unlistable disk refuses. Tests red-proved (script mutations, Go mutations, the old wrapper measured against the new tests). Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
113 lines
3.9 KiB
Go
113 lines
3.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The data-bearing partition path binds the disk id + the part number + the fs-UUID (felhom.eu architecture/16 §6,
|
|
// agent v0.158.0). The wipe re-resolve (localapi antiRetargetResolveExpect) formats only when resolve(id) succeeds AND
|
|
// DeviceDurableID(that device) == id — the round trip these tests pin, in the three ways a target can move.
|
|
|
|
type partFixture struct{ base, root string }
|
|
|
|
func newPartFixture(t *testing.T) partFixture {
|
|
t.Helper()
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("symlink fixtures run on Linux")
|
|
}
|
|
f := partFixture{base: t.TempDir()}
|
|
f.root = filepath.Join(f.base, "disk")
|
|
for _, d := range []string{"sdb", "sdb1", "sdb2", "sdc2"} {
|
|
if err := os.WriteFile(filepath.Join(f.base, d), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
os.MkdirAll(filepath.Join(f.root, "by-id"), 0o755)
|
|
os.MkdirAll(filepath.Join(f.root, "by-uuid"), 0o755)
|
|
withDevDiskRoot(t, f.root)
|
|
return f
|
|
}
|
|
|
|
func (f partFixture) link(t *testing.T, sub, name, dev string) {
|
|
t.Helper()
|
|
p := filepath.Join(f.root, sub, name)
|
|
os.Remove(p)
|
|
if err := os.Symlink(filepath.Join(f.base, dev), p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (f partFixture) dev(name string) string { return filepath.Join(f.base, name) }
|
|
|
|
func (f partFixture) standard(t *testing.T) {
|
|
f.link(t, "by-id", "wwn-0x5000aaaa", "sdb")
|
|
f.link(t, "by-id", "ata-TOSHIBA_MQ04_65NOP-part1", "sdb1")
|
|
f.link(t, "by-id", "wwn-0x5000aaaa-part1", "sdb1")
|
|
f.link(t, "by-id", "wwn-0x5000aaaa-part2", "sdb2")
|
|
f.link(t, "by-uuid", "1111-films", "sdb2")
|
|
}
|
|
|
|
func roundTrip(id string) (string, bool) {
|
|
dev, err := ResolveDurableDevice(id)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
again, err := DeviceDurableID(dev)
|
|
return dev, err == nil && again == id
|
|
}
|
|
|
|
func TestPartitionWipeID_BindsDiskPartAndFilesystem(t *testing.T) {
|
|
f := newPartFixture(t)
|
|
f.standard(t)
|
|
id, err := DeviceDurableID(f.dev("sdb2"))
|
|
if err != nil || id != "byid:wwn-0x5000aaaa-part2+fsuuid:1111-films" {
|
|
t.Fatalf("a part with a filesystem: want disk id + part + fs-UUID, got %q %v", id, err)
|
|
}
|
|
if dev, ok := roundTrip(id); !ok || dev != f.dev("sdb2") {
|
|
t.Fatalf("the unchanged part must round-trip, got %q %v", dev, ok)
|
|
}
|
|
// A blank part (no filesystem) and a whole disk keep the plain by-id binding.
|
|
if id1, _ := DeviceDurableID(f.dev("sdb1")); id1 != "byid:wwn-0x5000aaaa-part1" {
|
|
t.Fatalf("a blank part: want the plain part link, got %q", id1)
|
|
}
|
|
if idDisk, _ := DeviceDurableID(f.dev("sdb")); idDisk != "byid:wwn-0x5000aaaa" {
|
|
t.Fatalf("a whole disk keeps today's id, got %q", idDisk)
|
|
}
|
|
}
|
|
|
|
func TestPartitionWipeID_ReformattedPartRefuses(t *testing.T) {
|
|
f := newPartFixture(t)
|
|
f.standard(t)
|
|
id, _ := DeviceDurableID(f.dev("sdb2"))
|
|
os.Remove(filepath.Join(f.root, "by-uuid", "1111-films"))
|
|
f.link(t, "by-uuid", "2222-other", "sdb2") // someone reformatted the part since the household confirmed
|
|
if _, ok := roundTrip(id); ok {
|
|
t.Fatalf("a reformatted part must not round-trip (the wipe would hit a different filesystem)")
|
|
}
|
|
}
|
|
|
|
func TestPartitionWipeID_LinkMovedToAnotherDiskRefuses(t *testing.T) {
|
|
f := newPartFixture(t)
|
|
f.standard(t)
|
|
id, _ := DeviceDurableID(f.dev("sdb2"))
|
|
f.link(t, "by-id", "wwn-0x5000aaaa-part2", "sdc2") // a different node took the part link; the fs is still sdb2
|
|
if _, err := ResolveDurableDevice(id); err == nil || !strings.Contains(err.Error(), "different devices") {
|
|
t.Fatalf("the part link and its filesystem on different devices must refuse, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPartitionWipeID_RefusesAForgedCompound(t *testing.T) {
|
|
f := newPartFixture(t)
|
|
f.standard(t)
|
|
f.link(t, "by-uuid", "3333-whole", "sdb") // a filesystem straight on the whole disk: both halves name sdb
|
|
for _, id := range []string{"byid:wwn-0x5000aaaa+fsuuid:3333-whole", "byid:wwn-0x5000aaaa-part2+fsuuid:../x"} {
|
|
if _, err := ResolveDurableDevice(id); err == nil {
|
|
t.Fatalf("%q must be refused", id)
|
|
}
|
|
}
|
|
}
|