Skip to content

Commit

Permalink
providers/asrockrack: rework firmware install methods for newer inter…
Browse files Browse the repository at this point in the history
…face
  • Loading branch information
joelrebel committed Dec 4, 2023
1 parent 038acdd commit 7884aed
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 90 deletions.
7 changes: 5 additions & 2 deletions providers/asrockrack/asrockrack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestClose(t *testing.T) {
}
}

func Test_FirwmwareUpdateBMC(t *testing.T) {
func TestFirwmwareUpdateBMC(t *testing.T) {
err := aClient.httpsLogin(context.TODO())
if err != nil {
t.Errorf(err.Error())
Expand All @@ -48,7 +48,10 @@ func Test_FirwmwareUpdateBMC(t *testing.T) {
}

defer fh.Close()
err = aClient.firmwareInstallBMC(context.TODO(), fh, 0)
ctx, cancel := context.WithTimeout(context.TODO(), time.Minute*15)
defer cancel()

err = aClient.firmwareUploadBMC(ctx, fh)
if err != nil {
t.Errorf(err.Error())
}
Expand Down
207 changes: 126 additions & 81 deletions providers/asrockrack/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package asrockrack

import (
"context"
"io"
"fmt"
"os"
"strings"
"time"

"github.com/pkg/errors"

Expand All @@ -21,125 +22,167 @@ const (
versionStrEmpty = 2
)

// FirmwareInstall uploads and initiates firmware update for the component
func (a *ASRockRack) FirmwareInstall(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader) (jobID string, err error) {
var size int64
if file, ok := reader.(*os.File); ok {
finfo, err := file.Stat()
if err != nil {
a.log.V(2).Error(err, "unable to determine file size")
}

size = finfo.Size()
// bmc client interface implementations methods
func (a *ASRockRack) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {
switch strings.ToUpper(component) {
case common.SlugBMC:
return []constants.FirmwareInstallStep{
constants.FirmwareInstallStepUpload,
constants.FirmwareInstallStepInstallUploaded,
constants.FirmwareInstallStepInstallStatus,
constants.FirmwareInstallStepResetBMCPostInstall,
constants.FirmwareInstallStepResetBMCOnInstallFailure,
}, nil
}

component = strings.ToUpper(component)
switch component {
return nil, errors.Wrap(bmclibErrs.ErrFirmwareUpload, "component unsupported: "+component)
}

func (a *ASRockRack) FirmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) {
switch strings.ToUpper(component) {
case common.SlugBIOS:
err = a.firmwareInstallBIOS(ctx, reader, size)
return "", a.firmwareUploadBIOS(ctx, file)
case common.SlugBMC:
err = a.firmwareInstallBMC(ctx, reader, size)
default:
return "", errors.Wrap(bmclibErrs.ErrFirmwareInstall, "component unsupported: "+component)
return "", a.firmwareUploadBMC(ctx, file)
}

if err != nil {
err = errors.Wrap(bmclibErrs.ErrFirmwareInstall, err.Error())
}
return "", errors.Wrap(bmclibErrs.ErrFirmwareUpload, "component unsupported: "+component)

return jobID, err
}

// FirmwareInstallStatus returns the status of the firmware install process, a bool value indicating if the component requires a reset
func (a *ASRockRack) FirmwareInstallStatus(ctx context.Context, installVersion, component, taskID string) (status string, err error) {
component = strings.ToUpper(component)
switch component {
case common.SlugBIOS, common.SlugBMC:
return a.firmwareUpdateStatus(ctx, component, installVersion)
default:
return "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, "component unsupported: "+component)
func (a *ASRockRack) firmwareUploadBMC(ctx context.Context, file *os.File) error {
// // 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())
}
}

// firmwareInstallBMC uploads and installs firmware for the BMC component
func (a *ASRockRack) firmwareInstallBMC(ctx context.Context, reader io.Reader, fileSize int64) error {
var err error

// 0. take the model so that we use a different endpoint on E3C256D4ID-NL
device := common.NewDevice()
device.Metadata = map[string]string{}
err = a.fruAttributes(ctx, &device)
if err != nil {
return errors.Wrap(err, "failed to get model in step 0/4")
}

// 1. set the device to flash mode - prepares the flash
// Beware: this locks some capabilities, e.g. the access to fruAttributes
a.log.V(2).WithValues("step", "1/4").Info("set device to flash mode, takes a minute...")
err = a.setFlashMode(ctx)
err := a.setFlashMode(ctx)
if err != nil {
return errors.Wrap(err, "failed in step 1/4 - set device to flash mode")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 1/3 - set device to flash mode: "+err.Error(),
)
}

// 2. upload firmware image file
fwEndpoint := "api/maintenance/firmware"
var fwEndpoint string
switch a.deviceModel {
// E3C256D4ID-NL calls a different endpoint for firmware upload
if strings.EqualFold(device.Model, "E3C256D4ID-NL") {
case "E3C256D4ID-NL":
fwEndpoint = "api/maintenance/firmware/firmware"
default:
fwEndpoint = "api/maintenance/firmware"
}

a.log.V(2).WithValues("step", "2/4").Info("upload BMC firmware image to " + fwEndpoint)
err = a.uploadFirmware(ctx, fwEndpoint, reader, fileSize)
err = a.uploadFirmware(ctx, fwEndpoint, file)
if err != nil {
return errors.Wrap(err, "failed in step 2/4 - upload BMC firmware image")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 2/3 - upload BMC firmware image: "+err.Error(),
)
}

// 3. BMC to verify the uploaded file
err = a.verifyUploadedFirmware(ctx)
a.log.V(2).WithValues("step", "3/4").Info("verify uploaded BMC firmware")
err = a.verifyUploadedFirmware(ctx)
if err != nil {
return errors.Wrap(err, "failed in step 3/4 - verify uploaded BMC firmware")
}

// 4. Run the upgrade - preserving current config
a.log.V(2).WithValues("step", "4/4").Info("proceed with BMC firmware install, preserve current configuration")
err = a.upgradeBMC(ctx)
if err != nil {
return errors.Wrap(err, "failed in step 4/4 - proceed with BMC firmware install")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 3/3 - verify uploaded BMC firmware: "+err.Error(),
)
}

return nil
}

// firmwareInstallBIOS uploads and installs firmware for the BIOS component
func (a *ASRockRack) firmwareInstallBIOS(ctx context.Context, reader io.Reader, fileSize int64) error {
var err error

// 1. upload firmware image file
func (a *ASRockRack) firmwareUploadBIOS(ctx context.Context, file *os.File) error {
a.log.V(2).WithValues("step", "1/3").Info("upload BIOS firmware image")
err = a.uploadFirmware(ctx, "api/asrr/maintenance/BIOS/firmware", reader, fileSize)
err := a.uploadFirmware(ctx, "api/asrr/maintenance/BIOS/firmware", file)
if err != nil {
return errors.Wrap(err, "failed in step 1/3 - upload BIOS firmware image")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 1/3 - upload BIOS firmware image: "+err.Error(),
)
}

// 2. set update parameters to preserve configurations
a.log.V(2).WithValues("step", "2/3").Info("set BIOS preserve flash configuration")
err = a.biosUpgradeConfiguration(ctx)
if err != nil {
return errors.Wrap(err, "failed in step 2/3 - set flash configuration")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 2/3 - set flash configuration: "+err.Error(),
)
}

// 3. run upgrade
a.log.V(2).WithValues("step", "3/3").Info("proceed with BIOS firmware install")
err = a.upgradeBIOS(ctx)
if err != nil {
return errors.Wrap(err, "failed in step 3/3 - proceed with BIOS firmware install")
return errors.Wrap(
bmclibErrs.ErrFirmwareUpload,
"failed in step 3/3 - proceed with BIOS firmware install: "+err.Error(),
)
}

return nil
}

func (a *ASRockRack) FirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) {
switch strings.ToUpper(component) {
case common.SlugBIOS:
return "", a.firmwareInstallUploadedBIOS(ctx)
case common.SlugBMC:
return "", a.firmwareInstallUploadedBMC(ctx)
}

return "", errors.Wrap(bmclibErrs.ErrFirmwareInstall, "component unsupported: "+component)
}

// firmwareInstallUploadedBIOS uploads and installs firmware for the BMC component
func (a *ASRockRack) firmwareInstallUploadedBIOS(ctx context.Context) error {
// 4. Run the upgrade - preserving current config
a.log.V(2).WithValues("step", "install").Info("proceed with BIOS firmware install, preserve current configuration")
err := a.upgradeBIOS(ctx)
if err != nil {
return errors.Wrap(
bmclibErrs.ErrFirmwareInstallUploaded,
"failed in step 4/4 - proceed with BMC firmware install: "+err.Error(),
)
}

return nil
}

// firmwareInstallUploadedBMC uploads and installs firmware for the BMC component
func (a *ASRockRack) firmwareInstallUploadedBMC(ctx context.Context) error {
// 4. Run the upgrade - preserving current config
a.log.V(2).WithValues("step", "install").Info("proceed with BMC firmware install, preserve current configuration")
err := a.upgradeBMC(ctx)
if err != nil {
return errors.Wrap(
bmclibErrs.ErrFirmwareInstallUploaded,
"failed in step 4/4 - proceed with BMC firmware install"+err.Error(),
)
}

return nil
}

// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC.
func (a *ASRockRack) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {
component = strings.ToUpper(component)
switch component {
case common.SlugBIOS, common.SlugBMC:
return a.firmwareUpdateStatus(ctx, component, installVersion)
default:
return "", "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, "component unsupported: "+component)
}
}

// firmwareUpdateBIOSStatus returns the BIOS firmware install status
func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string, installVersion string) (status string, err error) {
func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string, installVersion string) (state constants.TaskState, status string, err error) {
var endpoint string
component = strings.ToUpper(component)
switch component {
Expand All @@ -148,7 +191,7 @@ func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string,
case common.SlugBMC:
endpoint = "api/maintenance/firmware/flash-progress"
default:
return "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, "component unsupported: "+component)
return "", "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, "component unsupported: "+component)
}

// 1. query the flash progress endpoint
Expand All @@ -160,13 +203,15 @@ func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string,
}

if progress != nil {
status = fmt.Sprintf("action: %s, progress: %s", progress.Action, progress.Progress)

switch progress.State {
case 0:
return constants.FirmwareInstallRunning, nil
return constants.Running, status, nil
case 1: // "Flashing To be done"
return constants.FirmwareInstallQueued, nil
return constants.Queued, status, nil
case 2:
return constants.FirmwareInstallComplete, nil
return constants.Complete, status, nil
default:
a.log.V(3).WithValues("state", progress.State).Info("warn", "bmc returned unknown flash progress state")
}
Expand All @@ -179,7 +224,7 @@ func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string,

installStatus, err = a.versionInstalled(ctx, component, installVersion)
if err != nil {
return "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())
return "", "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())
}

switch installStatus {
Expand All @@ -188,17 +233,17 @@ func (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string,
// TODO: we should pass the force parameter to firmwareUpdateStatus,
// so that we can know if we expect a version change or not
a.log.V(3).Info("Nil progress + no version change -> unknown")
return constants.FirmwareInstallUnknown, nil
return constants.Unknown, status, nil
}

return constants.FirmwareInstallComplete, nil
return constants.Complete, status, nil
case versionStrEmpty:
return constants.FirmwareInstallUnknown, nil
return constants.Unknown, status, nil
case versionStrMismatch:
return constants.FirmwareInstallRunning, nil
return constants.Running, status, nil
}

return constants.FirmwareInstallUnknown, nil
return constants.Unknown, status, nil
}

// versionInstalled returns int values on the status of the firmware version install
Expand Down
21 changes: 14 additions & 7 deletions providers/asrockrack/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ func (a *ASRockRack) setFlashMode(ctx context.Context) error {

pConfig := &preserveConfig{}
// preserve config is needed by e3c256d4i
if strings.EqualFold(device.Model, "E3C256D4ID-NL") {
switch device.Model {
case "E3C256D4ID-NL":
pConfig = &preserveConfig{PreserveConfig: 1}
}

Expand Down Expand Up @@ -222,14 +223,20 @@ func multipartSize(fieldname, filename string) int64 {
}

// 2 Upload the firmware file
func (a *ASRockRack) uploadFirmware(ctx context.Context, endpoint string, fwReader io.Reader, fileSize int64) error {
func (a *ASRockRack) uploadFirmware(ctx context.Context, endpoint string, file *os.File) error {
var size int64
finfo, err := file.Stat()
if err != nil {
return errors.Wrap(err, "unable to determine file size")
}

size = finfo.Size()

fieldName, fileName := "fwimage", "image"
contentLength := multipartSize(fieldName, fileName) + fileSize
contentLength := multipartSize(fieldName, fileName) + size

// Before reading the file, rewind to the beginning
if file, ok := fwReader.(*os.File); ok {
_, _ = file.Seek(0, 0)
}
_, _ = file.Seek(0, 0)

// setup pipe
pipeReader, pipeWriter := io.Pipe()
Expand All @@ -250,7 +257,7 @@ func (a *ASRockRack) uploadFirmware(ctx context.Context, endpoint string, fwRead
}

// copy from source into form part writer
_, err = io.Copy(part, fwReader)
_, err = io.Copy(part, file)
if err != nil {
errCh <- err
return
Expand Down

0 comments on commit 7884aed

Please sign in to comment.