Skip to content

Commit

Permalink
Add Supermicro UpdateService object (#375)
Browse files Browse the repository at this point in the history
This adds an OEM version of the UpdateService to expose some
OEM-specific actions and properties.

Signed-off-by: Sean McGinnis <[email protected]>
  • Loading branch information
stmcginnis authored Oct 17, 2024
1 parent 7257532 commit 4c227db
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 3 deletions.
191 changes: 191 additions & 0 deletions oem/smc/updateservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//
// SPDX-License-Identifier: BSD-3-Clause
//

package smc

import (
"encoding/json"
"errors"

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

type SSLCert struct {
common.Entity

ValidFrom string
GoodThrough string `json:"GoodTHRU"`

uploadTarget string
}

// UnmarshalJSON unmarshals a UpdateService object from the raw JSON.
func (cert *SSLCert) UnmarshalJSON(b []byte) error {
type temp SSLCert
var t struct {
temp
Actions struct {
Upload common.ActionTarget `json:"#SmcSSLCert.Upload"`
}
}

err := json.Unmarshal(b, &t)
if err != nil {
return err
}

*cert = SSLCert(t.temp)
cert.uploadTarget = t.Actions.Upload.Target

return nil
}

// GetSSLCert will get the SSLCert instance from the Redfish
// service.
func GetSSLCert(c common.Client, uri string) (*SSLCert, error) {
return common.GetObject[SSLCert](c, uri)
}

// Upload installs an SSL cert.
// NOTE: This is probably not correct. The jsonschema reported by SMC does not
// include any parameters for this action. That seems very unlikely, so expect
// this to fail.
func (cert *SSLCert) Upload() error {
if cert.uploadTarget == "" {
return errors.New("upload is not supported by this system")
}

return cert.Post(cert.uploadTarget, nil)
}

type IPMIConfig struct {
common.Entity

uploadTarget string
downloadTarget string
}

// UnmarshalJSON unmarshals a UpdateService object from the raw JSON.
func (ipmi *IPMIConfig) UnmarshalJSON(b []byte) error {
type temp IPMIConfig
var t struct {
temp
Actions struct {
Upload common.ActionTarget `json:"#SmcIPMIConfig.Upload"`
Download common.ActionTarget `json:"#SmcIPMIConfig.Download"`
}
}

err := json.Unmarshal(b, &t)
if err != nil {
return err
}

*ipmi = IPMIConfig(t.temp)
ipmi.uploadTarget = t.Actions.Upload.Target
ipmi.downloadTarget = t.Actions.Download.Target

return nil
}

// GetIPMIConfig will get the IPMIConfig instance from the Redfish
// service.
func GetIPMIConfig(c common.Client, uri string) (*IPMIConfig, error) {
return common.GetObject[IPMIConfig](c, uri)
}

// Upload restores a saved IPMI configuration.
// NOTE: This is probably not correct. The jsonschema reported by SMC does not
// include any parameters for this action. That seems very unlikely, so expect
// this to fail.
func (ipmi *IPMIConfig) Upload() error {
if ipmi.uploadTarget == "" {
return errors.New("upload is not supported by this system")
}

return ipmi.Post(ipmi.uploadTarget, nil)
}

// Download saves the current IPMI configuration.
// NOTE: This is probably not correct. The jsonschema reported by SMC does not
// include any parameters for this action. That seems very unlikely, so expect
// this to fail.
func (ipmi *IPMIConfig) Download() error {
if ipmi.downloadTarget == "" {
return errors.New("download is not supported by this system")
}

return ipmi.Post(ipmi.downloadTarget, nil)
}

// UpdateService is the license manager instance associated with the system.
type UpdateService struct {
redfish.UpdateService

sslCert string
ipmiConfig string

installTarget string
}

// FromUpdateService gets the OEM instance of the UpdateService.
func FromUpdateService(updateService *redfish.UpdateService) (*UpdateService, error) {
us := UpdateService{
UpdateService: *updateService,
}

var t struct {
Actions struct {
Oem struct {
Install common.ActionTarget `json:"#SmcUpdateService.Install"`
}
}
Oem struct {
Supermicro struct {
SSLCert common.Link
IPMIConfig common.Link
}
}
}

err := json.Unmarshal(updateService.RawData, &t)
if err != nil {
return nil, err
}

us.sslCert = t.Oem.Supermicro.SSLCert.String()
us.ipmiConfig = t.Oem.Supermicro.IPMIConfig.String()

us.installTarget = t.Actions.Oem.Install.Target

return &us, nil
}

// GetUpdateService will get a UpdateService instance from the service.
func GetUpdateService(c common.Client, uri string) (*UpdateService, error) {
return common.GetObject[UpdateService](c, uri)
}

// ActivateLicense performs the ActivateLicense action of the UpdateService.
func (us *UpdateService) Install(targets, installOptions []string) error {
if us.installTarget == "" {
return errors.New("Install is not supported by this system")
}

return us.Post(us.installTarget, map[string]any{
"Targets": targets,
"InstallOptions": installOptions,
})
}

// SSLCert will get the SSLCert information from the service.
func (us *UpdateService) SSLCert() (*SSLCert, error) {
return GetSSLCert(us.GetClient(), us.sslCert)
}

// IPMIConfig will get the IPMIConfig information from the service.
func (us *UpdateService) IPMIConfig() (*IPMIConfig, error) {
return GetIPMIConfig(us.GetClient(), us.ipmiConfig)
}
86 changes: 86 additions & 0 deletions oem/smc/updateservice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// SPDX-License-Identifier: BSD-3-Clause
//

package smc

import (
"encoding/json"
"testing"

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

var updateServiceBody = `{
"@odata.type": "#UpdateService.v1_8_4.UpdateService",
"@odata.id": "/redfish/v1/UpdateService",
"Id": "UpdateService",
"Name": "Update Service",
"Description": "Service for updating firmware and includes inventory of firmware",
"Status": {
"State": "Enabled",
"Health": "OK",
"HealthRollup": "OK"
},
"ServiceEnabled": true,
"MultipartHttpPushUri": "/redfish/v1/UpdateService/upload",
"FirmwareInventory": {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory"
},
"Actions": {
"Oem": {
"#SmcUpdateService.Install": {
"target": "/redfish/v1/UpdateService/Actions/Oem/SmcUpdateService.Install",
"@Redfish.ActionInfo": "/redfish/v1/UpdateService/Oem/Supermicro/InstallActionInfo"
}
},
"#UpdateService.SimpleUpdate": {
"target": "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
"@Redfish.ActionInfo": "/redfish/v1/UpdateService/SimpleUpdateActionInfo"
},
"#UpdateService.StartUpdate": {
"target": "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate"
}
},
"Oem": {
"Supermicro": {
"@odata.type": "#SmcUpdateServiceExtensions.v1_0_0.UpdateService",
"SSLCert": {
"@odata.id": "/redfish/v1/UpdateService/Oem/Supermicro/SSLCert"
},
"IPMIConfig": {
"@odata.id": "/redfish/v1/UpdateService/Oem/Supermicro/IPMIConfig"
}
}
},
"@odata.etag": "\"e9b94401dae9992fef2e71ef30cbcfdc\""
}`

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

updateService, err := FromUpdateService(us)
if err != nil {
t.Fatalf("error getting oem object: %v", err)
}

if updateService.ID != "UpdateService" {
t.Errorf("unexpected ID: %s", updateService.ID)
}

if updateService.installTarget != "/redfish/v1/UpdateService/Actions/Oem/SmcUpdateService.Install" {
t.Errorf("unexpected install target: %s", updateService.installTarget)
}

if updateService.sslCert != "/redfish/v1/UpdateService/Oem/Supermicro/SSLCert" {
t.Errorf("unexpected ssl cert link: %s", updateService.installTarget)
}

if updateService.ipmiConfig != "/redfish/v1/UpdateService/Oem/Supermicro/IPMIConfig" {
t.Errorf("unexpected ipmi config link: %s", updateService.installTarget)
}
}
6 changes: 3 additions & 3 deletions redfish/updateservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ type UpdateService struct {
// this object contains shall conform to the Redfish Specification
// described requirements.
Oem json.RawMessage
// rawData holds the original serialized JSON so we can compare updates.
rawData []byte
// RawData holds the original serialized JSON so we can compare updates.
RawData []byte
}

// UnmarshalJSON unmarshals a UpdateService object from the raw JSON.
Expand Down Expand Up @@ -243,7 +243,7 @@ func (updateService *UpdateService) UnmarshalJSON(b []byte) error {
updateService.TransferProtocol = t.Actions.SimpleUpdate.AllowableValues
updateService.OemActions = t.Actions.Oem

updateService.rawData = b
updateService.RawData = b

return nil
}
Expand Down

0 comments on commit 4c227db

Please sign in to comment.