Retry FreeBSD SMART probes after false standby (#1254)

This commit is contained in:
rcourtman 2026-03-25 22:34:24 +00:00
parent 48f4438d23
commit ec93de1a59
2 changed files with 118 additions and 5 deletions

View file

@ -464,6 +464,7 @@ func collectDeviceSMART(ctx context.Context, device string) (*DiskSMART, error)
attempts := smartctlProbeAttempts(device)
var firstParsed *DiskSMART
var firstStandby *DiskSMART
var lastErr error
for i, args := range attempts {
@ -473,11 +474,18 @@ func collectDeviceSMART(ctx context.Context, device string) (*DiskSMART, error)
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == smartctlStandbyExitStatus && len(output) == 0 {
return &DiskSMART{
standbyResult := &DiskSMART{
Device: filepath.Base(device),
Standby: true,
LastUpdated: timeNow(),
}, nil
}
if runtimeGOOS == "freebsd" && i < len(attempts)-1 {
if firstStandby == nil {
firstStandby = standbyResult
}
continue
}
return standbyResult, nil
}
if len(output) == 0 {
lastErr = err
@ -517,6 +525,12 @@ func collectDeviceSMART(ctx context.Context, device string) (*DiskSMART, error)
Msg("Collected SMART data")
return firstParsed, nil
}
if firstStandby != nil {
log.Debug().
Str("device", firstStandby.Device).
Msg("Collected SMART standby data")
return firstStandby, nil
}
if lastErr != nil {
if errors.Is(lastErr, errSMARTDataUnavailable) {
return nil, nil
@ -573,9 +587,6 @@ func shouldRetryFreeBSDSMART(device string, result *DiskSMART, attemptIndex, att
if runtimeGOOS != "freebsd" || attemptIndex >= attemptCount-1 || result == nil {
return false
}
if result.Standby {
return false
}
if result.Temperature > 0 {
return false
}

View file

@ -499,6 +499,108 @@ func TestCollectDeviceSMARTFreeBSDPrefersTypedProbe(t *testing.T) {
}
}
func TestCollectDeviceSMARTFreeBSDFalseStandbyFallsBackToUntypedProbe(t *testing.T) {
origRun := runCommandOutput
origLook := execLookPath
origNow := timeNow
origGOOS := runtimeGOOS
t.Cleanup(func() {
runCommandOutput = origRun
execLookPath = origLook
timeNow = origNow
runtimeGOOS = origGOOS
})
fixed := time.Date(2024, 4, 7, 9, 10, 11, 0, time.UTC)
timeNow = func() time.Time { return fixed }
execLookPath = func(string) (string, error) { return "smartctl", nil }
runtimeGOOS = "freebsd"
var attempts [][]string
runCommandOutput = func(ctx context.Context, name string, args ...string) ([]byte, error) {
attempts = append(attempts, append([]string(nil), args...))
if len(args) >= 2 && args[0] == "-d" && args[1] == "sat" {
return exec.CommandContext(ctx, "sh", "-c", "exit 3").Output()
}
payload := smartctlJSON{}
payload.Device.Protocol = "ATA"
payload.SmartStatus = &struct {
Passed bool `json:"passed"`
}{Passed: true}
payload.Temperature.Current = 41
out, _ := json.Marshal(payload)
return out, nil
}
result, err := collectDeviceSMART(context.Background(), "/dev/ada0")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil || result.Standby || result.Temperature != 41 || !result.LastUpdated.Equal(fixed) {
t.Fatalf("unexpected result: %#v", result)
}
if len(attempts) != 2 {
t.Fatalf("expected 2 attempts, got %d", len(attempts))
}
if len(attempts[0]) < 2 || attempts[0][0] != "-d" || attempts[0][1] != "sat" {
t.Fatalf("expected first probe to use -d sat, got %v", attempts[0])
}
if len(attempts[1]) < 1 || attempts[1][0] == "-d" {
t.Fatalf("expected second probe to fall back to untyped smartctl, got %v", attempts[1])
}
}
func TestCollectDeviceSMARTFreeBSDStandbyPayloadFallsBackToUntypedProbe(t *testing.T) {
origRun := runCommandOutput
origLook := execLookPath
origNow := timeNow
origGOOS := runtimeGOOS
t.Cleanup(func() {
runCommandOutput = origRun
execLookPath = origLook
timeNow = origNow
runtimeGOOS = origGOOS
})
fixed := time.Date(2024, 4, 7, 10, 11, 12, 0, time.UTC)
timeNow = func() time.Time { return fixed }
execLookPath = func(string) (string, error) { return "smartctl", nil }
runtimeGOOS = "freebsd"
var attempts [][]string
runCommandOutput = func(ctx context.Context, name string, args ...string) ([]byte, error) {
attempts = append(attempts, append([]string(nil), args...))
payload := smartctlJSON{}
payload.Device.Protocol = "ATA"
payload.SmartStatus = &struct {
Passed bool `json:"passed"`
}{Passed: true}
if len(args) >= 2 && args[0] == "-d" && args[1] == "sat" {
payload.PowerMode = "STANDBY"
} else {
payload.Temperature.Current = 42
}
out, _ := json.Marshal(payload)
return out, nil
}
result, err := collectDeviceSMART(context.Background(), "/dev/ada0")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil || result.Standby || result.Temperature != 42 || !result.LastUpdated.Equal(fixed) {
t.Fatalf("unexpected result: %#v", result)
}
if len(attempts) != 2 {
t.Fatalf("expected 2 attempts, got %d", len(attempts))
}
}
func TestCollectDeviceSMARTWWN(t *testing.T) {
origRun := runCommandOutput
origLook := execLookPath