From 58dd9d6cc242201aa9c89c516963d10e1c788907 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Mon, 14 Oct 2024 13:26:13 -0500 Subject: [PATCH] Add Supermicro OEM ComputerSystem objects This adds an OEM version of the ComputerSystem type for Supermicro systems. This enables access to the NodeManager and FixedBootOrder oem-specific objects. Signed-off-by: Sean McGinnis --- oem/smc/computersystem.go | 56 +++++++++ oem/smc/computersystem_test.go | 222 +++++++++++++++++++++++++++++++++ oem/smc/fixedbootorder.go | 31 +++++ oem/smc/fixedbootorder_test.go | 76 +++++++++++ oem/smc/nodemanager.go | 106 ++++++++++++++++ oem/smc/nodemanager_test.go | 101 +++++++++++++++ redfish/computersystem.go | 10 +- 7 files changed, 597 insertions(+), 5 deletions(-) create mode 100644 oem/smc/computersystem.go create mode 100644 oem/smc/computersystem_test.go create mode 100644 oem/smc/fixedbootorder.go create mode 100644 oem/smc/fixedbootorder_test.go create mode 100644 oem/smc/nodemanager.go create mode 100644 oem/smc/nodemanager_test.go diff --git a/oem/smc/computersystem.go b/oem/smc/computersystem.go new file mode 100644 index 00000000..c2397d43 --- /dev/null +++ b/oem/smc/computersystem.go @@ -0,0 +1,56 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "encoding/json" + + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +// ComputerSystem is a Supermicro OEM instance of a ComputerSystem. +type ComputerSystem struct { + redfish.ComputerSystem + nodeManager string + fixedBootOrder string +} + +// NodeManager gets the NodeManager for the system. +func (cs *ComputerSystem) NodeManager() (*NodeManager, error) { + return GetNodeManager(cs.GetClient(), cs.nodeManager) +} + +// FixedBootOrder gets the FixedBootOrder instance for the system. +func (cs *ComputerSystem) FixedBootOrder() (*FixedBootOrder, error) { + return GetFixedBootOrder(cs.GetClient(), cs.fixedBootOrder) +} + +// FromComputerSystem converts a standard ComputerSystem object to the OEM implementation. +func FromComputerSystem(system *redfish.ComputerSystem) (*ComputerSystem, error) { + type Oem struct { + Supermicro struct { + NodeManager common.Link `json:"NodeManager"` + FixedBootOrder common.Link `json:"FixedBootOrder"` + } `json:"Supermicro"` + } + + cs := &ComputerSystem{} + err := json.Unmarshal(system.RawData, cs) + if err != nil { + return nil, err + } + + oem := &Oem{} + err = json.Unmarshal(cs.OEM, oem) + if err != nil { + return nil, err + } + cs.nodeManager = oem.Supermicro.NodeManager.String() + cs.fixedBootOrder = oem.Supermicro.FixedBootOrder.String() + + cs.SetClient(system.GetClient()) + return cs, nil +} diff --git a/oem/smc/computersystem_test.go b/oem/smc/computersystem_test.go new file mode 100644 index 00000000..f5c936f4 --- /dev/null +++ b/oem/smc/computersystem_test.go @@ -0,0 +1,222 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stmcginnis/gofish/redfish" +) + +var computerSystemBody = `{ + "@odata.type": "#ComputerSystem.v1_14_0.ComputerSystem", + "@odata.id": "/redfish/v1/Systems/1", + "Id": "1", + "Name": "System", + "Description": "Description of server", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "SerialNumber": "S514359X4916804", + "PartNumber": "SYS-821GE-200-02-LL014", + "IndicatorLED": "Off", + "LocationIndicatorActive": false, + "SystemType": "Physical", + "BiosVersion": "2.1", + "Manufacturer": "Supermicro", + "Model": "SYS-821GE-TNHR", + "SKU": "0x1D1415D9", + "UUID": "D4216600-C32C-11EE-8000-7CC25586E492", + "ProcessorSummary": { + "Count": 2, + "Model": "Intel(R) Xeon(R) processor", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + }, + "Metrics": { + "@odata.id": "/redfish/v1/Systems/1/ProcessorSummary/ProcessorMetrics" + } + }, + "MemorySummary": { + "TotalSystemMemoryGiB": 2048, + "MemoryMirroring": "System", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + }, + "Metrics": { + "@odata.id": "/redfish/v1/Systems/1/MemorySummary/MemoryMetrics" + } + }, + "PowerState": "On", + "PowerOnDelaySeconds": 3, + "PowerOnDelaySeconds@Redfish.AllowableNumbers": [ + "3:254:1" + ], + "PowerOffDelaySeconds": 3, + "PowerOffDelaySeconds@Redfish.AllowableNumbers": [ + "3:254:1" + ], + "PowerCycleDelaySeconds": 5, + "PowerCycleDelaySeconds@Redfish.AllowableNumbers": [ + "5:254:1" + ], + "Boot": { + "BootSourceOverrideEnabled": "Disabled", + "BootSourceOverrideMode": "UEFI", + "BootSourceOverrideTarget": "Pxe", + "BootSourceOverrideTarget@Redfish.AllowableValues": [ + "None", + "Pxe", + "Floppy", + "Cd", + "Usb", + "Hdd", + "BiosSetup", + "UsbCd", + "UefiBootNext", + "UefiHttp" + ], + "BootOptions": { + "@odata.id": "/redfish/v1/Systems/1/BootOptions" + }, + "BootNext": null, + "BootOrder": [ + "Boot0003", + "Boot0004", + "Boot0005", + "Boot0006", + "Boot0002", + "Boot0001" + ] + }, + "GraphicalConsole": { + "ServiceEnabled": true, + "Port": 5900, + "MaxConcurrentSessions": 4, + "ConnectTypesSupported": [ + "KVMIP" + ] + }, + "SerialConsole": { + "MaxConcurrentSessions": 1, + "SSH": { + "ServiceEnabled": true, + "Port": 22, + "SharedWithManagerCLI": true, + "ConsoleEntryCommand": "cd system1/sol1; start", + "HotKeySequenceDisplay": "press , , and then to terminate session" + }, + "IPMI": { + "HotKeySequenceDisplay": "Press ~. - terminate connection", + "ServiceEnabled": true, + "Port": 623 + } + }, + "VirtualMediaConfig": { + "ServiceEnabled": true, + "Port": 623 + }, + "BootProgress": { + "LastState": "SystemHardwareInitializationComplete" + }, + "Processors": { + "@odata.id": "/redfish/v1/Systems/1/Processors" + }, + "Memory": { + "@odata.id": "/redfish/v1/Systems/1/Memory" + }, + "EthernetInterfaces": { + "@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces" + }, + "NetworkInterfaces": { + "@odata.id": "/redfish/v1/Systems/1/NetworkInterfaces" + }, + "SimpleStorage": { + "@odata.id": "/redfish/v1/Systems/1/SimpleStorage" + }, + "Storage": { + "@odata.id": "/redfish/v1/Systems/1/Storage" + }, + "LogServices": { + "@odata.id": "/redfish/v1/Systems/1/LogServices" + }, + "SecureBoot": { + "@odata.id": "/redfish/v1/Systems/1/SecureBoot" + }, + "Bios": { + "@odata.id": "/redfish/v1/Systems/1/Bios" + }, + "VirtualMedia": { + "@odata.id": "/redfish/v1/Managers/1/VirtualMedia" + }, + "Links": { + "Chassis": [ + { + "@odata.id": "/redfish/v1/Chassis/1" + } + ], + "ManagedBy": [ + { + "@odata.id": "/redfish/v1/Managers/1" + } + ] + }, + "Actions": { + "Oem": {}, + "#ComputerSystem.Reset": { + "target": "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset", + "@Redfish.ActionInfo": "/redfish/v1/Systems/1/ResetActionInfo" + } + }, + "Oem": { + "Supermicro": { + "NodeManager": { + "@odata.id": "/redfish/v1/Systems/1/Oem/Supermicro/NodeManager" + }, + "FixedBootOrder": { + "@odata.id": "/redfish/v1/Systems/1/Oem/Supermicro/FixedBootOrder" + }, + "@odata.type": "#SmcSystemExtensions.v1_0_0.System" + } + }, + "@odata.etag": "\"36f059d60095337115952f02709f65d6\"" +}` + +// TestComputerSystem tests the parsing of ComputerSystem objects. +func TestComputerSystem(t *testing.T) { + var cs redfish.ComputerSystem + err := json.NewDecoder(strings.NewReader(computerSystemBody)).Decode(&cs) + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + result, err := FromComputerSystem(&cs) + if err != nil { + t.Errorf("Error converting Redfish ComputerSystem to SMC ComputerSystem: %s", err) + } + + if result.ID != "1" { + t.Errorf("Received invalid ID: %s", result.ID) + } + + if result.Name != "System" { + t.Errorf("Received invalid name: %s", result.Name) + } + + if result.nodeManager != "/redfish/v1/Systems/1/Oem/Supermicro/NodeManager" { + t.Errorf("Invalid node manager link: %s", result.nodeManager) + } + + if result.fixedBootOrder != "/redfish/v1/Systems/1/Oem/Supermicro/FixedBootOrder" { + t.Errorf("Invalid fixed boot order link: %s", result.fixedBootOrder) + } +} diff --git a/oem/smc/fixedbootorder.go b/oem/smc/fixedbootorder.go new file mode 100644 index 00000000..d1dba87d --- /dev/null +++ b/oem/smc/fixedbootorder.go @@ -0,0 +1,31 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "github.com/stmcginnis/gofish/common" +) + +// FixedBootOrder is the fixed boot order information associated with the system. +// The non-OEM ComputerSystem BootOrder property does not support PATCH method +// since X13/H13 platforms Configuring system boot device order should be via +// FixedBootOrder. +// TODO: This is currently read-only in Gofish. +type FixedBootOrder struct { + common.Entity + BootModeSelected string + FixedBootOrder []string + FixedBootOrderDisabledItem []string + UEFINetwork []string + UEFINetworkDisabledItem []string + UEFIHardDisk []string + UEFIAP []string + UEFIAPDisabledItem []string +} + +// GetFixedBootOrder will get a FixedBootOrder instance from the service. +func GetFixedBootOrder(c common.Client, uri string) (*FixedBootOrder, error) { + return common.GetObject[FixedBootOrder](c, uri) +} diff --git a/oem/smc/fixedbootorder_test.go b/oem/smc/fixedbootorder_test.go new file mode 100644 index 00000000..89d3fb06 --- /dev/null +++ b/oem/smc/fixedbootorder_test.go @@ -0,0 +1,76 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "encoding/json" + "strings" + "testing" +) + +var fixedBootOrderBody = `{ + "@odata.type": "#SmcFixedBootOrder.v1_0_0.SmcFixedBootOrder", + "@odata.id": "/redfish/v1/Systems/1/Oem/Supermicro/FixedBootOrder", + "Id": "FixedBootOrder", + "Name": "Fixed Boot Order", + "BootModeSelected": "UEFI", + "FixedBootOrder": [ + "UEFI CD/DVD", + "UEFI USB CD/DVD", + "UEFI Network:(B83/D0/F0) UEFI PXE IPv4 Intel(R) Ethernet Controller X550 - 905A0839F618", + "UEFI Hard Disk:ubuntu (HDD,Port:900)", + "UEFI USB Hard Disk", + "UEFI USB Key", + "UEFI USB Floppy", + "UEFI USB Lan", + "UEFI AP:UEFI: Built-in EFI Shell" + ], + "FixedBootOrderDisabledItem": [ + "Disabled" + ], + "UEFINetwork": [ + "(B83/D0/F0) UEFI PXE IPv4 Intel(R) Ethernet Controller X550 - 905A0839F618", + "(B83/D0/F1) UEFI PXE IPv4 Intel(R) Ethernet Controller X550 - 905A0839F619", + "(B210/D0/F0) UEFI PXE IPv4 Nvidia Network Adapter - 5C:25:73:60:C5:D8 - 5C257360C5D8", + "(B210/D0/F1) UEFI PXE IPv4 Nvidia Network Adapter - 5C:25:73:60:C5:D9 - 5C257360C5D9" + ], + "UEFINetworkDisabledItem": [ + "Disabled" + ], + "UEFIHardDisk": [ + "ubuntu (HDD,Port:900)" + ], + "UEFIHardDiskDisabledItem": [ + "Disabled" + ], + "UEFIAP": [ + "UEFI: Built-in EFI Shell" + ], + "UEFIAPDisabledItem": [ + "Disabled" + ], + "@odata.etag": "\"120671877241e67076141a0d63fc7c7b\"" +}` + +// TestFixedBootOrder tests the parsing of FixedBootOrder objects. +func TestFixedBootOrder(t *testing.T) { + var result FixedBootOrder + err := json.NewDecoder(strings.NewReader(fixedBootOrderBody)).Decode(&result) + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + if result.ID != "FixedBootOrder" { + t.Errorf("Received invalid ID: %s", result.ID) + } + + if result.BootModeSelected != "UEFI" { + t.Errorf("Invalid BootModeSelected: %s", result.BootModeSelected) + } + + if len(result.FixedBootOrder) != 9 { + t.Errorf("Expected 9 fixed boot order entries, got %d", len(result.FixedBootOrder)) + } +} diff --git a/oem/smc/nodemanager.go b/oem/smc/nodemanager.go new file mode 100644 index 00000000..be0743ba --- /dev/null +++ b/oem/smc/nodemanager.go @@ -0,0 +1,106 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "encoding/json" + "fmt" + + "github.com/stmcginnis/gofish/common" +) + +type NodeManagerCapabilities struct { + DomainID string + PolicyType string + MaxConcurrentSettings int + MaxValueAfterReset uint + MinValueAfterReset uint + MaxCorrectionTime uint64 + MinCorrectionTime uint64 + MaxReportingPeriod uint64 + MinReportingPeriod uint64 + DomainLimitingScope int +} + +type NodeManagerStatistics struct { + Mode string + DomainID string + CurrentValue uint64 + MaximumValue uint64 + MinimumValue uint64 + AverageValue uint64 + ReportingPeriod uint64 +} + +type NodeManagerPolicy struct { + DomainID string + PolicyID uint + PolicyType uint + PolicyExceptionActions uint + PowerLimit uint + CorrectionTimeLimit uint + PolicyTriggerLimit uint + StatReportingPeriod uint +} + +// NodeManager is the node manager instance associated with the system. +// This Redfish API can only be supported on Intel platforms with Intel ME. +type NodeManager struct { + common.Entity + Capabilities []NodeManagerCapabilities + Statistics []NodeManagerStatistics + IntelPsysEnabled bool + IntelPsysSupported bool + Version struct { + IntelNMVersion string + IPMIVersion string + PatchVersion uint + MajorRevision uint + MinorRevision uint + } + SelfTest struct { + MajorCode uint + MinorCode uint + ImageFlags string + } + Policy []NodeManagerPolicy + + clearAllPoliciesTarget string +} + +// UnmarshalJSON unmarshals a NodeManager object from the raw JSON. +func (nm *NodeManager) UnmarshalJSON(b []byte) error { + type temp NodeManager + var t struct { + temp + Actions struct { + ClearAllPolicies common.ActionTarget `json:"#SmcNodeManager.ClearAllPolicies"` + } + } + + err := json.Unmarshal(b, &t) + if err != nil { + return err + } + + *nm = NodeManager(t.temp) + nm.clearAllPoliciesTarget = t.Actions.ClearAllPolicies.Target + + return nil +} + +// GetNodeManager will get a NodeManager instance from the service. +func GetNodeManager(c common.Client, uri string) (*NodeManager, error) { + return common.GetObject[NodeManager](c, uri) +} + +// ClearAllPolicies clears the configured policies of the NodeManager. +func (nm *NodeManager) ClearAllPolicies() error { + if nm.clearAllPoliciesTarget == "" { + return fmt.Errorf("ClearAllPolicies is not supported by this system") + } + + return nm.Post(nm.clearAllPoliciesTarget, nil) +} diff --git a/oem/smc/nodemanager_test.go b/oem/smc/nodemanager_test.go new file mode 100644 index 00000000..d04ef93a --- /dev/null +++ b/oem/smc/nodemanager_test.go @@ -0,0 +1,101 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package smc + +import ( + "encoding/json" + "strings" + "testing" +) + +var nodeManagerBody = `{ + "@odata.type": "#SmcNodeManager.v1_0_1.SmcNodeManager", + "@odata.id": "/redfish/v1/Systems/1/Oem/Supermicro/NodeManager", + "Id": "Node Manager", + "Name": "Node Manager", + "Capabilities": [ + { + "DomainID": "Entire platform", + "PolicyType": "Power Control Policy", + "MaxConcurrentSettings": 16, + "MaxValueAfterReset": 32767, + "MinValueAfterReset": 1, + "MaxCorrectionTime": 600000, + "MinCorrectionTime": 3000, + "MaxReportingPeriod": 3600, + "MinReportingPeriod": 1, + "DomainLimitingScope": 128 + } + ], + "Statistics": [ + { + "Mode": "Global power statistics", + "DomainID": "Entire platform", + "Timestamp": "2024-10-14T16:58:30:+00:00", + "CurrentValue": 8934, + "MaximumValue": 9587, + "MinimumValue": 88, + "AverageValue": 4692, + "ReportingPeriod": 94032 + } + ], + "IntelPsysEnabled": false, + "IntelPsysSupported": false, + "Version": { + "IntelNMVersion": "Supported Intel NM 6.0", + "IPMIVersion": "Intel NM IPMI version 6.0", + "PatchVersion": 0, + "MajorRevision": 6, + "MinorRevision": 20 + }, + "Selftest": { + "MajorCode": 85, + "MinorCode": 0, + "ImageFlags": "Operational" + }, + "Policy": [ + { + "DomainID": "Entire platform", + "PolicyID": 1, + "PolicyType": 0, + "PolicyExceptionActions": 0, + "PowerLimit": 0, + "CorrectionTimeLimit": 0, + "PolicyTriggerLimit": 0, + "StatReportingPeriod": 0 + } + ], + "Actions": { + "#SmcNodeManager.ClearAllPolicies": { + "target": "/redfish/v1/Systems/1/Oem/Supermicro/NodeManager/Actions/SmcNodeManager.ClearAllPolicies" + } + }, + "@odata.etag": "\"ec6bda0f0947b85ca44e4a068acd2e66\"" +}` + +// TestNodeManager tests the parsing of NodeManager objects. +func TestNodeManager(t *testing.T) { + var result NodeManager + err := json.NewDecoder(strings.NewReader(nodeManagerBody)).Decode(&result) + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + if result.ID != "Node Manager" { + t.Errorf("Received invalid ID: %s", result.ID) + } + + if result.clearAllPoliciesTarget != "/redfish/v1/Systems/1/Oem/Supermicro/NodeManager/Actions/SmcNodeManager.ClearAllPolicies" { + t.Errorf("Invalid clear all policies link: %s", result.clearAllPoliciesTarget) + } + + if len(result.Statistics) != 1 { + t.Errorf("Expected 1 statistic, got %d", len(result.Statistics)) + } + + if result.Statistics[0].Mode != "Global power statistics" { + t.Errorf("Expected 'Global power statistics', got %s", result.Statistics[0].Mode) + } +} diff --git a/redfish/computersystem.go b/redfish/computersystem.go index 32ab67f8..c6b49d32 100644 --- a/redfish/computersystem.go +++ b/redfish/computersystem.go @@ -931,8 +931,8 @@ type ComputerSystem struct { // setDefaultBootOrderTarget is the URL to send SetDefaultBootOrder actions to. setDefaultBootOrderTarget string settingsTarget 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. + RawData []byte } // UnmarshalJSON unmarshals a ComputerSystem object from the raw JSON. @@ -1020,7 +1020,7 @@ func (computersystem *ComputerSystem) UnmarshalJSON(b []byte) error { } // This is a read/write object, so we need to save the raw object data for later - computersystem.rawData = b + computersystem.RawData = b return nil } @@ -1030,7 +1030,7 @@ func (computersystem *ComputerSystem) Update() error { // Get a representation of the object's original state so we can find what // to update. cs := new(ComputerSystem) - err := cs.UnmarshalJSON(computersystem.rawData) + err := cs.UnmarshalJSON(computersystem.RawData) if err != nil { return err } @@ -1186,7 +1186,7 @@ func (computersystem *ComputerSystem) UpdateBootAttributesApplyAt(attrs Settings // Get a representation of the object's original state so we can find what // to update. original := new(Bios) - err := original.UnmarshalJSON(computersystem.rawData) + err := original.UnmarshalJSON(computersystem.RawData) if err != nil { return err }