Files
felhom-agent/internal/storage/partition_durable_test.go
T
admin 912f7c127e
gates / gates (push) Successful in 27s
The internal data place, partitions as places, add_internal_place (felhom.eu architecture/16 §5-§7, §9; build B)
- 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
2026-10-16 03:55:47 +02:00

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)
}
}
}