F6 deeper half: poll agent /disks/format/status on the 15s client timeout (slow USB mkfs runs detached) — agentapi.FormatStatus + awaitAgentFormat; then mount+register. Found on the live leg.

This commit is contained in:
2026-07-17 09:41:50 +02:00
parent 0025a3b090
commit 0515d153db
4 changed files with 142 additions and 3 deletions
@@ -40,6 +40,9 @@ type mockAgent struct {
assignCalls []assignCall
disksCalls int
formatCalls []formatCall
formatStatus agentapi.FormatStatusResult
formatStatusErr error
formatStatusCalls int
guestAttachCalls []string
decommissionCalls []string
guestRebootCalls int
@@ -65,6 +68,10 @@ func (m *mockAgent) FormatDisk(_ context.Context, device, fstype string, confirm
m.formatCalls = append(m.formatCalls, formatCall{device, fstype, durableID, confirmed})
return m.formatRes, m.formatErr
}
func (m *mockAgent) FormatStatus(context.Context) (agentapi.FormatStatusResult, error) {
m.formatStatusCalls++
return m.formatStatus, m.formatStatusErr
}
func (m *mockAgent) AssignDisk(ctx context.Context, uuid, where, fstype, _ string) error {
// Respect cancellation — a mount over a dead client/request context fails (this is the F6 bug's
// mechanism: a disconnect after format aborts the mount+register leg). Background ctx never
@@ -274,6 +281,50 @@ func TestStorageInit_DetachedSurvivesClientDisconnect(t *testing.T) {
})
}
// F6 deeper half — a slow mkfs outruns the agent-client's 15 s timeout; the agent runs it detached,
// so the controller must POLL GET /disks/format/status and continue to mount+register on `done`
// (rather than reporting the timeout as a failure). Observed live on a 64 GB USB.
func TestStorageInit_PollsAgentFormatStatusOnTimeout(t *testing.T) {
t.Run("timeout_then_done_registers", func(t *testing.T) {
s := testServer(t)
agent := &mockAgent{
formatErr: context.DeadlineExceeded, // the 15 s client timeout — mkfs continues detached
formatStatus: agentapi.FormatStatusResult{Phase: "done", Device: "/dev/sdb1"},
disks: agentapi.DisksResponse{Disks: []agentapi.DiskInfo{
{Name: "felhom-usb", BackingDevice: "/dev/sdb1", DurableID: "uuid:NEW-9999"},
}},
}
res, err := s.runStorageInit(context.Background(), agent, "/dev/sdb1", "ext4", "/mnt/hdd1", "HDD", true, false, "", nil)
if err != nil {
t.Fatalf("expected success via the format-status poll, got %v", err)
}
if !res.Registered {
t.Fatal("expected registered after the format-status poll (F6)")
}
if agent.formatStatusCalls == 0 {
t.Fatal("the agent format-status was never polled")
}
if len(agent.assignCalls) != 1 {
t.Fatalf("expected exactly one mount after poll-done: %+v", agent.assignCalls)
}
})
t.Run("timeout_then_failed_surfaces_error", func(t *testing.T) {
s := testServer(t)
agent := &mockAgent{
formatErr: context.DeadlineExceeded,
formatStatus: agentapi.FormatStatusResult{Phase: "failed", Device: "/dev/sdb1", Error: "mkfs exploded"},
}
_, err := s.runStorageInit(context.Background(), agent, "/dev/sdb1", "ext4", "/mnt/hdd1", "HDD", true, false, "", nil)
if err == nil {
t.Fatal("a failed detached format must surface an error, not register")
}
if len(s.settings.GetStoragePaths()) != 0 {
t.Fatalf("a failed format must NOT register: %+v", s.settings.GetStoragePaths())
}
})
}
// F7 (VALIDATION-n100) — the "Vissza" (Back) anchor on the init/attach wizards must route to
// /storage, not /settings. One assertion per template.
func TestStorageWizardBackAnchors_PointToStorage(t *testing.T) {