fix(webui): trust IPMI FRU readback over ipmitool's exit code

`ipmitool fru edit` frequently exits non-zero after a field resize
(shrinking/growing a string shifts every later offset in the FRU
record) even though the write itself landed correctly on the BMC -
the failure is in ipmitool's own post-write re-parse, not the write.
bee was treating that exit code as authoritative and marking the task
failed even when the field was actually written.

runIPMIFRUWriteTask now re-reads the FRU on a non-zero exit and
compares the target field's actual value before deciding the task
failed, logging which path it took either way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-24 18:39:02 +03:00
co-authored by Claude Sonnet 5
parent 198567dffe
commit 7aa276320b
+30 -1
View File
@@ -210,8 +210,37 @@ func runIPMIFRUWriteTask(ctx context.Context, j *jobState, exportDir string, p t
j.append(fmt.Sprintf("Setting %s (%s %d) = %q", c.Name, c.Area, c.Index, c.Value))
cmd := exec.CommandContext(ctx, "ipmitool", "fru", "edit", "0", "field", c.Area, fmt.Sprintf("%d", c.Index), c.Value)
if err := streamCmdJob(j, cmd); err != nil {
return fmt.Errorf("fru edit %s %d: %w", c.Area, c.Index, err)
// ipmitool's `fru edit` frequently exits non-zero after a field
// resize (shrinking/growing a string shifts every later offset in
// the FRU record) even though the write itself landed correctly -
// the failure is in ipmitool's own post-write re-parse, not the
// BMC write. Read the FRU back and trust the BMC's own data over
// ipmitool's exit code before declaring the task failed.
ok, verifyErr := verifyFRUField(ctx, c.Area, c.Index, c.Value)
if verifyErr != nil {
j.append("Readback verification failed: " + verifyErr.Error())
return fmt.Errorf("fru edit %s %d: %w", c.Area, c.Index, err)
}
if !ok {
return fmt.Errorf("fru edit %s %d: %w", c.Area, c.Index, err)
}
j.append(fmt.Sprintf("ipmitool exited with an error, but readback confirms %s %d was written correctly - continuing", c.Area, c.Index))
}
}
return nil
}
// verifyFRUField re-reads the FRU and checks whether the field at area/index
// already holds want, independent of any exit code from a prior edit.
func verifyFRUField(ctx context.Context, area string, index int, want string) (bool, error) {
out, err := exec.CommandContext(ctx, "ipmitool", "fru", "print", "0").CombinedOutput()
if err != nil {
return false, fmt.Errorf("fru print: %w", err)
}
for _, f := range parseFRUOutput(string(out)) {
if f.Area == area && f.Index == index {
return f.Value == want, nil
}
}
return false, nil
}