Skip to content

Commit 5519890

Browse files
author
James W. Brinkerhoff
committed
Supermicro X13 inventory/firmware support FS-1671
1 parent c88b955 commit 5519890

File tree

5 files changed

+326
-8
lines changed

5 files changed

+326
-8
lines changed

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 h1:t95Grn2
66
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230/go.mod h1:t2EzW1qybnPDQ3LR/GgeF0GOzHUXT5IVMLP2gkW1cmc=
77
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 h1:a0MBqYm44o0NcthLKCljZHe1mxlN6oahCQHHThnSwB4=
88
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22/go.mod h1:/B7V22rcz4860iDqstGvia/2+IYWXf3/JdQCVd/1D2A=
9-
github.com/bmc-toolbox/common v0.0.0-20240805193945-ce25765471a7 h1:+NcnInwZxn25daBCb3d1y3x9QF23uob1ghdiimj2Dwo=
10-
github.com/bmc-toolbox/common v0.0.0-20240805193945-ce25765471a7/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
119
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE=
1210
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
1311
github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=

providers/supermicro/firmware.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var (
2727
"X11SSE-F",
2828
"X12STH-SYS",
2929
"X12SPO-NTF",
30+
"X13DEM",
3031
}
3132

3233
errUploadTaskIDExpected = errors.New("expected an firmware upload taskID")

providers/supermicro/supermicro.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,14 @@ func (c *Client) Open(ctx context.Context) (err error) {
171171
return err
172172
}
173173

174-
if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
175-
!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
174+
// X13 appears to have dropped the initial 'mainmenu' redirect
175+
if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
176176
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents"))
177177
}
178+
// if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
179+
// !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
180+
// return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents"))
181+
// }
178182

