F20-BUG3: run mkfs detached (survives request deadline + agent restart); v0.31.0

The format ran mkfs under the HTTP request context, so the controller's 15s client
timeout cancelled it → SIGKILL mid-write → corrupt disk. Now mkfs runs DETACHED off
s.baseCtx (a dropped request can't kill it) via a persisted formatJob record; the handler
still waits to return the synchronous result (backward-compatible with the v0.62.0
controller) but abandoning the wait on client-disconnect leaves the mkfs running to
completion. New GET /disks/format/status surfaces the job (additive). RecoverFormatJob
runs on agent startup: a record left 'running' (agent died mid-format) is re-resolved by
durable-id (anti-retarget — absent/swapped disk NOT re-formatted) and the mkfs re-run; a
blank/path-bound interrupted format is marked failed (retry), never auto-re-run.

Tests: detached run persists running→done + binds durable-id; status endpoint; recovery
re-runs an interrupted durable-id-bound format; skips blank; skips unresolvable durable-id.
Version 0.30.0 → 0.31.0.
This commit is contained in:
2026-06-14 15:16:01 +02:00
parent 4cd1d024e9
commit 4777f8a221
5 changed files with 417 additions and 6 deletions
+7
View File
@@ -91,6 +91,10 @@ type Options struct {
// startup re-assert (ReassertGuestBinds) can restore a bind that a re-provision dropped (F9).
// OPTIONAL — when nil, guest binds are not recorded and the startup re-assert is a no-op.
GuestBinds *GuestBindStore
// FormatJobs persists the in-flight/last disk-format job so mkfs runs detached from the request
// (F20-BUG3: a request deadline can't kill it) and survives an agent restart (RecoverFormatJob).
// OPTIONAL — when nil, formats still run detached but are not persisted/recovered.
FormatJobs *FormatJobStore
// HostReader is the root-free host topology reader used to classify a device/mount's protection
// ROLE (it backs SystemDisks for the eject role-gate + the /disks role hints). OPTIONAL — when nil
// it defaults to the production *storage.ProcHostReader. Injectable so the role-gate is testable.
@@ -148,6 +152,7 @@ type Server struct {
guestAttach GuestAttacher // slice 10 P2 (optional)
intent IntentRecorder // slice 10 P3 (optional)
guestBinds *GuestBindStore // F9 startup bind re-assert record (optional)
formatJobs *FormatJobStore // F20-BUG3 detached-format job record (optional)
host storage.HostReader // role classification source (optional; defaults to ProcHostReader)
hostMetrics HostMetricsProvider // slice 9 (optional)
@@ -204,6 +209,7 @@ func NewServer(o Options) (*Server, error) {
guestAttach: o.GuestAttach,
intent: o.Intent,
guestBinds: o.GuestBinds,
formatJobs: o.FormatJobs,
host: o.HostReader,
hostMetrics: o.HostMetrics,
hostID: o.HostID,
@@ -232,6 +238,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("POST /disks/assign", s.withGuest(s.handleDiskAssign))
mux.HandleFunc("POST /disks/eject", s.withGuest(s.handleDiskEject))
mux.HandleFunc("POST /disks/format", s.withGuest(s.handleDiskFormat))
mux.HandleFunc("GET /disks/format/status", s.withGuest(s.handleDiskFormatStatus))
// Guest data-drive passthrough (slice 10 P2): bind an enrolled drive's felhom-data namespace in.
mux.HandleFunc("POST /disks/guest-attach", s.withGuest(s.handleDiskGuestAttach))
// Guest reboot (slice 10 P2 activation): user-triggered restart to activate pending drive binds.