raid: add storcli2 support for Tri-Mode controllers (SAS3808-iMR/9500 series)
storcli64 enumerates these controllers but reports zero drives; storcli2 is the tool Broadcom ships for Tri-Mode/MegaRAID8 hardware and uses a compatible JSON schema for drive listing. Wires storcli2 into both the collector (structured drive data) and the webui RAID Management page (dedup so a controller isn't double-listed if storcli64 already sees it with zero drives), plus techdump raw collection and the ISO vendor-tool build step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a3377083aa
commit
b7f015c713
@@ -41,6 +41,9 @@ func collectRAIDStorage(pcie []schema.HardwarePCIeDevice) []schema.HardwareStora
|
||||
if drives := collectStorcliDrives(); len(drives) > 0 {
|
||||
out = append(out, drives...)
|
||||
}
|
||||
if drives := collectStorcli2Drives(); len(drives) > 0 {
|
||||
out = append(out, drives...)
|
||||
}
|
||||
if drives := collectSASIrcuDrives("sas3ircu"); len(drives) > 0 {
|
||||
out = append(out, drives...)
|
||||
}
|
||||
@@ -99,6 +102,63 @@ func collectStorcliDrives() []schema.HardwareStorage {
|
||||
return drives
|
||||
}
|
||||
|
||||
// collectStorcli2Drives covers Broadcom Tri-Mode controllers (e.g.
|
||||
// SAS3808-iMR/9500 series), which storcli64 can enumerate at a basic level
|
||||
// but whose drives it never finds — a separate tool/JSON schema (storcli2)
|
||||
// is required. storcli2's "Drive Information" array shape matches storcli64's
|
||||
// (confirmed against Broadcom's published drive-info-schema.json), so
|
||||
// parseStorcliDrivesJSON is reused as-is; only the controller enumeration
|
||||
// and per-controller invocation syntax differ (storcli2 wants an explicit
|
||||
// /cN target rather than storcli64's /call wildcard).
|
||||
func collectStorcli2Drives() []schema.HardwareStorage {
|
||||
sysOut, err := raidToolQuery("storcli2", "show", "all", "J")
|
||||
if err != nil {
|
||||
slog.Info("raid: storcli2 unavailable", "err", err)
|
||||
return nil
|
||||
}
|
||||
indices := parseStorcli2ControllerIndices(sysOut)
|
||||
if len(indices) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var drives []schema.HardwareStorage
|
||||
for _, idx := range indices {
|
||||
out, err := raidToolQuery("storcli2", "/c"+strconv.Itoa(idx)+"/eall/sall", "show", "all", "J")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
drives = append(drives, parseStorcliDrivesJSON(out)...)
|
||||
}
|
||||
if len(drives) == 0 {
|
||||
slog.Info("raid: storcli2 returned no drives")
|
||||
}
|
||||
return drives
|
||||
}
|
||||
|
||||
// parseStorcli2ControllerIndices parses "storcli2 show all J" (Broadcom's
|
||||
// system-schema.json: Controllers[0].Response Data.System Overview[].Ctrl).
|
||||
func parseStorcli2ControllerIndices(raw []byte) []int {
|
||||
var doc struct {
|
||||
Controllers []struct {
|
||||
ResponseData struct {
|
||||
SystemOverview []struct {
|
||||
Ctrl int `json:"Ctrl"`
|
||||
} `json:"System Overview"`
|
||||
} `json:"Response Data"`
|
||||
} `json:"Controllers"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
return nil
|
||||
}
|
||||
var indices []int
|
||||
for _, c := range doc.Controllers {
|
||||
for _, ov := range c.ResponseData.SystemOverview {
|
||||
indices = append(indices, ov.Ctrl)
|
||||
}
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
func collectSASIrcuDrives(tool string) []schema.HardwareStorage {
|
||||
out, err := raidToolQuery(tool, "list")
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user