raid: parse storcli64/storcli2's actual per-slot drive JSON shape
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2d84ddb577
commit
4e306ff78c
@@ -103,13 +103,16 @@ func collectStorcliDrives() []schema.HardwareStorage {
|
||||
}
|
||||
|
||||
// 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).
|
||||
// SAS3808-iMR/9500 series) via storcli2, Broadcom's separate tool for this
|
||||
// controller line. On at least one confirmed SAS3808-iMR system storcli2
|
||||
// itself reports zero controllers even though storcli64 sees the controller
|
||||
// and its drives fine (a tool-side gap, not something parseable around), so
|
||||
// this is run as an additional source alongside storcli64, not a
|
||||
// replacement. parseStorcliDrivesJSON is reused as-is since storcli2 and
|
||||
// storcli64 share the same "Response Data" drive-listing shape; 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 {
|
||||
@@ -492,41 +495,7 @@ func parseSSACLIInterface(raw string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func parseStorcliDrivesJSON(raw []byte) []schema.HardwareStorage {
|
||||
var doc struct {
|
||||
Controllers []struct {
|
||||
ResponseData struct {
|
||||
DriveInformation []struct {
|
||||
EIDSlt string `json:"EID:Slt"`
|
||||
State string `json:"State"`
|
||||
Size string `json:"Size"`
|
||||
Intf string `json:"Intf"`
|
||||
Med string `json:"Med"`
|
||||
Model string `json:"Model"`
|
||||
SN string `json:"SN"`
|
||||
Sp string `json:"Sp"`
|
||||
Type string `json:"Type"`
|
||||
} `json:"Drive Information"`
|
||||
} `json:"Response Data"`
|
||||
} `json:"Controllers"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
slog.Warn("raid: parse storcli json failed", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var drives []schema.HardwareStorage
|
||||
for _, ctl := range doc.Controllers {
|
||||
for _, d := range ctl.ResponseData.DriveInformation {
|
||||
if s := storcliDriveToStorage(d); s != nil {
|
||||
drives = append(drives, *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return drives
|
||||
}
|
||||
|
||||
func storcliDriveToStorage(d struct {
|
||||
type storcliDrive struct {
|
||||
EIDSlt string `json:"EID:Slt"`
|
||||
State string `json:"State"`
|
||||
Size string `json:"Size"`
|
||||
@@ -536,7 +505,60 @@ func storcliDriveToStorage(d struct {
|
||||
SN string `json:"SN"`
|
||||
Sp string `json:"Sp"`
|
||||
Type string `json:"Type"`
|
||||
}) *schema.HardwareStorage {
|
||||
}
|
||||
|
||||
// storcliDrivePerSlotKeyRe matches "Response Data" keys like "Drive /c0/e69/s0"
|
||||
// — real storcli64/storcli2 "eall/sall show all J" output nests each drive
|
||||
// under its own dynamically-named key instead of a shared "Drive Information"
|
||||
// array (confirmed against a live SAS3808-iMR dump: the top-level "Drive
|
||||
// Information" array this parser previously assumed simply isn't present).
|
||||
// It must not match the paired "Drive /c0/e69/s0 - Detailed Information" key.
|
||||
var storcliDrivePerSlotKeyRe = regexp.MustCompile(`^Drive /c\d+/e\d+/s\d+$`)
|
||||
|
||||
func parseStorcliDrivesJSON(raw []byte) []schema.HardwareStorage {
|
||||
var doc struct {
|
||||
Controllers []struct {
|
||||
ResponseData map[string]json.RawMessage `json:"Response Data"`
|
||||
} `json:"Controllers"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &doc); err != nil {
|
||||
slog.Warn("raid: parse storcli json failed", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var drives []schema.HardwareStorage
|
||||
for _, ctl := range doc.Controllers {
|
||||
// Older storcli output (or "show" without "all") reports a single
|
||||
// "Drive Information" array instead of per-slot keys.
|
||||
if raw, ok := ctl.ResponseData["Drive Information"]; ok {
|
||||
var list []storcliDrive
|
||||
if err := json.Unmarshal(raw, &list); err == nil {
|
||||
for _, d := range list {
|
||||
if s := storcliDriveToStorage(d); s != nil {
|
||||
drives = append(drives, *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, raw := range ctl.ResponseData {
|
||||
if !storcliDrivePerSlotKeyRe.MatchString(key) {
|
||||
continue
|
||||
}
|
||||
var list []storcliDrive
|
||||
if err := json.Unmarshal(raw, &list); err != nil {
|
||||
continue
|
||||
}
|
||||
for _, d := range list {
|
||||
if s := storcliDriveToStorage(d); s != nil {
|
||||
drives = append(drives, *s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return drives
|
||||
}
|
||||
|
||||
func storcliDriveToStorage(d storcliDrive) *schema.HardwareStorage {
|
||||
present := true
|
||||
status := mapRAIDDriveStatus(d.State)
|
||||
s := schema.HardwareStorage{
|
||||
|
||||
Reference in New Issue
Block a user