Files
bee/audit/internal/webui/raid_mgmt_test.go
mchusandClaude Fable 5 10557ec0f6 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>
2026-07-08 23:16:04 +03:00

104 lines
3.6 KiB
Go

package webui
import "testing"
// Fixture shape matches Broadcom's published storcli2 drive-info-schema.json
// (Controllers[].Response Data.Drive Information[]), which is the same shape
// storcli64 already uses — confirmed by comparing the two tools' schemas.
func TestParseStorcli2DriveInformation(t *testing.T) {
raw := []byte(`{
"Controllers": [
{
"Command Status": {"Controller": 0, "Status": "Success"},
"Response Data": {
"Drive Information": [
{"EID:Slt": "252:0", "State": "Onln", "Size": "1.746 TB", "Intf": "NVMe", "Med": "SSD", "Model": "SAMSUNG MZQL21T9HCJR", "SN": "S6EYNE0T123456"},
{"EID:Slt": "252:1", "State": "UGood", "Size": "1.746 TB", "Intf": "NVMe", "Med": "SSD", "Model": "SAMSUNG MZQL21T9HCJR", "SN": "S6EYNE0T654321"}
]
}
}
]
}`)
all, foreign, free := parseStorcli2DriveInformation(raw)
if len(all) != 2 {
t.Fatalf("all=%d want 2 (%#v)", len(all), all)
}
if len(foreign) != 0 {
t.Fatalf("foreign=%d want 0", len(foreign))
}
if len(free) != 1 || free[0].Serial != "S6EYNE0T654321" {
t.Fatalf("free=%#v want one UGood drive", free)
}
if all[0].Slot != "252:0" || all[0].SizeGB <= 0 {
t.Fatalf("all[0]=%#v want populated slot/size", all[0])
}
}
func TestParseStorcli2DriveInformationEmptyOrMalformed(t *testing.T) {
if all, foreign, free := parseStorcli2DriveInformation(nil); all != nil || foreign != nil || free != nil {
t.Fatalf("nil input should return nil slices, got %#v %#v %#v", all, foreign, free)
}
if all, _, _ := parseStorcli2DriveInformation([]byte("not json")); all != nil {
t.Fatalf("malformed JSON should return nil, got %#v", all)
}
// Valid JSON but no Controllers (e.g. tool ran but found nothing) must not panic.
if all, _, _ := parseStorcli2DriveInformation([]byte(`{"Controllers":[]}`)); all != nil {
t.Fatalf("empty Controllers should return nil, got %#v", all)
}
}
// TestParseStorcliResponseDataDrivesPerSlotKeys regression-tests against a
// live SAS3808-iMR "storcli64 /call/eall/sall show all J" dump: "Response
// Data" has no "Drive Information" array at all, only per-slot keys like
// "Drive /c0/e69/s0" (plus a paired "... - Detailed Information" key that
// must be skipped). This is what made "RAID Controller Management" show
// "No drives detected" for a controller storcli64 could clearly see.
func TestParseStorcliResponseDataDrivesPerSlotKeys(t *testing.T) {
raw := []byte(`{
"Controllers": [
{
"Command Status": {"Status": "Success"},
"Response Data": {
"Drive /c0/e69/s0": [
{"EID:Slt": "69:0", "State": "JBOD", "Size": "447.130 GB", "Model": "SAMSUNG MZ7L3480HCHQ-00B7C"}
],
"Drive /c0/e69/s0 - Detailed Information": {
"Drive /c0/e69/s0 State": {"Shield Counter": 0}
}
}
}
]
}`)
all, _, free := parseStorcli2DriveInformation(raw)
if len(all) != 1 {
t.Fatalf("all=%d want 1 (%#v)", len(all), all)
}
if all[0].Model != "SAMSUNG MZ7L3480HCHQ-00B7C" || all[0].Slot != "69:0" {
t.Fatalf("all[0]=%#v want model/slot populated", all[0])
}
if len(free) != 1 {
t.Fatalf("free=%d want 1 (JBOD is a free drive)", len(free))
}
}
func TestClassifyRAIDPrepareAction(t *testing.T) {
cases := map[string]raidPrepareAction{
"UGood": raidPrepNone,
"": raidPrepNone,
"JBOD": raidPrepSetGood,
"UBad": raidPrepSetGood,
"GHS": raidPrepHotspare,
"DHS": raidPrepHotspare,
"Frgn": raidPrepBlockedFrgn,
"Onln": raidPrepBlockedOnln,
"Offln": raidPrepBlockedOnln,
}
for state, want := range cases {
if got := classifyRAIDPrepareAction(state); got != want {
t.Errorf("state %q: got %v want %v", state, got, want)
}
}
}