Skip to content

Commit

Permalink
Add Supermicro Drive object (#366)
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Vandermeulen <[email protected]>
  • Loading branch information
Matt1360 authored Oct 15, 2024
1 parent 0b7a5e2 commit 4b8c27e
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 4 deletions.
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

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
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

0 comments on commit 4b8c27e

Please sign in to comment.