Add health verdicts and acceptance tests
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bee/audit/internal/schema"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -16,6 +17,9 @@ func collectPSUs() []schema.HardwarePowerSupply {
|
||||
return nil
|
||||
}
|
||||
psus := parseFRU(string(out))
|
||||
if sdrOut, err := exec.Command("ipmitool", "sdr").Output(); err == nil {
|
||||
mergePSUSDR(psus, parsePSUSDR(string(sdrOut)))
|
||||
}
|
||||
slog.Info("psu: collected", "count", len(psus))
|
||||
return psus
|
||||
}
|
||||
@@ -116,6 +120,135 @@ func parseFRUBlock(block string, slotIdx int) (schema.HardwarePowerSupply, bool)
|
||||
return psu, true
|
||||
}
|
||||
|
||||
type psuSDR struct {
|
||||
slot int
|
||||
status string
|
||||
inputPowerW *float64
|
||||
outputPowerW *float64
|
||||
inputVoltage *float64
|
||||
}
|
||||
|
||||
var psuSlotRe = regexp.MustCompile(`(?i)\bpsu?\s*([0-9]+)\b|\bps\s*([0-9]+)\b`)
|
||||
|
||||
func parsePSUSDR(raw string) map[int]psuSDR {
|
||||
out := map[int]psuSDR{}
|
||||
for _, line := range strings.Split(raw, "\n") {
|
||||
fields := splitSDRFields(line)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
name := fields[0]
|
||||
value := fields[1]
|
||||
state := strings.ToLower(fields[2])
|
||||
slot, ok := parsePSUSlot(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := out[slot]
|
||||
entry.slot = slot
|
||||
if entry.status == "" {
|
||||
entry.status = "OK"
|
||||
}
|
||||
if state != "" && state != "ok" && state != "ns" {
|
||||
entry.status = "FAILED"
|
||||
}
|
||||
|
||||
lowerName := strings.ToLower(name)
|
||||
switch {
|
||||
case strings.Contains(lowerName, "input power"):
|
||||
entry.inputPowerW = parseFloatPtr(value)
|
||||
case strings.Contains(lowerName, "output power"):
|
||||
entry.outputPowerW = parseFloatPtr(value)
|
||||
case strings.Contains(lowerName, "input voltage"), strings.Contains(lowerName, "ac input"):
|
||||
entry.inputVoltage = parseFloatPtr(value)
|
||||
}
|
||||
out[slot] = entry
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mergePSUSDR(psus []schema.HardwarePowerSupply, sdr map[int]psuSDR) {
|
||||
for i := range psus {
|
||||
slotIdx, err := strconv.Atoi(derefPSUSlot(psus[i].Slot))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entry, ok := sdr[slotIdx+1]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if entry.inputPowerW != nil {
|
||||
psus[i].InputPowerW = entry.inputPowerW
|
||||
}
|
||||
if entry.outputPowerW != nil {
|
||||
psus[i].OutputPowerW = entry.outputPowerW
|
||||
}
|
||||
if entry.inputVoltage != nil {
|
||||
psus[i].InputVoltage = entry.inputVoltage
|
||||
}
|
||||
if entry.status != "" {
|
||||
psus[i].Status = &entry.status
|
||||
}
|
||||
if psus[i].Status != nil && *psus[i].Status == "OK" {
|
||||
if (entry.inputPowerW == nil && entry.outputPowerW == nil && entry.inputVoltage == nil) && entry.status == "" {
|
||||
unknown := "UNKNOWN"
|
||||
psus[i].Status = &unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func splitSDRFields(line string) []string {
|
||||
parts := strings.Split(line, "|")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parsePSUSlot(name string) (int, bool) {
|
||||
m := psuSlotRe.FindStringSubmatch(strings.ToLower(name))
|
||||
if len(m) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
for _, group := range m[1:] {
|
||||
if group == "" {
|
||||
continue
|
||||
}
|
||||
n, err := strconv.Atoi(group)
|
||||
if err == nil && n > 0 {
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func parseFloatPtr(raw string) *float64 {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || strings.EqualFold(raw, "na") {
|
||||
return nil
|
||||
}
|
||||
for _, field := range strings.Fields(raw) {
|
||||
n, err := strconv.ParseFloat(strings.TrimSpace(field), 64)
|
||||
if err == nil {
|
||||
return &n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func derefPSUSlot(slot *string) string {
|
||||
if slot == nil {
|
||||
return ""
|
||||
}
|
||||
return *slot
|
||||
}
|
||||
|
||||
// parseWattage extracts wattage from strings like "PSU 800W", "1200W PLATINUM".
|
||||
func parseWattage(s string) int {
|
||||
s = strings.ToUpper(s)
|
||||
|
||||
Reference in New Issue
Block a user