fix(hub): remove stale smart_devices records when a drive is no longer reported (#2178)

* Fix duplicate /dev/sdg-style entries

* only prune devices after complete refreshes

---------

Co-authored-by: henrygd <hank@henrygd.me>
This commit is contained in:
Sven van Ginkel
2026-08-15 19:40:30 +02:00
committed by GitHub
parent 3337dff64b
commit eb5dd230cf
11 changed files with 273 additions and 34 deletions

View File

@@ -166,14 +166,16 @@ type GetSmartDataHandler struct{}
func (h *GetSmartDataHandler) Handle(hctx *HandlerContext) error {
if hctx.Agent.smartManager == nil {
// return empty map to indicate no data
return hctx.SendResponse(map[string]smart.SmartData{}, hctx.RequestID)
return hctx.SendResponse(smart.SmartDataResponse{Data: map[string]smart.SmartData{}}, hctx.RequestID)
}
if err := hctx.Agent.smartManager.Refresh(false); err != nil {
complete, err := hctx.Agent.smartManager.Refresh(false)
if err != nil {
slog.Debug("smart refresh failed", "err", err)
}
data := hctx.Agent.smartManager.GetCurrentData()
return hctx.SendResponse(data, hctx.RequestID)
return hctx.SendResponse(smart.SmartDataResponse{
Data: hctx.Agent.smartManager.GetCurrentData(),
Complete: complete,
}, hctx.RequestID)
}
////////////////////////////////////////////////////////////////////////////

View File

@@ -7,6 +7,7 @@ import (
"github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/smart"
"github.com/stretchr/testify/assert"
)
@@ -17,6 +18,18 @@ type MockHandler struct {
handleFunc func(ctx *HandlerContext) error
}
func TestNewAgentResponseSmartData(t *testing.T) {
response := newAgentResponse(smart.SmartDataResponse{
Data: map[string]smart.SmartData{
"AAA": {SerialNumber: "AAA"},
},
Complete: true,
}, nil)
assert.Equal(t, "AAA", response.SmartData["AAA"].SerialNumber)
assert.True(t, response.SmartComplete)
}
func (m *MockHandler) Handle(ctx *HandlerContext) error {
if m.handleFunc != nil {
return m.handleFunc(ctx)

View File

@@ -21,6 +21,9 @@ func newAgentResponse(data any, requestID *uint32) common.AgentResponse {
response.String = &v
case map[string]smart.SmartData:
response.SmartData = v
case smart.SmartDataResponse:
response.SmartData = v.Data
response.SmartComplete = v.Complete
case systemd.ServiceDetails:
response.ServiceInfo = v
default:

View File

@@ -70,8 +70,9 @@ type deviceKey struct {
var errNoValidSmartData = fmt.Errorf("no valid SMART data found") // Error for missing data
// Refresh updates SMART data for all known devices
func (sm *SmartManager) Refresh(forceScan bool) error {
// Refresh updates SMART data for all known devices and reports whether every
// discovered device was collected successfully.
func (sm *SmartManager) Refresh(forceScan bool) (bool, error) {
sm.refreshMutex.Lock()
defer sm.refreshMutex.Unlock()
@@ -92,7 +93,7 @@ func (sm *SmartManager) Refresh(forceScan bool) error {
}
}
return sm.resolveRefreshError(scanErr, collectErr)
return scanErr == nil && collectErr == nil, sm.resolveRefreshError(scanErr, collectErr)
}
// devicesSnapshot returns a copy of the current device slice to avoid iterating