Skip to content

Commit 1c095e6

Browse files
committed
Refactor NVMe API to provide the SMART log page data
Signed-off-by: Anthony Floeder <anthony.floeder@hpe.com>
1 parent b315dec commit 1c095e6

8 files changed

Lines changed: 122 additions & 69 deletions

File tree

internal/switchtec/pkg/nvme/nvme.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package nvme
2222
import (
2323
"encoding/binary"
2424
"fmt"
25+
"math/rand"
2526
"path"
2627
"regexp"
2728
"strconv"
@@ -33,6 +34,7 @@ import (
3334
"github.com/google/uuid"
3435

3536
"github.com/NearNodeFlash/nnf-ec/internal/switchtec/pkg/switchtec"
37+
"github.com/NearNodeFlash/nnf-ec/pkg/ec"
3638
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
3739
)
3840

@@ -1169,6 +1171,31 @@ func InterpretSmartLog(log *SmartLog) sf.ResourceState {
11691171
return sf.ENABLED_RST // Healthy
11701172
}
11711173

1174+
func MangleSmartLog(log *SmartLog) {
1175+
1176+
// Occaionally managle the smart log data
1177+
if rand.Intn(100) < 10 { // 10% chance of simulating critical condition
1178+
warningType := rand.Intn(6)
1179+
switch warningType {
1180+
case 0:
1181+
log.CriticalWarning.SpareCapacity = 1
1182+
log.AvailableSpare = 5 // Below threshold
1183+
case 1:
1184+
log.CriticalWarning.Temperature = 1
1185+
log.CompositeTemperature = 358 // ~85°C
1186+
case 2:
1187+
log.CriticalWarning.Degraded = 1
1188+
case 3:
1189+
log.CriticalWarning.ReadOnly = 1
1190+
case 4:
1191+
log.CriticalWarning.BackupFailed = 1
1192+
case 5:
1193+
log.CriticalWarning.PersistentMemoryRegionReadOnly = 1
1194+
}
1195+
}
1196+
1197+
}
1198+
11721199
// Log contstants
11731200
const (
11741201
LogCdw10LogPageIdentiferMask = 0xFF
@@ -1224,3 +1251,26 @@ func (dev *Device) getLog(logPageIdentifier uint8, logSpecificField uint8, retai
12241251
return dev.ops.submitAdminPassthru(dev, &cmd, buf)
12251252

12261253
}
1254+
1255+
// LogSmartLog outputs the SMART log data in a structured way for logging.
1256+
func LogSmartLog(log ec.Logger, smartLog *SmartLog, context ...interface{}) {
1257+
if smartLog == nil {
1258+
log.Error(nil, "SMART log is nil", context...)
1259+
return
1260+
}
1261+
log.Info("SMART log data",
1262+
append(context,
1263+
"criticalWarning.spareCapacity", smartLog.CriticalWarning.SpareCapacity,
1264+
"criticalWarning.temperature", smartLog.CriticalWarning.Temperature,
1265+
"criticalWarning.degraded", smartLog.CriticalWarning.Degraded,
1266+
"criticalWarning.readOnly", smartLog.CriticalWarning.ReadOnly,
1267+
"criticalWarning.backupFailed", smartLog.CriticalWarning.BackupFailed,
1268+
"criticalWarning.persistentMemoryRegionReadOnly", smartLog.CriticalWarning.PersistentMemoryRegionReadOnly,
1269+
"availableSpare", smartLog.AvailableSpare,
1270+
"availableSpareThreshold", smartLog.AvailableSpareThreshold,
1271+
"percentageUsed", smartLog.PercentageUsed,
1272+
"compositeTemperature", smartLog.CompositeTemperature,
1273+
"mediaErrorsLo", smartLog.MediaErrorsLo,
1274+
"numberErrorLogEntriesLo", smartLog.NumberErrorLogEntriesLo,
1275+
)...)
1276+
}

pkg/manager-nvme/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ func (mgr *Manager) StorageIdControllersControllerIdGet(storageId, controllerId
12391239
return ec.NewErrNotFound().WithError(err).WithCause(fmt.Sprintf("Storage Controller fabric endpoint not found: Storage: %s Controller: %s", storageId, controllerId))
12401240
}
12411241

1242-
percentageUsage, err := s.device.GetWearLevelAsPercentageUsed()
1242+
percentageUsage, err := GetWearLevelAsPercentageUsed(s.device)
12431243
if err != nil {
12441244
return ec.NewErrInternalServerError().WithError(err).WithCause(fmt.Sprintf("Storage Controller failed to retrieve SMART data: Storage: %s", storageId))
12451245
}

pkg/manager-nvme/monitor.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,36 @@ func NewMonitor(log ec.Logger, interval time.Duration) *Monitor {
7070

7171
// Run starts the monitor loop. It periodically calls GetSmartLog on all devices.
7272
func (m *Monitor) Run() {
73-
log := m.Log
7473
for {
7574
time.Sleep(m.Interval)
7675

7776
storages := GetStorage()
78-
// log.V(2).Info("Drive monitor tick", "period", m.Interval, "device count", len(storages))
7977
for _, storage := range storages {
8078
if !storage.IsEnabled() {
81-
// log.V(2).Info("storage disabled", "slot", storage.Slot())
8279
continue
8380
}
8481

85-
state, err := storage.device.CheckSmartLogForStatus()
86-
if err != nil {
87-
log.Error(err, "smartlog request failed", "serial", storage.SerialNumber(), "slot", storage.Slot())
88-
storage.state = sf.DISABLED_RST
89-
}
90-
if state != storage.state {
91-
log.Info("smartlog state change, (see 'nnf-nvme.sh cmd smartlog' for details)", "old state", storage.state, "new state", state, "serial", storage.SerialNumber(), "slot", storage.Slot())
92-
storage.state = state
93-
}
82+
m.checkSmartLog(storage)
9483
}
9584
}
9685
}
86+
87+
func (m *Monitor) checkSmartLog(storage *Storage) {
88+
log := m.Log
89+
90+
smartLog, err := storage.device.GetSmartLog()
91+
if err != nil {
92+
log.Error(err, "smartlog request failed", "serial", storage.SerialNumber(), "slot", storage.Slot())
93+
storage.state = sf.DISABLED_RST
94+
}
95+
96+
// nvme.MangleSmartLog(smartLog)
97+
98+
state := nvme.InterpretSmartLog(smartLog)
99+
if state != storage.state {
100+
log.Info("smartlog state change", "old state", storage.state, "new state", state, "serial", storage.SerialNumber(), "slot", storage.Slot())
101+
storage.state = state
102+
103+
nvme.LogSmartLog(log, smartLog)
104+
}
105+
}

pkg/manager-nvme/nvme_api.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package nvme
2121

2222
import (
2323
"github.com/NearNodeFlash/nnf-ec/internal/switchtec/pkg/nvme"
24-
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
2524
)
2625

2726
type NvmeController interface {
@@ -65,9 +64,8 @@ type NvmeDeviceApi interface {
6564
SetNamespaceFeature(namespaceId nvme.NamespaceIdentifier, data []byte) error
6665
GetNamespaceFeature(namespaceId nvme.NamespaceIdentifier) ([]byte, error)
6766

68-
GetWearLevelAsPercentageUsed() (uint8, error)
69-
70-
CheckSmartLogForStatus() (sf.ResourceState, error)
67+
// GetSmartLog returns the raw SMART log page data
68+
GetSmartLog() (*nvme.SmartLog, error)
7169
}
7270

7371
// SecondaryControllersInitFunc -
@@ -83,3 +81,12 @@ const (
8381
VQResourceType SecondaryControllerResourceType = iota
8482
VIResourceType
8583
)
84+
85+
// GetWearLevelAsPercentageUsed returns the PercentageUsed field from the SMART log page.
86+
func GetWearLevelAsPercentageUsed(dev NvmeDeviceApi) (uint8, error) {
87+
smartLog, err := dev.GetSmartLog()
88+
if err != nil {
89+
return 0, err
90+
}
91+
return smartLog.PercentageUsed, nil
92+
}

pkg/manager-nvme/nvme_cli.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import (
3333

3434
"github.com/NearNodeFlash/nnf-ec/pkg/logging"
3535
fabric "github.com/NearNodeFlash/nnf-ec/pkg/manager-fabric"
36-
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
3736
)
3837

3938
func NewCliNvmeController() NvmeController {
@@ -376,33 +375,20 @@ func (*cliDevice) GetNamespaceFeature(namespaceId nvme.NamespaceIdentifier) ([]b
376375
return nil, nil
377376
}
378377

379-
func (d *cliDevice) GetWearLevelAsPercentageUsed() (uint8, error) {
378+
// GetSmartLog returns the raw SMART log page data
379+
func (d *cliDevice) GetSmartLog() (*nvme.SmartLog, error) {
380380
rsp, err := d.run(fmt.Sprintf("smart-log %s --output-format=binary", d.dev()))
381381
if err != nil {
382-
return 0, err
383-
}
384-
385-
log := new(nvme.SmartLog)
386-
387-
err = structex.DecodeByteBuffer(bytes.NewBuffer([]byte(rsp)), log)
388-
389-
return log.PercentageUsed, err
390-
}
391-
392-
func (d *cliDevice) CheckSmartLogForStatus() (sf.ResourceState, error) {
393-
rsp, err := d.run(fmt.Sprintf("smart-log %s --output-format=binary", d.dev()))
394-
if err != nil {
395-
return sf.DISABLED_RST, err
382+
return nil, err
396383
}
397384

398385
log := new(nvme.SmartLog)
399-
400386
err = structex.DecodeByteBuffer(bytes.NewBuffer([]byte(rsp)), log)
401387
if err != nil {
402-
return sf.DISABLED_RST, err
388+
return nil, err
403389
}
404390

405-
return nvme.InterpretSmartLog(log), err
391+
return log, nil
406392
}
407393

408394
func (d *cliDevice) run(cmd string) (string, error) {

pkg/manager-nvme/nvme_device.go

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
fabric "github.com/NearNodeFlash/nnf-ec/pkg/manager-fabric"
2828

2929
"github.com/NearNodeFlash/nnf-ec/internal/switchtec/pkg/nvme"
30-
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
3130
)
3231

3332
type SwitchtecNvmeController struct{}
@@ -248,22 +247,7 @@ func (d *nvmeDevice) GetNamespaceFeature(namespaceId nvme.NamespaceIdentifier) (
248247
return buf, nil
249248
}
250249

251-
func (d *nvmeDevice) GetWearLevelAsPercentageUsed() (uint8, error) {
252-
253-
log, err := d.dev.GetSmartLog()
254-
if err != nil {
255-
return 0, err
256-
}
257-
258-
return log.PercentageUsed, nil
259-
}
260-
261-
func (d *nvmeDevice) CheckSmartLogForStatus() (sf.ResourceState, error) {
262-
263-
sl, err := d.dev.GetSmartLog()
264-
if err != nil {
265-
return sf.DISABLED_RST, err
266-
}
267-
268-
return nvme.InterpretSmartLog(sl), nil
250+
// GetSmartLog returns the raw SMART log page data
251+
func (d *nvmeDevice) GetSmartLog() (*nvme.SmartLog, error) {
252+
return d.dev.GetSmartLog()
269253
}

pkg/manager-nvme/nvme_direct_device.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424

2525
"github.com/NearNodeFlash/nnf-ec/internal/switchtec/pkg/nvme"
2626
fabric "github.com/NearNodeFlash/nnf-ec/pkg/manager-fabric"
27-
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
2827
)
2928

3029
type DirectNvmeController struct {
@@ -143,10 +142,7 @@ func (*nvmeDirectDevice) SetNamespaceFeature(namespaceId nvme.NamespaceIdentifie
143142
panic("unimplemented")
144143
}
145144

146-
func (d *nvmeDirectDevice) GetWearLevelAsPercentageUsed() (uint8, error) {
147-
return d.cliDevice.GetWearLevelAsPercentageUsed()
148-
}
149-
150-
func (d *nvmeDirectDevice) CheckSmartLogForStatus() (sf.ResourceState, error) {
151-
return d.cliDevice.CheckSmartLogForStatus()
145+
// GetSmartLog returns the raw SMART log page data
146+
func (d *nvmeDirectDevice) GetSmartLog() (*nvme.SmartLog, error) {
147+
return d.cliDevice.GetSmartLog()
152148
}

pkg/manager-nvme/nvme_mock.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"strings"
2929

3030
"github.com/NearNodeFlash/nnf-ec/internal/switchtec/pkg/nvme"
31-
sf "github.com/NearNodeFlash/nnf-ec/pkg/rfsf/pkg/models"
3231
)
3332

3433
type MockNvmeController struct {
@@ -445,10 +444,6 @@ func (d *mockDevice) GetNamespaceFeature(namespaceId nvme.NamespaceIdentifier) (
445444
return ns.metadata, nil
446445
}
447446

448-
func (*mockDevice) GetWearLevelAsPercentageUsed() (uint8, error) {
449-
return 0, nil
450-
}
451-
452447
func (d *mockDevice) findNamespace(namespaceId nvme.NamespaceIdentifier) *mockNamespace {
453448

454449
for idx, ns := range d.namespaces {
@@ -460,6 +455,32 @@ func (d *mockDevice) findNamespace(namespaceId nvme.NamespaceIdentifier) *mockNa
460455
return nil
461456
}
462457

463-
func (*mockDevice) CheckSmartLogForStatus() (sf.ResourceState, error) {
464-
return sf.ENABLED_RST, nil
458+
// GetSmartLog returns mock SMART log page data
459+
func (d *mockDevice) GetSmartLog() (*nvme.SmartLog, error) {
460+
// Create a mock SMART log with typical healthy values
461+
log := &nvme.SmartLog{
462+
CriticalWarning: struct {
463+
SpareCapacity uint8 `bitfield:"1"`
464+
Temperature uint8 `bitfield:"1"`
465+
Degraded uint8 `bitfield:"1"`
466+
ReadOnly uint8 `bitfield:"1"`
467+
BackupFailed uint8 `bitfield:"1"`
468+
PersistentMemoryRegionReadOnly uint8 `bitfield:"1"`
469+
Reserved uint8 `bitfield:"2"`
470+
}{
471+
SpareCapacity: 0,
472+
Temperature: 0,
473+
Degraded: 0,
474+
ReadOnly: 0,
475+
BackupFailed: 0,
476+
PersistentMemoryRegionReadOnly: 0,
477+
Reserved: 0,
478+
},
479+
CompositeTemperature: 308, // ~35°C (273 + 35)
480+
AvailableSpare: 95, // 95% spare capacity available
481+
AvailableSpareThreshold: 10, // Warning at 10%
482+
PercentageUsed: 25, // 25% of device life used
483+
}
484+
485+
return log, nil
465486
}

0 commit comments

Comments
 (0)