RAID Controller Management showed "No drives detected" for a live
SAS3808-iMR controller even though storcli64 clearly enumerates its
drives. Root cause: the "eall/sall show all J" drive-listing parser
(collector/raid.go and its webui/raid_mgmt.go duplicate) assumed
drives are reported as a single "Drive Information" array, but real
storcli64/storcli2 output nests each drive under its own dynamically
named key ("Drive /c0/e69/s0", paired with a "... - Detailed
Information" key) — confirmed against a live techdump/storcli64-drives.json
capture. The assumed shape was never actually produced by the tool, so
this affected every storcli64/storcli2 controller, not just Tri-Mode
ones (the storcli2 fallback added in b7f015c never got a chance to
mask it in practice, since storcli2 itself reports zero controllers on
this hardware — a separate, unrelated tool-side gap).
Both parsers now read "Response Data" as a raw key map and pull drives
from either shape, so older storcli output using the array form still
works alongside the real per-slot form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
3.9 KiB
Go
111 lines
3.9 KiB
Go
package collector
|
|
|
|
import "testing"
|
|
|
|
// Fixture shape matches Broadcom's published storcli2 system-schema.json
|
|
// ("show all" -> Controllers[0].Response Data.System Overview[].Ctrl).
|
|
func TestParseStorcli2ControllerIndices(t *testing.T) {
|
|
raw := []byte(`{
|
|
"Controllers": [
|
|
{
|
|
"Command Status": {"Status": "Success"},
|
|
"Response Data": {
|
|
"Number of Controllers": 2,
|
|
"System Overview": [
|
|
{"Ctrl": 0, "Product Name": "SAS3808-iMR", "Personality": "RAID", "Status": "Optimal", "PD(s)": 2, "VD(s)": 0},
|
|
{"Ctrl": 1, "Product Name": "SAS3808-iMR", "Personality": "RAID", "Status": "Optimal", "PD(s)": 4, "VD(s)": 1}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
|
|
got := parseStorcli2ControllerIndices(raw)
|
|
if len(got) != 2 || got[0] != 0 || got[1] != 1 {
|
|
t.Fatalf("indices=%v want [0 1]", got)
|
|
}
|
|
}
|
|
|
|
func TestParseStorcli2ControllerIndicesEmptyOrMalformed(t *testing.T) {
|
|
if got := parseStorcli2ControllerIndices([]byte("not json")); got != nil {
|
|
t.Fatalf("malformed input should return nil, got %v", got)
|
|
}
|
|
if got := parseStorcli2ControllerIndices([]byte(`{"Controllers":[]}`)); got != nil {
|
|
t.Fatalf("no controllers should return nil, got %v", got)
|
|
}
|
|
}
|
|
|
|
// TestCollectStorcli2DrivesEndToEnd exercises the full path this bug fix
|
|
// targets: storcli64 sees the Tri-Mode controller but no drives (real
|
|
// support-bundle behavior for SAS3808-iMR), storcli2 is the tool that
|
|
// actually finds them.
|
|
func TestCollectStorcli2DrivesEndToEnd(t *testing.T) {
|
|
orig := raidToolQuery
|
|
t.Cleanup(func() { raidToolQuery = orig })
|
|
raidToolQuery = func(name string, args ...string) ([]byte, error) {
|
|
switch name {
|
|
case "storcli2":
|
|
if len(args) > 0 && args[0] == "show" {
|
|
return []byte(`{"Controllers":[{"Response Data":{"System Overview":[{"Ctrl":0,"Product Name":"SAS3808-iMR"}]}}]}`), nil
|
|
}
|
|
// per-controller drive listing: /c0/eall/sall show all J
|
|
return []byte(`{
|
|
"Controllers": [
|
|
{
|
|
"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"}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}`), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
drives := collectStorcli2Drives()
|
|
if len(drives) != 2 {
|
|
t.Fatalf("drives=%d want 2 (%#v)", len(drives), drives)
|
|
}
|
|
if drives[0].SerialNumber == nil || *drives[0].SerialNumber != "S6EYNE0T123456" {
|
|
t.Fatalf("drives[0]=%#v want serial S6EYNE0T123456", drives[0])
|
|
}
|
|
}
|
|
|
|
// TestParseStorcliDrivesJSONPerSlotKeys 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). The previous parser, which only looked for "Drive
|
|
// Information", silently returned zero drives against this real output.
|
|
func TestParseStorcliDrivesJSONPerSlotKeys(t *testing.T) {
|
|
raw := []byte(`{
|
|
"Controllers": [
|
|
{
|
|
"Command Status": {"Status": "Success"},
|
|
"Response Data": {
|
|
"Drive /c0/e69/s0": [
|
|
{"EID:Slt": "69:0", "DID": 0, "State": "JBOD", "DG": "-", "Size": "447.130 GB", "Intf": "SATA", "Med": "SSD", "Model": "SAMSUNG MZ7L3480HCHQ-00B7C", "Sp": "U", "Type": "-"}
|
|
],
|
|
"Drive /c0/e69/s0 - Detailed Information": {
|
|
"Drive /c0/e69/s0 State": {"Shield Counter": 0}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}`)
|
|
|
|
drives := parseStorcliDrivesJSON(raw)
|
|
if len(drives) != 1 {
|
|
t.Fatalf("drives=%d want 1 (%#v)", len(drives), drives)
|
|
}
|
|
if drives[0].Model == nil || *drives[0].Model != "SAMSUNG MZ7L3480HCHQ-00B7C" {
|
|
t.Fatalf("drives[0]=%#v want model SAMSUNG MZ7L3480HCHQ-00B7C", drives[0])
|
|
}
|
|
if drives[0].Slot == nil || *drives[0].Slot != "69:0" {
|
|
t.Fatalf("drives[0].Slot=%v want 69:0", drives[0].Slot)
|
|
}
|
|
}
|