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