Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redfish/drive: expose oem data and actions #366

Merged
merged 8 commits into from
Oct 15, 2024
Merged
83 changes: 83 additions & 0 deletions oem/smc/drive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// SPDX-License-Identifier: BSD-3-Clause
//

package smc
Matt1360 marked this conversation as resolved.
Show resolved Hide resolved

import (
"encoding/json"
"errors"

"github.com/stmcginnis/gofish/common"
"github.com/stmcginnis/gofish/redfish"
)

// ErrActionNotSupported is returned when the requested OEM-specific action
// does not appear to be supported. This might happen when a device is new
// or upgraded to a new firmware that follows the DMTF standards.
var ErrActionNotSupported = errors.New("oem-specific action unsupported")

// Drive extends a redfish.Drive for additional OEM fields
type Drive struct {
redfish.Drive

// Fields from the SMC OEM section
Temperature int
PercentageDriveLifeUsed int
DriveFunctional bool

// indicateTarget is the uri to hit to change the light state
indicateTarget string
}

// FromDrive returns an OEM-extended redfish drive
func FromDrive(drive *redfish.Drive) (Drive, error) {
smcDrive := Drive{
Drive: *drive,
}

var t struct {
Oem struct {
Supermicro struct {
Temperature int
PercentageDriveLifeUsed int
DriveFunctional bool
} `json:"Supermicro"`
} `json:"Oem"`
Actions struct {
Oem struct {
DriveIndicate common.ActionTarget `json:"#Drive.Indicate"`
SmcDriveIndicate common.ActionTarget `json:"#SmcDrive.Indicate"`
} `json:"Oem"`
} `json:"Actions"`
}

// Populate the Oem data
if err := json.Unmarshal(drive.RawData, &t); err != nil {
return smcDrive, err
}

smcDrive.Temperature = t.Oem.Supermicro.Temperature
smcDrive.PercentageDriveLifeUsed = t.Oem.Supermicro.PercentageDriveLifeUsed
smcDrive.DriveFunctional = t.Oem.Supermicro.DriveFunctional

// We check both the SmcDriveIndicate and the DriveIndicate targets
// in the Oem sections - certain models and bmc firmwares will mix
// these up, so we check both
smcDrive.indicateTarget = t.Actions.Oem.DriveIndicate.Target
stmcginnis marked this conversation as resolved.
Show resolved Hide resolved
if t.Actions.Oem.SmcDriveIndicate.Target != "" {
smcDrive.indicateTarget = t.Actions.Oem.SmcDriveIndicate.Target
}

return smcDrive, nil
}

// Indicate will set the indicator light activity, true for on, false for off
func (d *Drive) Indicate(active bool) error {
// Return a common error to let the user try falling back on the normal gofish path
if d.indicateTarget == "" {
return ErrActionNotSupported
}

return d.Post(d.indicateTarget, map[string]interface{}{"Active": active})
}
71 changes: 71 additions & 0 deletions oem/smc/drive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// SPDX-License-Identifier: BSD-3-Clause
//

package smc

import (
"encoding/json"
"testing"

"github.com/stmcginnis/gofish/redfish"
)

var smcDriveBody = `{
"@odata.type": "#Drive.v1_6_2.Drive",
"@odata.id": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.22",
"Name": "Disk.Bay.22",
"Id": "22",
"Manufacturer": "INTEL",
"SerialNumber": "PHLWOOFMEOWIAMCATDOG",
"Model": "INTEL SSDPE2KX080T8O",
"StatusIndicator": "OK",
"FailurePredicted": false,
"CapacityBytes": 8001563222016,
"CapableSpeedGbs": 31.5,
"Oem": {
"Supermicro": {
"@odata.type": "#SmcDriveExtensions.v1_0_0.Drive",
"Temperature": 33,
"PercentageDriveLifeUsed": 3,
"DriveFunctional": true
}
},
"IndicatorLED": "Off",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"Links": {
"Volumes": []
},
"Actions": {
"Oem": {
"#Drive.Indicate": {
"target": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.22/Actions/Oem/Drive.Indicate",
"@Redfish.ActionInfo": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.22/IndicateActionInfo"
}
}
}
}`

// TestSmcDriveOem tests the parsing of the Drive oem field
func TestSmcDriveOem(t *testing.T) {
drive := &redfish.Drive{}
if err := json.Unmarshal([]byte(smcDriveBody), drive); err != nil {
t.Fatalf("error decoding json: %v", err)
}

smcDrive, err := FromDrive(drive)
if err != nil {
t.Fatalf("error getting oem info from drive: %v", err)
}

if smcDrive.Temperature != 33 {
t.Errorf("unexpected oem drive temperature: %d", smcDrive.Temperature)
}

if smcDrive.indicateTarget != "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.22/Actions/Oem/Drive.Indicate" {
t.Errorf("unexpected oem drive indicator target: %s", smcDrive.indicateTarget)
}
}
12 changes: 8 additions & 4 deletions redfish/drive.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ type Drive struct {
// WriteCacheEnabled shall indicate whether the drive
// write cache is enabled.
WriteCacheEnabled bool
// Oem is all the available OEM information for the drive
Oem json.RawMessage

// ActiveSoftwareImage shall contain a link a resource of type SoftwareInventory that represents the active drive
// firmware image.
Expand Down Expand Up @@ -331,8 +333,9 @@ type Drive struct {
StoragePoolsCount int
// secureEraseTarget is the URL for SecureErase actions.
secureEraseTarget string
// rawData holds the original serialized JSON so we can compare updates.
rawData []byte
// RawData holds the original serialized JSON so we can compare updates
// as well as access Oem values in the oem package.
RawData []byte
}

// UnmarshalJSON unmarshals a Drive object from the raw JSON.
Expand Down Expand Up @@ -391,6 +394,7 @@ func (drive *Drive) UnmarshalJSON(b []byte) error {
drive.EndpointsCount = t.Links.EndpointCount
drive.networkDeviceFunctions = t.Links.NetworkDeviceFunctions.ToStrings()
drive.NetworkDeviceFunctionsCount = t.Links.NetworkDeviceFunctionsCount
drive.Oem = t.Oem
drive.pcieFunctions = t.Links.PCIeFunctions.ToStrings()
drive.PCIeFunctionCount = t.Links.PCIeFunctionsCount
drive.softwareImages = t.Links.SoftwareImages.ToStrings()
Expand All @@ -404,7 +408,7 @@ func (drive *Drive) UnmarshalJSON(b []byte) error {
drive.secureEraseTarget = t.Actions.SecureErase.Target

// This is a read/write object, so we need to save the raw object data for later
drive.rawData = b
drive.RawData = b

return nil
}
Expand All @@ -414,7 +418,7 @@ func (drive *Drive) Update() error {
// Get a representation of the object's original state so we can find what
// to update.
original := new(Drive)
err := original.UnmarshalJSON(drive.rawData)
err := original.UnmarshalJSON(drive.RawData)
if err != nil {
return err
}
Expand Down