From 3b0f1ade3c3478e2518f26dcf030394ed41685e8 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Tue, 20 Aug 2024 08:25:30 -0400 Subject: [PATCH 01/14] Supermicro X13 inventory/firmware support FS-1671 --- providers/supermicro/firmware.go | 1 + providers/supermicro/supermicro.go | 16 +- providers/supermicro/x11.go | 3 +- providers/supermicro/x13.go | 312 +++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 6 deletions(-) create mode 100644 providers/supermicro/x13.go diff --git a/providers/supermicro/firmware.go b/providers/supermicro/firmware.go index a7140c81..fdaba142 100644 --- a/providers/supermicro/firmware.go +++ b/providers/supermicro/firmware.go @@ -27,6 +27,7 @@ var ( "X11SSE-F", "X12STH-SYS", "X12SPO-NTF", + "X13DEM", } errUploadTaskIDExpected = errors.New("expected an firmware upload taskID") diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index e7d224f0..ab21244b 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -175,10 +175,14 @@ func (c *Client) Open(ctx context.Context) (err error) { return err } - if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) && - !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) { + // X13 appears to have dropped the initial 'mainmenu' redirect + if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) { return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents")) } + // if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) && + // !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) { + // return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents")) + // } contentsTopMenu, status, err := c.serviceClient.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0) if err != nil { @@ -197,6 +201,7 @@ func (c *Client) Open(ctx context.Context) (err error) { c.serviceClient.setCsrfToken(csrfToken) c.bmc, err = c.bmcQueryor(ctx) + if err != nil { return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error())) } @@ -283,10 +288,11 @@ func (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) { func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { x11 := newX11Client(c.serviceClient, c.log) x12 := newX12Client(c.serviceClient, c.log) + x13 := newX13Client(c.serviceClient, c.log) var queryor bmcQueryor - for _, bmc := range []bmcQueryor{x11, x12} { + for _, bmc := range []bmcQueryor{x11, x12, x13} { var err error // Note to maintainers: x12 lacks support for the ipmi.cgi endpoint, @@ -309,8 +315,8 @@ func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { } model := strings.ToLower(queryor.deviceModel()) - if !strings.HasPrefix(model, "x12") && !strings.HasPrefix(model, "x11") { - return nil, errors.Wrap(ErrModelUnsupported, "expected one of X11* or X12*, got:"+model) + if !strings.HasPrefix(model, "x13") && !strings.HasPrefix(model, "x12") && !strings.HasPrefix(model, "x11") { + return nil, errors.Wrap(ErrModelUnsupported, "expected one of X11*, X12* or X13*, got:"+model) } return queryor, nil diff --git a/providers/supermicro/x11.go b/providers/supermicro/x11.go index db24f028..604218a6 100644 --- a/providers/supermicro/x11.go +++ b/providers/supermicro/x11.go @@ -39,7 +39,8 @@ func (c *x11) queryDeviceModel(ctx context.Context) (string, error) { errBoardPartNumUnknown := errors.New("baseboard part number unknown") data, err := c.fruInfo(ctx) if err != nil { - if strings.Contains(err.Error(), "404") { + c.log.Error(err, "fruInfo error") + if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "") { return "", ErrXMLAPIUnsupported } diff --git a/providers/supermicro/x13.go b/providers/supermicro/x13.go new file mode 100644 index 00000000..f9dabc60 --- /dev/null +++ b/providers/supermicro/x13.go @@ -0,0 +1,312 @@ +package supermicro + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/bmc-toolbox/bmclib/v2/constants" + brrs "github.com/bmc-toolbox/bmclib/v2/errors" + rfw "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" + "github.com/bmc-toolbox/common" + "github.com/go-logr/logr" + "github.com/pkg/errors" + "github.com/stmcginnis/gofish/redfish" + "golang.org/x/exp/slices" +) + +type x13 struct { + *serviceClient + model string + log logr.Logger +} + +func newX13Client(client *serviceClient, logger logr.Logger) bmcQueryor { + return &x13{ + serviceClient: client, + log: logger, + } +} + +func (c *x13) deviceModel() string { + return c.model +} + +func (c *x13) queryDeviceModel(ctx context.Context) (string, error) { + if err := c.redfishSession(ctx); err != nil { + return "", err + } + + _, model, err := c.redfish.DeviceVendorModel(ctx) + if err != nil { + return "", err + } + + if model == "" { + return "", errors.Wrap(ErrModelUnknown, "empty value") + } + + c.model = common.FormatProductName(model) + + return c.model, nil +} + +func (c *x13) supportsInstall(component string) error { + errComponentNotSupported := fmt.Errorf("component %s on device %s not supported", component, c.model) + + supported := []string{common.SlugBIOS, common.SlugBMC} + if !slices.Contains(supported, strings.ToUpper(component)) { + return errComponentNotSupported + } + + return nil +} + +func (c *x13) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) { + if err := c.supportsInstall(component); err != nil { + return nil, err + } + + return []constants.FirmwareInstallStep{ + constants.FirmwareInstallStepUpload, + constants.FirmwareInstallStepUploadStatus, + constants.FirmwareInstallStepInstallUploaded, + constants.FirmwareInstallStepInstallStatus, + }, nil +} + +// upload firmware +func (c *x13) firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", err + } + + err = c.firmwareTaskActive(ctx, component) + if err != nil { + return "", err + } + + targetID, err := c.redfishOdataID(ctx, component) + if err != nil { + return "", err + } + + params, err := c.redfishParameters(component, targetID) + if err != nil { + return "", err + } + + taskID, err = c.redfish.FirmwareUpload(ctx, file, params) + if err != nil { + if strings.Contains(err.Error(), "OemFirmwareAlreadyInUpdateMode") { + return "", errors.Wrap(brrs.ErrBMCColdResetRequired, "BMC currently in update mode, either continue the update OR if no update is currently running - reset the BMC") + } + + return "", errors.Wrap(err, "error in firmware upload") + } + + if taskID == "" { + return "", errUploadTaskIDEmpty + } + + return taskID, nil +} + +// returns an error when a bmc firmware install is active +func (c *x13) firmwareTaskActive(ctx context.Context, component string) error { + tasks, err := c.redfish.Tasks(ctx) + if err != nil { + return errors.Wrap(err, "error querying redfish tasks") + } + + for _, t := range tasks { + t := t + + if stateFinalized(t.TaskState) { + continue + } + + if err := noTasksRunning(component, t); err != nil { + return err + } + } + + return nil +} + +// // noTasksRunning returns an error if a firmware related task was found active +// func noTasksRunning(component string, t *redfish.Task) error { +// errTaskActive := errors.New("A firmware task was found active for component: " + component) + +// const ( +// // The redfish task name when the BMC is verifies the uploaded BMC firmware. +// verifyBMCFirmware = "BMC Verify" +// // The redfish task name when the BMC is installing the uploaded BMC firmware. +// updateBMCFirmware = "BMC Update" +// // The redfish task name when the BMC is verifies the uploaded BIOS firmware. +// verifyBIOSFirmware = "BIOS Verify" +// // The redfish task name when the BMC is installing the uploaded BIOS firmware. +// updateBIOSFirmware = "BIOS Update" +// ) + +// var verifyTaskName, updateTaskName string + +// switch strings.ToUpper(component) { +// case common.SlugBMC: +// verifyTaskName = verifyBMCFirmware +// updateTaskName = updateBMCFirmware +// case common.SlugBIOS: +// verifyTaskName = verifyBIOSFirmware +// updateTaskName = updateBIOSFirmware +// } + +// taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus) + +// switch t.Name { +// case verifyTaskName: +// return errors.Wrap(errTaskActive, taskInfo) +// case updateTaskName: +// return errors.Wrap(errTaskActive, taskInfo) +// default: +// return nil +// } +// } + +// func stateFinalized(s redfish.TaskState) bool { +// finalized := []redfish.TaskState{ +// redfish.CompletedTaskState, +// redfish.CancelledTaskState, +// redfish.InterruptedTaskState, +// redfish.ExceptionTaskState, +// } + +// return slices.Contains(finalized, s) +// } + +// type Supermicro struct { +// BIOS map[string]bool `json:"BIOS,omitempty"` +// BMC map[string]bool `json:"BMC,omitempty"` +// } + +// type OEM struct { +// Supermicro `json:"Supermicro"` +// } + +// redfish OEM fw install parameters +func (c *x13) biosFwInstallParams() (map[string]bool, error) { + switch c.model { + case "x13spo-ntf": + return map[string]bool{ + "PreserveME": false, + "PreserveNVRAM": false, + "PreserveSMBIOS": true, + "BackupBIOS": false, + "PreserveBOOTCONF": true, + }, nil + case "x13sth-sys": + return map[string]bool{ + "PreserveME": false, + "PreserveNVRAM": false, + "PreserveSMBIOS": true, + "PreserveOA": true, + "PreserveSETUPCONF": true, + "PreserveSETUPPWD": true, + "PreserveSECBOOTKEY": true, + "PreserveBOOTCONF": true, + }, nil + default: + // ideally we never get in this position, since theres model number validation in parent callers. + return nil, errors.New("unsupported model for BIOS fw install: " + c.model) + } +} + +// redfish OEM fw install parameters +func (c *x13) bmcFwInstallParams() map[string]bool { + return map[string]bool{ + "PreserveCfg": true, + "PreserveSdr": true, + "PreserveSsl": true, + } +} + +func (c *x13) redfishParameters(component, targetODataID string) (*rfw.RedfishUpdateServiceParameters, error) { + errUnsupported := errors.New("redfish parameters for x13 hardware component not supported: " + component) + + oem := OEM{} + + biosInstallParams, err := c.biosFwInstallParams() + if err != nil { + return nil, err + } + + switch strings.ToUpper(component) { + case common.SlugBIOS: + oem.Supermicro.BIOS = biosInstallParams + case common.SlugBMC: + oem.Supermicro.BMC = c.bmcFwInstallParams() + default: + return nil, errUnsupported + } + + b, err := json.Marshal(oem) + if err != nil { + return nil, errors.Wrap(err, "error preparing redfish parameters") + } + + return &rfw.RedfishUpdateServiceParameters{ + // NOTE: + // X13s support the OnReset Apply time for BIOS updates if we want to implement that in the future. + OperationApplyTime: constants.OnStartUpdateRequest, + Targets: []string{targetODataID}, + Oem: b, + }, nil +} + +func (c *x13) redfishOdataID(ctx context.Context, component string) (string, error) { + errUnsupported := errors.New("unable to return redfish OData ID for unsupported component: " + component) + + switch strings.ToUpper(component) { + case common.SlugBMC: + return c.redfish.ManagerOdataID(ctx) + case common.SlugBIOS: + // hardcoded since SMCs without the DCMS license will throw license errors + return "/redfish/v1/Systems/1/Bios", nil + //return c.redfish.SystemsBIOSOdataID(ctx) + } + + return "", errUnsupported +} + +func (c *x13) firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", err + } + + task, err := c.redfish.Task(ctx, uploadTaskID) + if err != nil { + e := fmt.Sprintf("error querying redfish tasks for firmware upload taskID: %s, err: %s", uploadTaskID, err.Error()) + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, e) + } + + taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus) + + if task.TaskState != redfish.CompletedTaskState { + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) + } + + if task.TaskStatus != "OK" { + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) + } + + return c.redfish.StartUpdateForUploadedFirmware(ctx) +} + +func (c *x13) firmwareTaskStatus(ctx context.Context, component, taskID string) (state constants.TaskState, status string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", "", errors.Wrap(brrs.ErrFirmwareTaskStatus, err.Error()) + } + + return c.redfish.TaskStatus(ctx, taskID) +} From 0ce25c2dbdfeedd56f5f9bb4ef74c0b065099033 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Tue, 27 Aug 2024 13:28:49 -0400 Subject: [PATCH 02/14] .github/workflows/ci.yaml: Add line numbers to golangci output --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 637c879d..ab27539e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v3 with: - args: -v --config .golangci.yml --timeout=5m + args: -v --config .golangci.yml --timeout=5m --out-format=colored-line-number version: latest - name: make all-checks run: make all-checks From 9be85e137782b122b76f12b0b4c5e241581456f6 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Tue, 27 Aug 2024 13:33:18 -0400 Subject: [PATCH 03/14] .github/workflows/ci.yaml: Pin golangci to v1.55.2 --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab27539e..3e14194f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: uses: golangci/golangci-lint-action@v3 with: args: -v --config .golangci.yml --timeout=5m --out-format=colored-line-number - version: latest + version: v1.55.2 - name: make all-checks run: make all-checks test: From ecdd50d4cc2e8a383917f017b2183c17ee328f59 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Tue, 27 Aug 2024 13:38:13 -0400 Subject: [PATCH 04/14] providers/supermicro/supermicro_test.go: Correct happy path case in TestOpen() --- providers/supermicro/supermicro_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/supermicro/supermicro_test.go b/providers/supermicro/supermicro_test.go index f503813a..989dc910 100644 --- a/providers/supermicro/supermicro_test.go +++ b/providers/supermicro/supermicro_test.go @@ -143,7 +143,7 @@ func TestOpen(t *testing.T) { From 0b78af1e24dacf685a0cf291215f670d2a6835b7 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Wed, 28 Aug 2024 10:46:41 -0400 Subject: [PATCH 05/14] providers/supermicro/x11.go: Remove misleading debug error logging --- providers/supermicro/x11.go | 1 - 1 file changed, 1 deletion(-) diff --git a/providers/supermicro/x11.go b/providers/supermicro/x11.go index 604218a6..e31fabca 100644 --- a/providers/supermicro/x11.go +++ b/providers/supermicro/x11.go @@ -39,7 +39,6 @@ func (c *x11) queryDeviceModel(ctx context.Context) (string, error) { errBoardPartNumUnknown := errors.New("baseboard part number unknown") data, err := c.fruInfo(ctx) if err != nil { - c.log.Error(err, "fruInfo error") if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "") { return "", ErrXMLAPIUnsupported } From 3bb5ad01e4d4ebacb65842ab7780fb4efbdee097 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Wed, 28 Aug 2024 10:54:09 -0400 Subject: [PATCH 06/14] providers/supermicro/x13.go: Fix biosFwInstallParams() to reference proper X13 model(s) --- providers/supermicro/x13.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/providers/supermicro/x13.go b/providers/supermicro/x13.go index f9dabc60..1a3531ca 100644 --- a/providers/supermicro/x13.go +++ b/providers/supermicro/x13.go @@ -197,15 +197,7 @@ func (c *x13) firmwareTaskActive(ctx context.Context, component string) error { // redfish OEM fw install parameters func (c *x13) biosFwInstallParams() (map[string]bool, error) { switch c.model { - case "x13spo-ntf": - return map[string]bool{ - "PreserveME": false, - "PreserveNVRAM": false, - "PreserveSMBIOS": true, - "BackupBIOS": false, - "PreserveBOOTCONF": true, - }, nil - case "x13sth-sys": + case "x13dem": return map[string]bool{ "PreserveME": false, "PreserveNVRAM": false, From 9a7a3e9aca82d7d58a69748351d8d2ed209f47e8 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Wed, 28 Aug 2024 11:14:32 -0400 Subject: [PATCH 07/14] providers/supermicro/x12.go: Add x12 to unsupported model logging --- providers/supermicro/x12.go | 2 +- providers/supermicro/x13.go | 60 +------------------------------------ 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/providers/supermicro/x12.go b/providers/supermicro/x12.go index 52893a99..b777b4d1 100644 --- a/providers/supermicro/x12.go +++ b/providers/supermicro/x12.go @@ -222,7 +222,7 @@ func (c *x12) biosFwInstallParams() (map[string]bool, error) { }, nil default: // ideally we never get in this position, since theres model number validation in parent callers. - return nil, errors.New("unsupported model for BIOS fw install: " + c.model) + return nil, errors.New("unsupported model for x12 BIOS fw install: " + c.model) } } diff --git a/providers/supermicro/x13.go b/providers/supermicro/x13.go index 1a3531ca..310c5b0a 100644 --- a/providers/supermicro/x13.go +++ b/providers/supermicro/x13.go @@ -136,64 +136,6 @@ func (c *x13) firmwareTaskActive(ctx context.Context, component string) error { return nil } -// // noTasksRunning returns an error if a firmware related task was found active -// func noTasksRunning(component string, t *redfish.Task) error { -// errTaskActive := errors.New("A firmware task was found active for component: " + component) - -// const ( -// // The redfish task name when the BMC is verifies the uploaded BMC firmware. -// verifyBMCFirmware = "BMC Verify" -// // The redfish task name when the BMC is installing the uploaded BMC firmware. -// updateBMCFirmware = "BMC Update" -// // The redfish task name when the BMC is verifies the uploaded BIOS firmware. -// verifyBIOSFirmware = "BIOS Verify" -// // The redfish task name when the BMC is installing the uploaded BIOS firmware. -// updateBIOSFirmware = "BIOS Update" -// ) - -// var verifyTaskName, updateTaskName string - -// switch strings.ToUpper(component) { -// case common.SlugBMC: -// verifyTaskName = verifyBMCFirmware -// updateTaskName = updateBMCFirmware -// case common.SlugBIOS: -// verifyTaskName = verifyBIOSFirmware -// updateTaskName = updateBIOSFirmware -// } - -// taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus) - -// switch t.Name { -// case verifyTaskName: -// return errors.Wrap(errTaskActive, taskInfo) -// case updateTaskName: -// return errors.Wrap(errTaskActive, taskInfo) -// default: -// return nil -// } -// } - -// func stateFinalized(s redfish.TaskState) bool { -// finalized := []redfish.TaskState{ -// redfish.CompletedTaskState, -// redfish.CancelledTaskState, -// redfish.InterruptedTaskState, -// redfish.ExceptionTaskState, -// } - -// return slices.Contains(finalized, s) -// } - -// type Supermicro struct { -// BIOS map[string]bool `json:"BIOS,omitempty"` -// BMC map[string]bool `json:"BMC,omitempty"` -// } - -// type OEM struct { -// Supermicro `json:"Supermicro"` -// } - // redfish OEM fw install parameters func (c *x13) biosFwInstallParams() (map[string]bool, error) { switch c.model { @@ -210,7 +152,7 @@ func (c *x13) biosFwInstallParams() (map[string]bool, error) { }, nil default: // ideally we never get in this position, since theres model number validation in parent callers. - return nil, errors.New("unsupported model for BIOS fw install: " + c.model) + return nil, errors.New("unsupported model for x13 BIOS fw install: " + c.model) } } From 8a5cbb29c4086239b22ebd3fbaed436c5fae9c54 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Tue, 3 Sep 2024 10:17:56 -0400 Subject: [PATCH 08/14] providers/supermicro/supermicro.go: Fix bmcQueryor to properly match queryor to model --- providers/supermicro/supermicro.go | 37 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index ab21244b..4a5279dc 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -286,18 +286,42 @@ func (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) { } func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { - x11 := newX11Client(c.serviceClient, c.log) - x12 := newX12Client(c.serviceClient, c.log) - x13 := newX13Client(c.serviceClient, c.log) + x11bmc := newX11Client(c.serviceClient, c.log) + x12bmc := newX12Client(c.serviceClient, c.log) + x13bmc := newX13Client(c.serviceClient, c.log) var queryor bmcQueryor - for _, bmc := range []bmcQueryor{x11, x12, x13} { + expected := func(deviceModel string, bmc bmcQueryor) bool { + deviceModel = strings.ToLower(deviceModel) + switch bmc.(type) { + case *x11: + if strings.HasPrefix(deviceModel, "x11") { + return true + } + case *x12: + if strings.HasPrefix(deviceModel, "x12") { + return true + } + case *x13: + if strings.HasPrefix(deviceModel, "x13") { + return true + } + } + + return false + } + + for _, bmc := range []bmcQueryor{x11bmc, x12bmc, x13bmc} { var err error +<<<<<<< HEAD // Note to maintainers: x12 lacks support for the ipmi.cgi endpoint, // which will lead to our graceful handling of ErrXMLAPIUnsupported below. _, err = bmc.queryDeviceModel(ctx) +======= + deviceModel, err := bmc.queryDeviceModel(ctx) +>>>>>>> 3d1fe09 (providers/supermicro/supermicro.go: Fix bmcQueryor to properly match queryor to model) if err != nil { if errors.Is(err, ErrXMLAPIUnsupported) { continue @@ -306,6 +330,11 @@ func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { return nil, errors.Wrap(ErrModelUnknown, err.Error()) } + // ensure the device model matches the expected queryor + if !expected(deviceModel, bmc) { + continue + } + queryor = bmc break } From a5eaf9f81cf910185579d92fbfec024cabffa267 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Wed, 18 Sep 2024 09:18:46 -0400 Subject: [PATCH 09/14] internal/redfishwrapper/firmware.go: If body parses into redfish.Task, then pull TaskID from there directly --- internal/redfishwrapper/firmware.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/redfishwrapper/firmware.go b/internal/redfishwrapper/firmware.go index 81c3f1a5..9910a37d 100644 --- a/internal/redfishwrapper/firmware.go +++ b/internal/redfishwrapper/firmware.go @@ -19,6 +19,7 @@ import ( "github.com/bmc-toolbox/bmclib/v2/constants" bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" + redfish "github.com/stmcginnis/gofish/redfish" ) type installMethod string @@ -105,6 +106,14 @@ func (c *Client) FirmwareUpload(ctx context.Context, updateFile *os.File, params ) } + // For X13 the full Task structure is returned in the body. If we can Unmarshall then we can safely assume + // that redfishTask.ID contains the ID. + redfishTask := &redfish.Task{} + err = json.Unmarshal(response, redfishTask) + if err == nil { + return redfishTask.ID, nil + } + // The response contains a location header pointing to the task URI // Location: /redfish/v1/TaskService/Tasks/JID_467696020275 var location = resp.Header.Get("Location") From 4bf550a9a353bf8e94f0d6e6b5cb57d7bec66c80 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Wed, 18 Sep 2024 09:23:09 -0400 Subject: [PATCH 10/14] providers/supermicro/x12.go: noTasksRunning - Ignore Killed tasks --- providers/supermicro/x12.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/providers/supermicro/x12.go b/providers/supermicro/x12.go index b777b4d1..83a8cf61 100644 --- a/providers/supermicro/x12.go +++ b/providers/supermicro/x12.go @@ -142,6 +142,10 @@ func (c *x12) firmwareTaskActive(ctx context.Context, component string) error { // noTasksRunning returns an error if a firmware related task was found active func noTasksRunning(component string, t *redfish.Task) error { + if t.TaskState == "Killed" { + return nil + } + errTaskActive := errors.New("A firmware task was found active for component: " + component) const ( From 9ea6909db62d33e83c537a734b4486d662849107 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Thu, 19 Sep 2024 09:22:10 -0400 Subject: [PATCH 11/14] providers/supermicro/firmware.go: Clean up small comment typo --- providers/supermicro/firmware.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/supermicro/firmware.go b/providers/supermicro/firmware.go index fdaba142..b235f916 100644 --- a/providers/supermicro/firmware.go +++ b/providers/supermicro/firmware.go @@ -47,7 +47,7 @@ func (c *Client) FirmwareUpload(ctx context.Context, component string, file *os. return "", err } - // // expect atleast 5 minutes left in the deadline to proceed with the upload + // expect atleast 5 minutes left in the deadline to proceed with the upload d, _ := ctx.Deadline() if time.Until(d) < 5*time.Minute { return "", errors.New("remaining context deadline insufficient to perform update: " + time.Until(d).String()) From 1c1c0c5de1b17c74035f7923e6bead1ed3819a29 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Thu, 19 Sep 2024 09:22:37 -0400 Subject: [PATCH 12/14] providers/supermicro/x13.go: Attempt to mirror the Dell install flow for SMCI X13 --- providers/supermicro/x13.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/providers/supermicro/x13.go b/providers/supermicro/x13.go index 310c5b0a..a9b3cf26 100644 --- a/providers/supermicro/x13.go +++ b/providers/supermicro/x13.go @@ -69,10 +69,12 @@ func (c *x13) firmwareInstallSteps(component string) ([]constants.FirmwareInstal return nil, err } + // return []constants.FirmwareInstallStep{ + // constants.FirmwareInstallStepUploadInitiateInstall, + // constants.FirmwareInstallStepInstallStatus, + // }, nil return []constants.FirmwareInstallStep{ - constants.FirmwareInstallStepUpload, - constants.FirmwareInstallStepUploadStatus, - constants.FirmwareInstallStepInstallUploaded, + constants.FirmwareInstallStepUploadInitiateInstall, constants.FirmwareInstallStepInstallStatus, }, nil } From 2a3bfc26fa7f9eee0daa312e8b72e916e6acaabd Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Mon, 7 Oct 2024 11:08:08 -0400 Subject: [PATCH 13/14] providers/supermicro/supermicro.go: Merge changes to bmcQueryor from main --- providers/supermicro/supermicro.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index 4a5279dc..189f4855 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -315,13 +315,7 @@ func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { for _, bmc := range []bmcQueryor{x11bmc, x12bmc, x13bmc} { var err error -<<<<<<< HEAD - // Note to maintainers: x12 lacks support for the ipmi.cgi endpoint, - // which will lead to our graceful handling of ErrXMLAPIUnsupported below. - _, err = bmc.queryDeviceModel(ctx) -======= deviceModel, err := bmc.queryDeviceModel(ctx) ->>>>>>> 3d1fe09 (providers/supermicro/supermicro.go: Fix bmcQueryor to properly match queryor to model) if err != nil { if errors.Is(err, ErrXMLAPIUnsupported) { continue From 663935dec974a94f0dd7c204cda764756f72e211 Mon Sep 17 00:00:00 2001 From: "James W. Brinkerhoff" Date: Mon, 7 Oct 2024 11:18:29 -0400 Subject: [PATCH 14/14] providers/supermicro/x13.go: Implement bootComplete as a copy of x12's --- providers/supermicro/x13.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/providers/supermicro/x13.go b/providers/supermicro/x13.go index a9b3cf26..1bf3673c 100644 --- a/providers/supermicro/x13.go +++ b/providers/supermicro/x13.go @@ -246,3 +246,21 @@ func (c *x13) firmwareTaskStatus(ctx context.Context, component, taskID string) return c.redfish.TaskStatus(ctx, taskID) } + +func (c *x13) getBootProgress() (*redfish.BootProgress, error) { + bps, err := c.redfish.GetBootProgress() + if err != nil { + return nil, err + } + return bps[0], nil +} + +// this is some syntactic sugar to avoid having to code potentially provider- or model-specific knowledge into a caller +func (c *x13) bootComplete() (bool, error) { + bp, err := c.getBootProgress() + if err != nil { + return false, err + } + // we determined this by experiment on X12STH-SYS with redfish 1.14.0 + return bp.LastState == redfish.SystemHardwareInitializationCompleteBootProgressTypes, nil +}