769c4c3cf2
After mkfs the agent re-resolves the bound durable id, requires it to name the device it just formatted, reads the superblock back (blkid -p, requested fstype) and returns fs_uuid in POST /disks/format and GET /disks/format/status. Anything unverified returns "" — never a path-resolved guess. The controller half (mount fs_uuid instead of re-resolving the UUID from the /dev path) is owed in felhom-controller. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
97 lines
3.6 KiB
Go
97 lines
3.6 KiB
Go
package localapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
|
)
|
|
|
|
// R-25: the format answer carries the UUID of the filesystem the agent JUST made, verified against the
|
|
// bound durable id after mkfs, so the controller mounts that filesystem rather than whatever the /dev
|
|
// path resolves to a few requests later. The consequence asserted: the UUID on the wire (and in the
|
|
// polled job record) is the new superblock's — and is EMPTY whenever the binding cannot be re-proved.
|
|
|
|
const newFSUUID = "0fc63daf-8483-4772-8e79-3d69d8477de4"
|
|
|
|
func confirmedFormat(t *testing.T, d *fakeDiskOps, srv *Server, fj *FormatJobStore) (string, *formatJob) {
|
|
t.Helper()
|
|
w := do(t, srv.Handler(), "POST", "/disks/format", "A", `{"device":"/dev/sdb1","fstype":"ext4","confirmed":true,"durable_id":"byid:wwn-/dev/sdb1"}`)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("confirmed format: %d (%s)", w.Code, w.Body.String())
|
|
}
|
|
var resp struct {
|
|
Data FormatResponse `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("decode: %v (%s)", err, w.Body.String())
|
|
}
|
|
if !resp.Data.Formatted {
|
|
t.Fatalf("not formatted: %s", w.Body.String())
|
|
}
|
|
return resp.Data.FSUUID, waitFormatPhase(t, fj, formatPhaseDone)
|
|
}
|
|
|
|
func confirmedGate() *fakeGate {
|
|
return &fakeGate{decision: WipeDecision{Allowed: true, Tier: "customer_confirmable", Reason: "customer_confirmed"}}
|
|
}
|
|
|
|
func TestFormat_ReportsNewFilesystemUUID(t *testing.T) {
|
|
d := &fakeDiskOps{probe: deviceProbeDataBearing(),
|
|
afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4", FSUUID: newFSUUID}}
|
|
fj := tempFormatStore(t)
|
|
srv := formatServer(t, d, confirmedGate(), fj)
|
|
|
|
got, job := confirmedFormat(t, d, srv, fj)
|
|
if got != newFSUUID {
|
|
t.Fatalf("response fs_uuid = %q, want the new filesystem's %q", got, newFSUUID)
|
|
}
|
|
if job.FSUUID != newFSUUID {
|
|
t.Fatalf("job record fs_uuid = %q, want %q (the polled status path)", job.FSUUID, newFSUUID)
|
|
}
|
|
}
|
|
|
|
// The node moved between mkfs and the read-back: the bound durable id now resolves elsewhere. The
|
|
// UUID must NOT be reported — reading it would name the other disk's filesystem.
|
|
func TestFormat_FSUUIDWithheldWhenDurableIDMoved(t *testing.T) {
|
|
d := &fakeDiskOps{probe: deviceProbeDataBearing(),
|
|
afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4", FSUUID: newFSUUID}}
|
|
fj := tempFormatStore(t)
|
|
srv := formatServer(t, d, confirmedGate(), fj)
|
|
var mu sync.Mutex
|
|
calls := 0
|
|
srv.reresolveWipe = func(_ context.Context, _ string) (string, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
calls++
|
|
if calls == 1 {
|
|
return "/dev/sdb", nil // the pre-mkfs anti-retarget re-resolve
|
|
}
|
|
return "/dev/sdc", nil // after mkfs: the durable id now names another node
|
|
}
|
|
|
|
got, job := confirmedFormat(t, d, srv, fj)
|
|
if got != "" || job.FSUUID != "" {
|
|
t.Fatalf("fs_uuid reported after the durable id moved (response %q, job %q) — must be empty", got, job.FSUUID)
|
|
}
|
|
if calls < 2 {
|
|
t.Fatalf("the post-mkfs re-resolve never ran (calls=%d)", calls)
|
|
}
|
|
}
|
|
|
|
// The superblock did not read back as the requested filesystem → not verified → empty.
|
|
func TestFormat_FSUUIDWithheldOnFSTypeMismatch(t *testing.T) {
|
|
d := &fakeDiskOps{probe: deviceProbeDataBearing(),
|
|
afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "xfs", FSUUID: newFSUUID}}
|
|
fj := tempFormatStore(t)
|
|
srv := formatServer(t, d, confirmedGate(), fj)
|
|
|
|
got, job := confirmedFormat(t, d, srv, fj)
|
|
if got != "" || job.FSUUID != "" {
|
|
t.Fatalf("fs_uuid reported for a superblock of the wrong type (response %q, job %q)", got, job.FSUUID)
|
|
}
|
|
}
|