179183
contentsTopMenu, status, err := c.serviceClient.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0)
180184
if err != nil {
@@ -193,6 +197,7 @@ func (c *Client) Open(ctx context.Context) (err error) {
193197
c.serviceClient.setCsrfToken(csrfToken)
194198

195199
c.bmc, err = c.bmcQueryor(ctx)
200+
196201
if err != nil {
197202
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))
198203
}
@@ -279,10 +284,11 @@ func (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) {
279284
func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) {
280285
x11 := newX11Client(c.serviceClient, c.log)
281286
x12 := newX12Client(c.serviceClient, c.log)
287+
x13 := newX13Client(c.serviceClient, c.log)
282288

283289
var queryor bmcQueryor
284290

285-
for _, bmc := range []bmcQueryor{x11, x12} {
291+
for _, bmc := range []bmcQueryor{x11, x12, x13} {
286292
var err error
287293

288294
_, err = bmc.queryDeviceModel(ctx)
@@ -303,8 +309,8 @@ func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) {
303309
}
304310

305311
model := strings.ToLower(queryor.deviceModel())
306-
if !strings.HasPrefix(model, "x12") && !strings.HasPrefix(model, "x11") {
307-
return nil, errors.Wrap(ErrModelUnsupported, "expected one of X11* or X12*, got:"+model)
312+
if !strings.HasPrefix(model, "x13") && !strings.HasPrefix(model, "x12") && !strings.HasPrefix(model, "x11") {
313+
return nil, errors.Wrap(ErrModelUnsupported, "expected one of X11*, X12* or X13*, got:"+model)
308314
}
309315

310316
return queryor, nil

providers/supermicro/x11.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ func (c *x11) queryDeviceModel(ctx context.Context) (string, error) {
3838
errBoardPartNumUnknown := errors.New("baseboard part number unknown")
3939
data, err := c.fruInfo(ctx)
4040
if err != nil {
41-
if strings.Contains(err.Error(), "404") {
41+
c.log.Error(err, "fruInfo error")
42+
if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "<html>") {
4243
return "", ErrXMLAPIUnsupported
4344
}
4445

providers/supermicro/x13.go

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
package supermicro
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"strings"
9+
10+
"github.com/bmc-toolbox/bmclib/v2/constants"
11+
brrs "github.com/bmc-toolbox/bmclib/v2/errors"
12+
rfw "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper"
13+
"github.com/bmc-toolbox/common"
14+
"github.com/go-logr/logr"
15+
"github.com/pkg/errors"
16+
"github.com/stmcginnis/gofish/redfish"
17+
"golang.org/x/exp/slices"
18+
)
19+
20+
type x13 struct {
21+
*serviceClient
22+
model string
23+
log logr.Logger
24+
}
25+
26+
func newX13Client(client *serviceClient, logger logr.Logger) bmcQueryor {
27+
return &x13{
28+
serviceClient: client,
29+
log: logger,
30+
}
31+
}
32+
33+
func (c *x13) deviceModel() string {
34+
return c.model
35+
}
36+
37+
func (c *x13) queryDeviceModel(ctx context.Context) (string, error) {
38+
if err := c.redfishSession(ctx); err != nil {
39+
return "", err
40+
}
41+
42+
_, model, err := c.redfish.DeviceVendorModel(ctx)
43+
if err != nil {
44+
return "", err
45+
}
46+
47+
if model == "" {
48+
return "", errors.Wrap(ErrModelUnknown, "empty value")
49+
}
50+
51+
c.model = common.FormatProductName(model)
52+
53+
return c.model, nil
54+
}
55+
56+
func (c *x13) supportsInstall(component string) error {
57+
errComponentNotSupported := fmt.Errorf("component %s on device %s not supported", component, c.model)
58+
59+
supported := []string{common.SlugBIOS, common.SlugBMC}
60+
if !slices.Contains(supported, strings.ToUpper(component)) {
61+
return errComponentNotSupported
62+
}
63+
64+
return nil
65+
}
66+
67+
func (c *x13) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) {
68+
if err := c.supportsInstall(component); err != nil {
69+
return nil, err
70+
}
71+
72+
return []constants.FirmwareInstallStep{
73+
constants.FirmwareInstallStepUpload,
74+
constants.FirmwareInstallStepUploadStatus,
75+
constants.FirmwareInstallStepInstallUploaded,
76+
constants.FirmwareInstallStepInstallStatus,
77+
}, nil
78+
}
79+
80+
// upload firmware
81+
func (c *x13) firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) {
82+
if err = c.supportsInstall(component); err != nil {
83+
return "", err
84+
}
85+
86+
err = c.firmwareTaskActive(ctx, component)
87+
if err != nil {
88+
return "", err
89+
}
90+
91+
targetID, err := c.redfishOdataID(ctx, component)
92+
if err != nil {
93+
return "", err
94+
}
95+
96+
params, err := c.redfishParameters(component, targetID)
97+
if err != nil {
98+
return "", err
99+
}
100+
101+
taskID, err = c.redfish.FirmwareUpload(ctx, file, params)
102+
if err != nil {
103+
if strings.Contains(err.Error(), "OemFirmwareAlreadyInUpdateMode") {
104+
return "", errors.Wrap(brrs.ErrBMCColdResetRequired, "BMC currently in update mode, either continue the update OR if no update is currently running - reset the BMC")
105+
}
106+
107+
return "", errors.Wrap(err, "error in firmware upload")
108+
}
109+
110+
if taskID == "" {
111+
return "", errUploadTaskIDEmpty
112+
}
113+
114+
return taskID, nil
115+
}
116+
117+
// returns an error when a bmc firmware install is active
118+
func (c *x13) firmwareTaskActive(ctx context.Context, component string) error {
119+
tasks, err := c.redfish.Tasks(ctx)
120+
if err != nil {
121+
return errors.Wrap(err, "error querying redfish tasks")
122+
}
123+
124+
for _, t := range tasks {
125+
t := t
126+
127+
if stateFinalized(t.TaskState) {
128+
continue
129+
}
130+
131+
if err := noTasksRunning(component, t); err != nil {
132+
return err
133+
}
134+
}
135+
136+
return nil
137+
}
138+
139+
// // noTasksRunning returns an error if a firmware related task was found active
140+
// func noTasksRunning(component string, t *redfish.Task) error {
141+
// errTaskActive := errors.New("A firmware task was found active for component: " + component)
142+
143+
// const (
144+
// // The redfish task name when the BMC is verifies the uploaded BMC firmware.
145+
// verifyBMCFirmware = "BMC Verify"
146+
// // The redfish task name when the BMC is installing the uploaded BMC firmware.
147+
// updateBMCFirmware = "BMC Update"
148+
// // The redfish task name when the BMC is verifies the uploaded BIOS firmware.
149+
// verifyBIOSFirmware = "BIOS Verify"
150+
// // The redfish task name when the BMC is installing the uploaded BIOS firmware.
151+
// updateBIOSFirmware = "BIOS Update"
152+
// )
153+
154+
// var verifyTaskName, updateTaskName string
155+
156+
// switch strings.ToUpper(component) {
157+
// case common.SlugBMC:
158+
// verifyTaskName = verifyBMCFirmware
159+
// updateTaskName = updateBMCFirmware
160+
// case common.SlugBIOS:
161+
// verifyTaskName = verifyBIOSFirmware
162+
// updateTaskName = updateBIOSFirmware
163+
// }
164+
165+
// taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus)
166+
167+
// switch t.Name {
168+
// case verifyTaskName:
169+
// return errors.Wrap(errTaskActive, taskInfo)
170+
// case updateTaskName:
171+
// return errors.Wrap(errTaskActive, taskInfo)
172+
// default:
173+
// return nil
174+
// }
175+
// }
176+
177+
// func stateFinalized(s redfish.TaskState) bool {
178+
// finalized := []redfish.TaskState{
179+
// redfish.CompletedTaskState,
180+
// redfish.CancelledTaskState,
181+
// redfish.InterruptedTaskState,
182+
// redfish.ExceptionTaskState,
183+
// }
184+
185+
// return slices.Contains(finalized, s)
186+
// }
187+
188+
// type Supermicro struct {
189+
// BIOS map[string]bool `json:"BIOS,omitempty"`
190+
// BMC map[string]bool `json:"BMC,omitempty"`
191+
// }
192+
193+
// type OEM struct {
194+
// Supermicro `json:"Supermicro"`
195+
// }
196+
197+
// redfish OEM fw install parameters
198+
func (c *x13) biosFwInstallParams() (map[string]bool, error) {
199+
switch c.model {
200+
case "x13spo-ntf":
201+
return map[string]bool{
202+
"PreserveME": false,
203+
"PreserveNVRAM": false,
204+
"PreserveSMBIOS": true,
205+
"BackupBIOS": false,
206+
"PreserveBOOTCONF": true,
207+
}, nil
208+
case "x13sth-sys":
209+
return map[string]bool{
210+
"PreserveME": false,
211+
"PreserveNVRAM": false,
212+
"PreserveSMBIOS": true,
213+
"PreserveOA": true,
214+
"PreserveSETUPCONF": true,
215+
"PreserveSETUPPWD": true,
216+
"PreserveSECBOOTKEY": true,
217+
"PreserveBOOTCONF": true,
218+
}, nil
219+
default:
220+
// ideally we never get in this position, since theres model number validation in parent callers.
221+
return nil, errors.New("unsupported model for BIOS fw install: " + c.model)
222+
}
223+
}
224+
225+
// redfish OEM fw install parameters
226+
func (c *x13) bmcFwInstallParams() map[string]bool {
227+
return map[string]bool{
228+
"PreserveCfg": true,
229+
"PreserveSdr": true,
230+
"PreserveSsl": true,
231+
}
232+
}
233+
234+
func (c *x13) redfishParameters(component, targetODataID string) (*rfw.RedfishUpdateServiceParameters, error) {
235+
errUnsupported := errors.New("redfish parameters for x13 hardware component not supported: " + component)
236+
237+
oem := OEM{}
238+
239+
biosInstallParams, err := c.biosFwInstallParams()
240+
if err != nil {
241+
return nil, err
242+
}
243+
244+
switch strings.ToUpper(component) {
245+
case common.SlugBIOS:
246+
oem.Supermicro.BIOS = biosInstallParams
247+
case common.SlugBMC:
248+
oem.Supermicro.BMC = c.bmcFwInstallParams()
249+
default:
250+
return nil, errUnsupported
251+
}
252+
253+
b, err := json.Marshal(oem)
254+
if err != nil {
255+
return nil, errors.Wrap(err, "error preparing redfish parameters")
256+
}
257+
258+
return &rfw.RedfishUpdateServiceParameters{
259+
// NOTE:
260+
// X13s support the OnReset Apply time for BIOS updates if we want to implement that in the future.
261+
OperationApplyTime: constants.OnStartUpdateRequest,
262+
Targets: []string{targetODataID},
263+
Oem: b,
264+
}, nil
265+
}
266+
267+
func (c *x13) redfishOdataID(ctx context.Context, component string) (string, error) {
268+
errUnsupported := errors.New("unable to return redfish OData ID for unsupported component: " + component)
269+
270+
switch strings.ToUpper(component) {
271+
case common.SlugBMC:
272+
return c.redfish.ManagerOdataID(ctx)
273+
case common.SlugBIOS:
274+
// hardcoded since SMCs without the DCMS license will throw license errors
275+
return "/redfish/v1/Systems/1/Bios", nil
276+
//return c.redfish.SystemsBIOSOdataID(ctx)
277+
}
278+
279+
return "", errUnsupported
280+
}
281+
282+
func (c *x13) firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) {
283+
if err = c.supportsInstall(component); err != nil {
284+
return "", err
285+
}
286+
287+
task, err := c.redfish.Task(ctx, uploadTaskID)
288+
if err != nil {
289+
e := fmt.Sprintf("error querying redfish tasks for firmware upload taskID: %s, err: %s", uploadTaskID, err.Error())
290+
return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, e)
291+
}
292+
293+
taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus)
294+
295+
if task.TaskState != redfish.CompletedTaskState {
296+
return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo)
297+
}
298+
299+
if task.TaskStatus != "OK" {
300+
return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo)
301+
}
302+
303+
return c.redfish.StartUpdateForUploadedFirmware(ctx)
304+
}
305+
306+
func (c *x13) firmwareTaskStatus(ctx context.Context, component, taskID string) (state constants.TaskState, status string, err error) {
307+
if err = c.supportsInstall(component); err != nil {
308+
return "", "", errors.Wrap(brrs.ErrFirmwareTaskStatus, err.Error())
309+
}
310+
311+
return c.redfish.TaskStatus(ctx, taskID)
312+
}

0 commit comments

Comments
 (0)