From 58a9e90fbbde319283303f07e7a41938a9550a41 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Sun, 20 Oct 2024 07:57:11 -0500 Subject: [PATCH] Add some AMI OEM objects (#377) This is far from complete, but adds some basic OEM objects for AMI-based Redfish implementations. Signed-off-by: Sean McGinnis --- oem/ami/accountservice.go | 86 +++++++++++++++ oem/ami/accountservice_test.go | 102 ++++++++++++++++++ oem/ami/computersystem.go | 141 ++++++++++++++++++++++++ oem/ami/computersystem_test.go | 192 +++++++++++++++++++++++++++++++++ oem/ami/eventservice.go | 52 +++++++++ oem/ami/eventservice_test.go | 123 +++++++++++++++++++++ oem/ami/updateservice.go | 162 ++++++++++++++++++++++++++++ oem/ami/updateservice_test.go | 100 +++++++++++++++++ oem/smc/updateservice.go | 2 +- redfish/eventservice.go | 1 - 10 files changed, 959 insertions(+), 2 deletions(-) create mode 100644 oem/ami/accountservice.go create mode 100644 oem/ami/accountservice_test.go create mode 100644 oem/ami/computersystem.go create mode 100644 oem/ami/computersystem_test.go create mode 100644 oem/ami/eventservice.go create mode 100644 oem/ami/eventservice_test.go create mode 100644 oem/ami/updateservice.go create mode 100644 oem/ami/updateservice_test.go diff --git a/oem/ami/accountservice.go b/oem/ami/accountservice.go new file mode 100644 index 00000000..993c84cc --- /dev/null +++ b/oem/ami/accountservice.go @@ -0,0 +1,86 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +// PAMOrder is the PAM modules used for authentication. +type PAMOrder string + +const ( + // IPMIPAMOrder specifies IPMI authentication. + IPMIPAMOrder PAMOrder = "IPMI" + // LDAPPAMOrder specifies LDAP authentication. + LDAPPAMOrder PAMOrder = "LDAP" + // ACTIVEDIRECTORYPAMOrder specifies ACTIVE DIRECTORY authentication. + ACTIVEDIRECTORYPAMOrder PAMOrder = "ACTIVE DIRECTORY" + // RADIUSPAMOrder specifies RADIUS authentication. + RADIUSPAMOrder PAMOrder = "RADIUS" +) + +// AccountServiceConfigurations allows additional configuring of the AMI AccountService. +type AccountServiceConfigurations struct { + common.Entity + // ODataContext is the odata context. + ODataContext string `json:"@odata.context"` + // ODataEtag is the odata etag. + ODataEtag string `json:"@odata.etag"` + // ODataType is the odata type. + ODataType string `json:"@odata.type"` + // Description provides a description of this resource. + Description string + // PAMEnabled indicates whether or not PAM authentication should be used when authenticating Redfish requests. + PAMEnabled bool + // PAMOrder is an array that represents the order the PAM modules will be checked for authentication. + PAMOrder []PAMOrder +} + +// GetAccountServiceConfigurations will get an AccountServiceConfigurations instance from the Redfish +// service. +func GetAccountServiceConfigurations(c common.Client, uri string) (*AccountServiceConfigurations, error) { + return common.GetObject[AccountServiceConfigurations](c, uri) +} + +// AccountService is an AMI OEM instance of an AccountService. +type AccountService struct { + redfish.AccountService + + configuration string +} + +// FromAccountService converts a standard AccountService object to the OEM implementation. +func FromAccountService(accountService *redfish.AccountService) (*AccountService, error) { + as := AccountService{ + AccountService: *accountService, + } + + var t struct { + Oem struct { + AMI struct { + Configurtion common.Link `json:"Configuration"` + } `json:"AMI"` + } `json:"Oem"` + } + + err := json.Unmarshal(accountService.RawData, &t) + if err != nil { + return nil, err + } + + as.configuration = t.Oem.AMI.Configurtion.String() + as.SetClient(accountService.GetClient()) + + return &as, nil +} + +// Configuration will get the AccountServiceConfigurations for this AccountService. +func (as *AccountService) Configuration() (*AccountServiceConfigurations, error) { + return GetAccountServiceConfigurations(as.GetClient(), as.configuration) +} diff --git a/oem/ami/accountservice_test.go b/oem/ami/accountservice_test.go new file mode 100644 index 00000000..c06be518 --- /dev/null +++ b/oem/ami/accountservice_test.go @@ -0,0 +1,102 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stmcginnis/gofish/redfish" +) + +var accountServiceBody = `{ + "@odata.type": "#AccountService.v1_7_2.AccountService", + "@odata.id": "/redfish/v1/AccountService", + "Id": "AccountService", + "Name": "Account Service", + "Oem": { + "Ami": { + "@odata.type": "#AMIAccountService.v1_0_0.AMIAccountService", + "Configuration": { + "@odata.id": "/redfish/v1/AccountService/Oem/Ami/Configurations" + } + } + }, + "PrivilegeMap": { + "@odata.id": "/redfish/v1/Registries/Redfish_1.4.0_PrivilegeRegistry.json" + }, + "Roles": { + "@odata.id": "/redfish/v1/AccountService/Roles" + }, + "ServiceEnabled": true, + "Status": { + "Health": "OK", + "State": "Enabled" + } +}` + +// TestAMIAccountService tests the parsing of the AccountService. +func TestAMIAccountService(t *testing.T) { + as := &redfish.AccountService{} + if err := json.Unmarshal([]byte(accountServiceBody), as); err != nil { + t.Fatalf("error decoding json: %v", err) + } + + accountService, err := FromAccountService(as) + if err != nil { + t.Fatalf("error getting oem info: %v", err) + } + + if accountService.ID != "AccountService" { + t.Errorf("unexpected ID: %s", accountService.ID) + } + + if accountService.configuration != "/redfish/v1/AccountService/Oem/Ami/Configurations" { + t.Errorf("unexpected configuration link: %s", accountService.configuration) + } +} + +var accountServiceConfigurationsBody = `{ + "@odata.context": "/redfish/v1/$metadata#AMIAccountServiceConfigurations.AMIAccountServiceConfigurations", + "@odata.etag": "\"1729105654\"", + "@odata.id": "/redfish/v1/AccountService/Oem/Ami/Configurations", + "@odata.type": "#AMIAccountServiceConfigurations.v1_0_0.AMIAccountServiceConfigurations", + "Id": "Configurations", + "Name": "AccountService Configurations", + "PAMEnabled": true, + "PAMOrder": [ + "IPMI", + "LDAP", + "ACTIVE DIRECTORY", + "RADIUS" + ] +}` + +// TestAMIAccountServiceConfigurations tests the parsing of the AccountServiceConfigurations. +func TestAMIAccountServiceConfigurations(t *testing.T) { + var result AccountServiceConfigurations + err := json.NewDecoder(strings.NewReader(accountServiceConfigurationsBody)).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + if result.ID != "Configurations" { + t.Errorf("unexpected ID: %s", result.ID) + } + + if !result.PAMEnabled { + t.Errorf("unexpected PAMEnabled: %t", result.PAMEnabled) + } + + if len(result.PAMOrder) != 4 { + t.Errorf("unexpected PAMOrder length: %d", len(result.PAMOrder)) + } + + if result.PAMOrder[0] != "IPMI" { + t.Errorf("unexpected PAMOrder[0]: %s", result.PAMOrder[0]) + } +} diff --git a/oem/ami/computersystem.go b/oem/ami/computersystem.go new file mode 100644 index 00000000..0e147cce --- /dev/null +++ b/oem/ami/computersystem.go @@ -0,0 +1,141 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +// ManagerBootMode is is the boot mode of the manager. +type ManagerBootMode string + +const ( + // NoneManagerBootMode Added None in Boot Option + NoneManagerBootMode ManagerBootMode = "None" + // SoftResetManagerBootMode Added SoftReset in Boot Option + SoftResetManagerBootMode ManagerBootMode = "SoftReset" + // ResetTimeoutManagerBootMode ResetTimeout support is Boot Option + ResetTimeoutManagerBootMode ManagerBootMode = "ResetTimeout" +) + +// AMIBIOSInventoryCRC provides the information related to inventory data/ +// +//nolint:revive +type AMIBIOSInventoryCRC struct { + // Bios provides the information related to inventory data. + Bios Bios + // ManagerBootConfiguration indicates the properties related to ManagerBoot + ManagerBootConfiguration ManagerBootConfiguration +} + +// BiosTableis the root for BiosTable information. +type BiosTable struct { + common.Entity + // ODataContext is the odata context. + ODataContext string `json:"@odata.context"` + // ODataType is the odata type. + ODataType string `json:"@odata.type"` + // Description provides a description of this resource. + Description string + // FilesContent contains the contents of the BiosTable file. + FilesContent string +} + +// TableTag contains the TableTag informations. +type TableTag struct { + // TableType shall contain a string representing the TableTag. + TableType string + // Value shall contains the value for the corresponding TableTag. + Value string +} + +// BiosTableTags is the root for TableTags information. +type BiosTableTags struct { + common.Entity + // ODataContext is the odata context. + ODataContext string `json:"@odata.context"` + // ODataType is the odata type. + ODataType string `json:"@odata.type"` + // Description provides a description of this resource. + Description string + // NumberofTables contains the number of TableTags present. + NumberofTables string + // TableTags contains the TableTags informations. + TableTags []TableTag +} + +// Bios +type Bios struct { + // BiosTable provides the information related to BiosTable + BiosTable BiosTable + // BiosTableTags provides the information related to BiosTableTags. + BiosTableTags BiosTableTags + // Inventory provides the information related to inventory data Crc value. + Inventory Inventory + // RedfishVersion shall represent the version of the Redfish service. The format of this string shall be of the + // format majorversion.minorversion.errata in compliance with Protocol Version section of the Redfish + // specification. + RedfishVersion string + // RTPVersion shall represent the version of the RTP Version. + RTPVersion string +} + +// Crc +type Crc struct { + // GroupCrcList provides the information related to inventory data of GroupCrcList value. + GroupCrcList []map[string]uint64 +} + +// Inventory +type Inventory struct { + // Crc provides the information related to inventory data of Crc value. + Crc Crc +} + +// ManagerBootConfiguration +type ManagerBootConfiguration struct { + // ManagerBootMode shall specify the enum supported by ManagerBootMode. + ManagerBootMode ManagerBootMode +} + +// ComputerSystem is the update service instance associated with the system. +type ComputerSystem struct { + redfish.ComputerSystem + + BIOS Bios + ManagerBootConfiguration ManagerBootConfiguration + SSIFMode string +} + +// FromComputerSystem gets the OEM instance of the ComputerSystemSystem. +func FromComputerSystem(computerSystem *redfish.ComputerSystem) (*ComputerSystem, error) { + us := ComputerSystem{ + ComputerSystem: *computerSystem, + } + + var t struct { + Oem struct { + Ami struct { + BIOS Bios `json:"BIOS"` + ManagerBootConfiguration ManagerBootConfiguration `json:"ManagerBootConfiguration"` + SSIFMode string `json:"SSIFMode"` + } + } + } + + err := json.Unmarshal(computerSystem.RawData, &t) + if err != nil { + return nil, err + } + + us.BIOS = t.Oem.Ami.BIOS + us.ManagerBootConfiguration = t.Oem.Ami.ManagerBootConfiguration + us.SSIFMode = t.Oem.Ami.SSIFMode + + return &us, nil +} diff --git a/oem/ami/computersystem_test.go b/oem/ami/computersystem_test.go new file mode 100644 index 00000000..671bd342 --- /dev/null +++ b/oem/ami/computersystem_test.go @@ -0,0 +1,192 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stmcginnis/gofish/redfish" +) + +var computerSystemBody = `{ + "@Redfish.Settings": { + "@odata.type": "#Settings.v1_2_2.Settings", + "SettingsObject": { + "@odata.id": "/redfish/v1/Systems/System_0/SD" + } + }, + "@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", + "@odata.etag": "\"1729105654\"", + "@odata.id": "/redfish/v1/Systems/System_0", + "@odata.type": "#ComputerSystem.v1_16_0.ComputerSystem", + "AssetTag": "---", + "Bios": { + "@odata.id": "/redfish/v1/Systems/System_0/Bios" + }, + "Description": "System Self", + "EthernetInterfaces": { + "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces" + }, + "GraphicsControllers": { + "@odata.id": "/redfish/v1/Systems/System_0/GraphicsControllers" + }, + "HostName": null, + "HostingRoles": [ + "ApplicationServer" + ], + "Id": "System_0", + "IndicatorLED": "Off", + "IndicatorLED@Redfish.AllowableValues": [ + "Lit", + "Blinking", + "Off" + ], + "MemorySummary": { + "MemoryMirroring": null, + "Metrics": { + "@odata.id": "/redfish/v1/Systems/System_0/MemorySummary/MemoryMetrics" + }, + "Status": { + "Health": "OK", + "HealthRollup": null, + "State": "Enabled" + }, + "TotalSystemMemoryGiB": 0, + "TotalSystemPersistentMemoryGiB": 0 + }, + "Model": "QuantaGrid S74G-2U 1S7GZ9Z0001", + "Name": "System", + "NetworkInterfaces": { + "@odata.id": "/redfish/v1/Systems/System_0/NetworkInterfaces" + }, + "Oem": { + "Ami": { + "@odata.type": "#AMIBIOSInventoryCRC.v1_0_0.AMIBIOSInventoryCRC", + "Bios": { + "Inventory": { + "Crc": { + "@odata.id": "/redfish/v1/Systems/System_0/Oem/Ami/Inventory/Crc", + "GroupCrcList": [ + { + "PCIE": 17555861 + }, + { + "CERTIFICATE": 0 + }, + { + "CPU": 2772866038 + }, + { + "DIMM": 3469537117 + }, + { + "SECUREBOOT": 2614701783 + } + ] + } + }, + "RedfishVersion": "1.15.1", + "RtpVersion": "RB_1.0.17" + }, + "ManagerBootConfiguration": { + "ManagerBootMode": "None", + "ManagerBootMode@Redfish.AllowableValues": [ + "None", + "SoftReset", + "ResetTimeout" + ] + }, + "SSIFMode": "Enabled" + } + }, + "PCIeFunctions@odata.count": 21, + "PartNumber": "1S7GZ9Z0001", + "PowerRestorePolicy": "LastState", + "PowerState": "On", + "ProcessorSummary": { + "CoreCount": 72, + "Count": 1, + "Model": null, + "Status": { + "Health": "OK", + "HealthRollup": null, + "State": "Enabled" + } + }, + "Processors": { + "@odata.id": "/redfish/v1/Systems/System_0/Processors" + }, + "SKU": "Default string", + "SecureBoot": { + "@odata.id": "/redfish/v1/Systems/System_0/SecureBoot" + }, + "SerialNumber": "QTWS7G0234700030", + "SimpleStorage": { + "@odata.id": "/redfish/v1/Systems/System_0/SimpleStorage" + }, + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + }, + "Storage": { + "@odata.id": "/redfish/v1/Systems/System_0/Storage" + }, + "SubModel": null, + "SystemType": "Physical", + "TrustedModules": [ + { + "FirmwareVersion": "7.85", + "FirmwareVersion2": "17.51968", + "InterfaceType": "TPM2_0", + "InterfaceTypeSelection": "BiosSetting", + "Status": { + "Health": null, + "HealthRollup": null, + "State": "Enabled" + } + } + ], + "USBControllers": { + "@odata.id": "/redfish/v1/Systems/System_0/USBControllers" + }, + "UUID": "C7CEB99A-0133-11EE-B226-74D4DD2E8868" +}` + +// 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 AMI ComputerSystem: %s", err) + } + + if result.ID != "System_0" { + t.Errorf("Received invalid ID: %s", result.ID) + } + + if result.Name != "System" { + t.Errorf("Received invalid name: %s", result.Name) + } + + if result.BIOS.Inventory.Crc.GroupCrcList[0]["PCIE"] != 17555861 { + t.Errorf("Received invalid PCIe value: %d", result.BIOS.Inventory.Crc.GroupCrcList[0]["PCIE"]) + } + + if result.ManagerBootConfiguration.ManagerBootMode != NoneManagerBootMode { + t.Errorf("Received invalid ManagerBootMode: %s", result.ManagerBootConfiguration.ManagerBootMode) + } + + if result.SSIFMode != "Enabled" { + t.Errorf("Received invalid SSIFMode: %s", result.SSIFMode) + } +} diff --git a/oem/ami/eventservice.go b/oem/ami/eventservice.go new file mode 100644 index 00000000..bd616012 --- /dev/null +++ b/oem/ami/eventservice.go @@ -0,0 +1,52 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +// EventService is an AMI OEM instance of an EventService. +type EventService struct { + redfish.EventService + SecondarySMTP redfish.SMTP + + certificates string +} + +// FromEventService converts a standard EventService object to the OEM implementation. +func FromEventService(eventService *redfish.EventService) (*EventService, error) { + es := EventService{ + EventService: *eventService, + } + + var t struct { + Oem struct { + AMI struct { + Certificates common.Link + SecondarySMTP redfish.SMTP + } `json:"Ami"` + } `json:"Oem"` + } + + err := json.Unmarshal(eventService.RawData, &t) + if err != nil { + return nil, err + } + + es.SecondarySMTP = t.Oem.AMI.SecondarySMTP + es.certificates = t.Oem.AMI.Certificates.String() + es.SetClient(eventService.GetClient()) + + return &es, nil +} + +// Certificates will get the Certificates for this EventService. +func (es *EventService) Certificates() ([]*redfish.Certificate, error) { + return redfish.ListReferencedCertificates(es.GetClient(), es.certificates) +} diff --git a/oem/ami/eventservice_test.go b/oem/ami/eventservice_test.go new file mode 100644 index 00000000..b2a4a4cd --- /dev/null +++ b/oem/ami/eventservice_test.go @@ -0,0 +1,123 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + "testing" + + "github.com/stmcginnis/gofish/redfish" +) + +var eventServiceBody = `{ + "@odata.context": "/redfish/v1/$metadata#EventService.EventService", + "@odata.etag": "\"1729105654\"", + "@odata.id": "/redfish/v1/EventService", + "@odata.type": "#EventService.v1_5_0.EventService", + "Actions": { + "#EventService.SubmitTestEvent": { + "@Redfish.ActionInfo": "/redfish/v1/EventService/SubmitTestEventActionInfo", + "target": "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent" + } + }, + "DeliveryRetryAttempts": 3, + "DeliveryRetryIntervalSeconds": 60, + "Description": "Event Service", + "EventFormatTypes": [ + "MetricReport", + "Event" + ], + "Id": "EventService", + "Name": "Event Service", + "Oem": { + "Ami": { + "@odata.type": "#AMIEventService.v1_0_0.AMIEventService", + "Certificates": { + "@odata.id": "/redfish/v1/EventService/Oem/Ami/SMTP/Certificates" + }, + "SecondarySMTP": { + "Authentication": "None", + "ConnectionProtocol": "None", + "FromAddress": null, + "Password": null, + "Port": 25, + "ServerAddress": null, + "ServiceEnabled": false, + "Username": null + } + } + }, + "RegistryPrefixes": [ + "Base", + "SyncAgent", + "HttpStatus", + "EventLog", + "Security", + "Task", + "IPMI" + ], + "ResourceTypes": [ + "Systems", + "Managers", + "AccountService", + "Chassis", + "TelemetryService", + "EventService", + "TaskService" + ], + "SMTP": { + "Authentication": "None", + "ConnectionProtocol": "None", + "FromAddress": null, + "Password": null, + "Port": 25, + "ServerAddress": null, + "ServiceEnabled": false, + "Username": null + }, + "SSEFilterPropertiesSupported": { + "EventFormatType": true, + "MessageId": true, + "MetricReportDefinition": false, + "OriginResource": true, + "RegistryPrefix": true, + "ResourceType": true, + "SubordinateResources": false + }, + "ServerSentEventUri": "/redfish/v1/EventService/SSE", + "ServiceEnabled": true, + "Status": { + "State": "Enabled" + }, + "SubordinateResourcesSupported": false, + "Subscriptions": { + "@odata.id": "/redfish/v1/EventService/Subscriptions" + } +}` + +// TestAMIEventService tests the parsing of the EventService. +func TestAMIEventService(t *testing.T) { + es := &redfish.EventService{} + if err := json.Unmarshal([]byte(eventServiceBody), es); err != nil { + t.Fatalf("error decoding json: %v", err) + } + + eventService, err := FromEventService(es) + if err != nil { + t.Fatalf("error getting oem info: %v", err) + } + + if eventService.ID != "EventService" { + t.Errorf("unexpected ID: %s", eventService.ID) + } + + if eventService.certificates != "/redfish/v1/EventService/Oem/Ami/SMTP/Certificates" { + t.Errorf("unexpected certificates link: %s", eventService.certificates) + } + + if eventService.SecondarySMTP.Port != 25 { + t.Errorf("unexpected port: %d", eventService.SecondarySMTP.Port) + } +} diff --git a/oem/ami/updateservice.go b/oem/ami/updateservice.go new file mode 100644 index 00000000..d9a7ae70 --- /dev/null +++ b/oem/ami/updateservice.go @@ -0,0 +1,162 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + "errors" + + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +type PreserveConfiguration string + +const ( + // AUTOMATION_ENGINE To preserve AUTOMATION_ENGINE. + AutomationEnginePreserveConfiguration PreserveConfiguration = "AUTOMATION_ENGINE" + // Authentication To preserve Authentication. + AuthenticationPreserveConfiguration PreserveConfiguration = "Authentication" + // CMX To preserve CMX. + CMXPreserveConfiguration PreserveConfiguration = "CMX" + // EXTLOG To preserve EXTLOG. + EXTLOGPreserveConfiguration PreserveConfiguration = "EXTLOG" + // FRU To preserve FRU. + FRUPreserveConfiguration PreserveConfiguration = "FRU" + // IPMI To preserve config of IPMI. It will preserve Network automatically if preserve IPMI + IPMIPreserveConfiguration PreserveConfiguration = "IPMI" + // KVM To preserve KVM. + KVMPreserveConfiguration PreserveConfiguration = "KVM" + // NTP To preserve NTP. + NTPPreserveConfiguration PreserveConfiguration = "NTP" + // Network To preserve config of Network. It will preserve IPMI automatically if preserve Network + NetworkPreserveConfiguration PreserveConfiguration = "Network" + // REDFISH To preserve REDFISH. + REDFISHPreserveConfiguration PreserveConfiguration = "REDFISH" + // SDR To preserve SDR. + SDRPreserveConfiguration PreserveConfiguration = "SDR" + // SEL To preserve SEL. + SELPreserveConfiguration PreserveConfiguration = "SEL" + // SNMP To preserve SNMP. + SNMPPreserveConfiguration PreserveConfiguration = "SNMP" + // SSH To preserve SSH. + SSHPreserveConfiguration PreserveConfiguration = "SSH" + // Syslog To preserve Syslog. + SyslogPreserveConfiguration PreserveConfiguration = "Syslog" + // WEB To preserve WEB. + WEBPreserveConfiguration PreserveConfiguration = "WEB" +) + +// AMIUpdateService shall be used to represent an Update Service for a Redfish implementation. It represents the +// properties that affect the service itself. +// +//nolint:revive +type AMIUpdateService struct { + // FlashPercentage shall represent the FlashPercentage of the UpdateService. The format of the string shall be the + // Percentage completed for Flashing of the UpdateService. + FlashPercentage string + // PreserveConfiguration is whether the configuration needs to be preserved + // when doing firmware update or restore factory defaults. + PreserveConfiguration bool + // UpdateInformation is the information about the updated firmware. + UpdateInformation string + // UpdateStatus shall represent the UpdateStatus of the UpdateService. The format of the string shall be the Status + // is Preparing or Downloading or Verifying or Flashing. + UpdateStatus string + // UpdateTarget shall represent UpdateTarget of the UpdateService. The format of this string shall be BMC. + UpdateTarget string +} + +// BMC is the schema definition for image configurations and preserve configurations. +type BMC struct { + // DualImageConfigurations is information about dual image handling. + DualImageConfigurations DualImageConfigurations +} + +// DualImageConfigurations contains information about dual image handling. +type DualImageConfigurations struct { + // ActiveImage is the active image in the BMC. + ActiveImage string + // BootImage represents the image to which BMC boots to. + BootImage string + // FirmwareImage1Name is the name of image #1. + FirmwareImage1Name string + // FirmwareImage1Version is the version of the first firmware image. + FirmwareImage1Version string + // FirmwareImage2Name is the name of image #2. + FirmwareImage2Name string + // FirmwareImage2Version is the version of the second firmware image. + FirmwareImage2Version string +} + +// UpdateInformation shall contain the available actions for this resource. +type UpdateInformation struct { + // UpdateComponent The information about the updated firmware. + UpdateComponent string +} + +type BIOS struct { + BIOSPreserveNVRAM bool +} + +// UpdateService is the update service instance associated with the system. +type UpdateService struct { + redfish.UpdateService + + AMIUpdateService AMIUpdateService + BIOS BIOS + BMC BMC + + uploadCABundleTarget 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 { + UploadCABundle common.ActionTarget `json:"#UpdateService.UploadCABundle"` + } + } + Oem struct { + AMIUpdateService AMIUpdateService `json:"AMIUpdateService"` + BMC BMC `json:"BMC"` + BIOS BIOS `json:"BIOS"` + } + } + + err := json.Unmarshal(updateService.RawData, &t) + if err != nil { + return nil, err + } + + us.AMIUpdateService = t.Oem.AMIUpdateService + us.BMC = t.Oem.BMC + us.BIOS = t.Oem.BIOS + + us.uploadCABundleTarget = t.Actions.Oem.UploadCABundle.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) +} + +// UploadCABundle uploads CA certificates. +// WARNING: The AMI Redfish service JsonSchema does not define any parameters for +// this action. This is most likely incorrect and will cause this to fail. +func (us *UpdateService) UploadCABundle() error { + if us.uploadCABundleTarget == "" { + return errors.New("upload ca bundle is not supported by this system") + } + + return us.Post(us.uploadCABundleTarget, nil) +} diff --git a/oem/ami/updateservice_test.go b/oem/ami/updateservice_test.go new file mode 100644 index 00000000..f82bcae1 --- /dev/null +++ b/oem/ami/updateservice_test.go @@ -0,0 +1,100 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package ami + +import ( + "encoding/json" + "testing" + + "github.com/stmcginnis/gofish/redfish" +) + +var updateServiceBody = `{ + "@odata.context": "/redfish/v1/$metadata#UpdateService.UpdateService", + "@odata.etag": "\"1729105654\"", + "@odata.id": "/redfish/v1/UpdateService", + "@odata.type": "#UpdateService.v1_6_0.UpdateService", + "Actions": { + "#UpdateService.SimpleUpdate": { + "@Redfish.ActionInfo": "/redfish/v1/UpdateService/SimpleUpdateActionInfo", + "target": "/redfish/v1/UpdateService/Actions/SimpleUpdate" + }, + "Oem": { + "#UpdateService.UploadCABundle": { + "@Redfish.ActionInfo": "/redfish/v1/UpdateService/UploadCABundleActionInfo", + "target": "/redfish/v1/UpdateService/Actions/Oem/UpdateService.UploadCABundle" + } + } + }, + "Description": "Redfish Update Service", + "FirmwareInventory": { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory" + }, + "Id": "UpdateService", + "MaxImageSizeBytes": 441393152, + "MultipartHttpPushUri": "/redfish/v1/UpdateService/upload", + "Name": "Update Service", + "Oem": { + "AMIUpdateService": { + "@odata.type": "#AMIUpdateService.v1_0_0.AMIUpdateService", + "FlashPercentage": null, + "PreserveConfiguration": true, + "UpdateStatus": null, + "UpdateTarget": null + }, + "BIOS": { + "BIOSPreserveNVRAM": true + }, + "BMC": { + "@odata.type": "#AMIUpdateService.v1_0_0.BMC", + "DualImageConfigurations": { + "ActiveImage": "1", + "BootImage": "1", + "FirmwareImage1Name": "Image1", + "FirmwareImage1Version": "3.35.00", + "FirmwareImage2Name": "Image2", + "FirmwareImage2Version": "3.35.00" + } + } + }, + "ServiceEnabled": true, + "Status": { + "Health": "OK", + "State": "Enabled" + } +}` + +// TestUpdateService tests the parsing of the UpdateService oem fields. +func TestUpdateService(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.uploadCABundleTarget != "/redfish/v1/UpdateService/Actions/Oem/UpdateService.UploadCABundle" { + t.Errorf("unexpected uploadCABundle target: %s", updateService.uploadCABundleTarget) + } + + if !updateService.AMIUpdateService.PreserveConfiguration { + t.Errorf("unexpected preserve configuration: %t", updateService.AMIUpdateService.PreserveConfiguration) + } + + if !updateService.BIOS.BIOSPreserveNVRAM { + t.Errorf("unexpected preserve nvram: %t", updateService.BIOS.BIOSPreserveNVRAM) + } + + if updateService.BMC.DualImageConfigurations.ActiveImage != "1" { + t.Errorf("unexpected active image: %s", updateService.BMC.DualImageConfigurations.ActiveImage) + } +} diff --git a/oem/smc/updateservice.go b/oem/smc/updateservice.go index 54290fdf..a1094eca 100644 --- a/oem/smc/updateservice.go +++ b/oem/smc/updateservice.go @@ -120,7 +120,7 @@ func (ipmi *IPMIConfig) Download() error { return ipmi.Post(ipmi.downloadTarget, nil) } -// UpdateService is the license manager instance associated with the system. +// UpdateService is the update service instance associated with the system. type UpdateService struct { redfish.UpdateService diff --git a/redfish/eventservice.go b/redfish/eventservice.go index 003293a7..d46f10f9 100644 --- a/redfish/eventservice.go +++ b/redfish/eventservice.go @@ -439,7 +439,6 @@ type SSEFilterPropertiesSupported struct { // SMTP is shall contain settings for SMTP event delivery. type SMTP struct { - // Authentication shall contain the authentication // method for the SMTP server. Authentication SMTPAuthenticationMethods