mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Retry FreeBSD SMART probes after false standby (#1254)
This commit is contained in:
parent
48f4438d23
commit
ec93de1a59
2 changed files with 118 additions and 5 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue