webui/collector: strip ANSI escapes in nvidia-smi topo parsing, auto-prepare drives for RAID mirror creation

- nvidia-smi underlines the topo -m header with ANSI CSI codes even when
  writing to a file; both NVLink matrix parsers failed to find the header
  and /topo showed "No NVLink-bonded GPU pairs found" on bonded systems.
- raid-lsi-create-mirror failed with "resources already in use" (exit 11)
  on JBOD drives; the task now reads drive states and auto-converts
  JBOD/UBad (set good force), releases hotspares, refuses Frgn/Onln with
  actionable messages, and dumps preservedcache info when add vd fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 23:16:04 +03:00
co-authored by Claude Fable 5
parent 1175e6ccd8
commit 10557ec0f6
6 changed files with 184 additions and 3 deletions
@@ -71,6 +71,25 @@ GPU1 NV0 X
}
}
func TestParseNVIDIATopologyMatrixANSIUnderlinedHeader(t *testing.T) {
t.Parallel()
// nvidia-smi underlines the header row with ANSI escapes even when stdout
// is not a TTY: the header line starts with ESC[4m, not "GPU0".
input := "\x1b[4m\tGPU0\tGPU1\tNIC0\tCPU Affinity\x1b[0m\n" +
"GPU0\t X \tNV18\tNODE\t0-31,64-95\n" +
"GPU1\tNV18\t X \tNODE\t0-31,64-95\n" +
"NIC0\tNODE\tNODE\t X \n"
got := parseNVIDIATopologyMatrix(input)
if got.GPUCount != 2 {
t.Fatalf("GPUCount=%d want 2", got.GPUCount)
}
if !got.AllActive || got.MinNVLinks != 18 {
t.Fatalf("AllActive=%v MinNVLinks=%d want true/18", got.AllActive, got.MinNVLinks)
}
}
func TestParseNVIDIATopologyMatrixEmpty(t *testing.T) {
t.Parallel()