Files
felhom-controller/controller/internal/web/partitions_test.go
T
admin 36da4089ca
gates / gates (push) Successful in 45s
v0.318.0: a part is offered for formatting only while nothing on its disk is mounted (agent 0.158.1 formattable)
The agent's root mkfs wrapper (security review of agent 0.158.0) refuses any mounted node and trusts no sibling claim; the
wizard now matches it. A free filesystem part of an in-use disk stays offered for use. Red-proved.

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
2026-10-16 04:05:09 +02:00

105 lines
5.0 KiB
Go

package web
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
// Partitions as places (felhom.eu architecture/16 §6, v0.318.0): the free part of a disk whose other part is a
// registered drive stays on offer; the whole disk never does — in the list AND on the server.
const partMounts = `/dev/mapper/pve-root /mnt/felhom-drives ext4 rw,relatime 0 0
/dev/sdb1 /mnt/felhom-drives/hdd_1 ext4 rw,relatime 0 0
/dev/mapper/pve-vm--9201--disk--1 /mnt/sys_drive ext4 rw,relatime 0 0
`
func diskWithParts() agentapi.DiskCandidate {
return agentapi.DiskCandidate{Device: "/dev/sdb", MountSource: "/dev/sdb1", FSType: "ext4", DataBearing: true,
Partitions: []agentapi.CandidatePartition{
{Device: "/dev/sdb1", Number: 1, FSType: "ext4", State: agentapi.PartInUse, MountedAt: "/mnt/felhom-drives/hdd_1"},
{Device: "/dev/sdb2", Number: 2, State: agentapi.PartEmpty},
{Device: "/dev/sdb3", Number: 3, FSType: "ext4", State: agentapi.PartFilesystem, Mountable: true, DurableID: "uuid:3333"},
}}
}
// A disk whose one part is a registered drive: its free FILESYSTEM part stays offered for USE (attach erases nothing);
// nothing of it is offered for formatting — the agent marks its parts not Formattable (a node is mounted) and its root
// wrapper would refuse (agent 0.158.1, security review). Positive control: an unmounted disk's formattable part.
func TestPartitions_InUseDiskOffersItsFreePartsForUseOnly(t *testing.T) {
reg := map[string]bool{"/mnt/felhom-drives/hdd_1": true}
d := diskWithParts()
got := dropRegisteredDrives(agentapi.CandidatesResult{Initialize: []agentapi.DiskCandidate{d}, Attach: []agentapi.DiskCandidate{d}}, partMounts, reg)
if len(got.Initialize) != 0 {
t.Fatalf("nothing of a disk with a mounted part may be offered for formatting, got %+v", got.Initialize)
}
if len(got.Attach) != 1 || !got.Attach[0].WholeDiskInUse || len(got.Attach[0].Partitions) != 1 || got.Attach[0].Partitions[0].Device != "/dev/sdb3" {
t.Fatalf("the free filesystem part must stay offered for use (and only it), got %+v", got.Attach)
}
free := agentapi.DiskCandidate{Device: "/dev/sdc", Partitions: []agentapi.CandidatePartition{{Device: "/dev/sdc1", State: agentapi.PartEmpty, Formattable: true}}}
got = dropRegisteredDrives(agentapi.CandidatesResult{Initialize: []agentapi.DiskCandidate{free}}, partMounts, reg)
if len(got.Initialize) != 1 || len(got.Initialize[0].Partitions) != 1 {
t.Fatalf("positive control: an unmounted disk's formattable part stays offered, got %+v", got.Initialize)
}
}
// R-542 unchanged: a disk with nothing free is still dropped.
func TestPartitions_InUseDiskWithNoFreePartIsDropped(t *testing.T) {
d := diskWithParts()
d.Partitions = d.Partitions[:1]
got := dropRegisteredDrives(agentapi.CandidatesResult{Initialize: []agentapi.DiskCandidate{d}}, partMounts, map[string]bool{"/mnt/felhom-drives/hdd_1": true})
if len(got.Initialize) != 0 {
t.Fatalf("a disk with no free part must not be offered, got %+v", got.Initialize)
}
}
func TestPartitions_ServerRefusesTheWholeDiskButNotTheFreePart(t *testing.T) {
reg := map[string]bool{"/mnt/felhom-drives/hdd_1": true}
for dev, want := range map[string]bool{"/dev/sdb": true, "/dev/sdb1": true, "/dev/sdb2": false, "/dev/sdc": false} {
if got := deviceBacksRegistered(dev, partMounts, reg); got != want {
t.Errorf("%s: backs a registered drive = %v, want %v", dev, got, want)
}
}
if !deviceBacksRegistered("/dev/sdc", "", reg) {
t.Errorf("an unreadable mount table must refuse (fail-safe)")
}
}
func TestPartitions_AFormattedPartsUUIDIsFound(t *testing.T) {
ag := &mockAgent{candidates: agentapi.CandidatesResult{Initialize: []agentapi.DiskCandidate{diskWithParts()}}}
if got := resolveEnrollUUID(context.Background(), ag, "/dev/sdb3"); got != "3333" {
t.Fatalf("the part's new filesystem must be found for the mount, got %q", got)
}
}
// The handler asks before it starts anything: the whole disk under a registered part → 409, never a format job.
// Positive control: the free part passes the guard and reaches the agent step (503 here: no agent configured).
func TestPartitions_InitHandlerRefusesTheWholeDisk(t *testing.T) {
s := testServer(t)
s.mountsFn = func() string { return partMounts }
if err := s.settings.AddStoragePath(settingsPath("/mnt/felhom-drives/hdd_1")); err != nil {
t.Fatal(err)
}
post := func(dev string) int {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/api/storage/init", strings.NewReader(`{"device":"`+dev+`","fstype":"ext4","mount_name":"films"}`))
s.handleStorageInit(w, r)
return w.Code
}
if c := post("/dev/sdb"); c != http.StatusConflict {
t.Fatalf("the whole disk under a registered part: want 409, got %d", c)
}
if c := post("/dev/sdb2"); c == http.StatusConflict {
t.Fatalf("positive control: the free part must pass the guard, got %d", c)
}
}
func settingsPath(p string) settings.StoragePath {
return settings.StoragePath{Path: p, Schedulable: true}
}