Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
joelrebel committed Oct 14, 2023
1 parent b6af042 commit b05f9c2
Show file tree
Hide file tree
Showing 34 changed files with 1,662 additions and 248 deletions.
2 changes: 2 additions & 0 deletions bmc/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ func OpenConnectionFromInterfaces(ctx context.Context, timeout time.Duration, pr
// For every provider, launch a goroutine that attempts to open a connection and report
// back via the results channel what happened.
for _, elem := range providers {

switch p := elem.(type) {

case Opener:
providerName := getProviderName(elem)
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, providerName)
Expand Down
228 changes: 222 additions & 6 deletions bmc/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"

"github.com/bmc-toolbox/bmclib/v2/constants"
bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"

"github.com/hashicorp/go-multierror"
Expand All @@ -17,13 +18,13 @@ type FirmwareInstaller interface {
//
// parameters:
// component - the component slug for the component update being installed.
// applyAt - one of "Immediate", "OnReset".
// operationsApplyTime - one of the OperationApplyTime constants
// forceInstall - purge the install task queued/scheduled firmware install BMC task (if any).
// reader - the io.reader to the firmware update file.
//
// return values:
// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.
FirmwareInstall(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader) (taskID string, err error)
FirmwareInstall(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, forceInstall bool, reader io.Reader) (taskID string, err error)
}

// firmwareInstallerProvider is an internal struct to correlate an implementation/provider and its name
Expand All @@ -33,7 +34,7 @@ type firmwareInstallerProvider struct {
}

// firmwareInstall uploads and initiates firmware update for the component
func firmwareInstall(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader, generic []firmwareInstallerProvider) (taskID string, metadata Metadata, err error) {
func firmwareInstall(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, forceInstall bool, reader io.Reader, generic []firmwareInstallerProvider) (taskID string, metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range generic {
Expand All @@ -47,7 +48,7 @@ func firmwareInstall(ctx context.Context, component, applyAt string, forceInstal
return taskID, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
taskID, vErr := elem.FirmwareInstall(ctx, component, applyAt, forceInstall, reader)
taskID, vErr := elem.FirmwareInstall(ctx, component, operationApplyTime, forceInstall, reader)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
err = multierror.Append(err, vErr)
Expand All @@ -63,7 +64,7 @@ func firmwareInstall(ctx context.Context, component, applyAt string, forceInstal
}

// FirmwareInstallFromInterfaces identifies implementations of the FirmwareInstaller interface and passes the found implementations to the firmwareInstall() wrapper
func FirmwareInstallFromInterfaces(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader, generic []interface{}) (taskID string, metadata Metadata, err error) {
func FirmwareInstallFromInterfaces(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, forceInstall bool, reader io.Reader, generic []interface{}) (taskID string, metadata Metadata, err error) {
implementations := make([]firmwareInstallerProvider, 0)
for _, elem := range generic {
temp := firmwareInstallerProvider{name: getProviderName(elem)}
Expand All @@ -86,7 +87,7 @@ func FirmwareInstallFromInterfaces(ctx context.Context, component, applyAt strin
)
}

return firmwareInstall(ctx, component, applyAt, forceInstall, reader, implementations)
return firmwareInstall(ctx, component, operationApplyTime, forceInstall, reader, implementations)
}

// FirmwareInstallVerifier defines an interface to check firmware install status
Expand Down Expand Up @@ -165,3 +166,218 @@ func FirmwareInstallStatusFromInterfaces(ctx context.Context, installVersion, co

return firmwareInstallStatus(ctx, installVersion, component, taskID, implementations)
}

type FirmwareInstallOptions struct {
// The firmware upload task ID if any.
UploadTaskID string
// operationsApplyTime - one of the OperationApplyTime constants
OperationApplyTime constants.OperationApplyTime
}

// FirmwareInstallerWithOpts defines an interface to install firmware updates with the given install parameters
type FirmwareInstallerWithOptions interface {
// FirmwareInstallWithOptions uploads firmware update payload to the BMC returning the task ID
//
// parameters:
// component - the component slug for the component update being installed.
// reader - the io.reader to the firmware update file.
//
// return values:
// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.
FirmwareInstallWithOptions(ctx context.Context, component string, reader io.Reader, opts *FirmwareInstallOptions) (taskID string, err error)
}

// firmwareInstallerProvider is an internal struct to correlate an implementation/provider and its name
type firmwareInstallerWithOptionsProvider struct {
name string
FirmwareInstallerWithOptions
}

// firmwareInstallWithOptions uploads and initiates firmware update for the component
func firmwareInstallWithOptions(ctx context.Context, component string, reader io.Reader, opts *FirmwareInstallOptions, generic []firmwareInstallerWithOptionsProvider) (taskID string, metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range generic {
if elem.FirmwareInstallerWithOptions == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return taskID, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
taskID, vErr := elem.FirmwareInstallWithOptions(ctx, component, reader, opts)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
err = multierror.Append(err, vErr)
continue

}
metadataLocal.SuccessfulProvider = elem.name
return taskID, metadataLocal, nil
}
}

return taskID, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareInstallWithOptions"))
}

// FirmwareInstallWithOptionsFromInterfaces identifies implementations of the FirmwareInstallerWithOptions interface and passes the found implementations to the firmwareInstallWithOptions() wrapper
func FirmwareInstallWithOptionsFromInterfaces(ctx context.Context, component string, reader io.Reader, opts *FirmwareInstallOptions, generic []interface{}) (taskID string, metadata Metadata, err error) {
implementations := make([]firmwareInstallerWithOptionsProvider, 0)
for _, elem := range generic {
temp := firmwareInstallerWithOptionsProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FirmwareInstallerWithOptions:
temp.FirmwareInstallerWithOptions = p
implementations = append(implementations, temp)
default:
e := fmt.Sprintf("not a FirmwareInstallerWithOptions implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(implementations) == 0 {
return taskID, metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
("no FirmwareInstallerWithOptions implementations found"),
),
)
}

return firmwareInstallWithOptions(ctx, component, reader, opts, implementations)
}

type FirmwareActionsGetter interface {
FirmwareInstallActions(ctx context.Context, component string) ([]constants.FirmwareAction, error)
}

// firmwareInstallActionsGetterProvider is an internal struct to correlate an implementation/provider and its name
type firmwareInstallActionsGetterProvider struct {
name string
FirmwareActionsGetter
}

// FirmwareActionsFromInterfaces identifies implementations of the FirmwareActionsGetter interface and passes the found implementations to the firmwareInstallActions() wrapper.
func FirmwareActionsFromInterfaces(ctx context.Context, component string, generic []interface{}) (steps []constants.FirmwareAction, metadata Metadata, err error) {
implementations := make([]firmwareInstallActionsGetterProvider, 0)
for _, elem := range generic {
temp := firmwareInstallActionsGetterProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FirmwareActionsGetter:
temp.FirmwareActionsGetter = p
implementations = append(implementations, temp)
default:
e := fmt.Sprintf("not a FirmwareActionsGetter implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(implementations) == 0 {
return steps, metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
("no FirmwareActionsGetter implementations found"),
),
)
}

return firmwareInstallActions(ctx, component, implementations)
}

func firmwareInstallActions(ctx context.Context, component string, generic []firmwareInstallActionsGetterProvider) (steps []constants.FirmwareAction, metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range generic {
if elem.FirmwareActionsGetter == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return steps, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
steps, vErr := elem.FirmwareInstallActions(ctx, component)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
err = multierror.Append(err, vErr)
continue

}
metadataLocal.SuccessfulProvider = elem.name
return steps, metadataLocal, nil
}
}

return steps, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareActions"))
}

type FirmwareUploader interface {
FirmwareUpload(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, reader io.Reader) (uploadVerifyTaskID string, err error)
}

// firmwareUploaderProvider is an internal struct to correlate an implementation/provider and its name
type firmwareUploaderProvider struct {
name string
FirmwareUploader
}

// FirmwareUploaderFromInterfaces identifies implementations of the FirmwareUploader interface and passes the found implementations to the firmwareUpload() wrapper.
func FirmwareUploaderFromInterfaces(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, reader io.Reader, generic []interface{}) (taskID string, metadata Metadata, err error) {
implementations := make([]firmwareUploaderProvider, 0)
for _, elem := range generic {
temp := firmwareUploaderProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FirmwareUploader:
temp.FirmwareUploader = p
implementations = append(implementations, temp)
default:
e := fmt.Sprintf("not a FirmwareUploader implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(implementations) == 0 {
return taskID, metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
("no FirmwareUploader implementations found"),
),
)
}

return firmwareUpload(ctx, component, operationApplyTime, reader, implementations)
}

func firmwareUpload(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, reader io.Reader, generic []firmwareUploaderProvider) (taskID string, metadata Metadata, err error) {
var metadataLocal Metadata

for _, elem := range generic {
if elem.FirmwareUploader == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())

return taskID, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
taskID, vErr := elem.FirmwareUpload(ctx, component, operationApplyTime, reader)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
err = multierror.Append(err, vErr)
continue

}
metadataLocal.SuccessfulProvider = elem.name
return taskID, metadataLocal, nil
}
}

return taskID, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareUpload"))
}
10 changes: 5 additions & 5 deletions bmc/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func TestFirmwareInstall(t *testing.T) {
providerName string
providersAttempted int
}{
{"success with metadata", common.SlugBIOS, constants.FirmwareApplyOnReset, false, nil, "1234", nil, 5 * time.Second, "foo", 1},
{"failure with metadata", common.SlugBIOS, constants.FirmwareApplyOnReset, false, nil, "1234", errors.ErrNon200Response, 5 * time.Second, "foo", 1},
{"failure with context timeout", common.SlugBIOS, constants.FirmwareApplyOnReset, false, nil, "1234", context.DeadlineExceeded, 1 * time.Nanosecond, "foo", 1},
{"success with metadata", common.SlugBIOS, constants.OnReset, false, nil, "1234", nil, 5 * time.Second, "foo", 1},

Check failure on line 42 in bmc/firmware_test.go

View workflow job for this annotation

GitHub Actions / lint

cannot use constants.OnReset (constant "OnReset" of type constants.OperationApplyTime) as string value in struct literal (typecheck)
{"failure with metadata", common.SlugBIOS, constants.OnReset, false, nil, "1234", errors.ErrNon200Response, 5 * time.Second, "foo", 1},

Check failure on line 43 in bmc/firmware_test.go

View workflow job for this annotation

GitHub Actions / lint

cannot use constants.OnReset (constant "OnReset" of type constants.OperationApplyTime) as string value in struct literal (typecheck)
{"failure with context timeout", common.SlugBIOS, constants.OnReset, false, nil, "1234", context.DeadlineExceeded, 1 * time.Nanosecond, "foo", 1},

Check failure on line 44 in bmc/firmware_test.go

View workflow job for this annotation

GitHub Actions / lint

cannot use constants.OnReset (constant "OnReset" of type constants.OperationApplyTime) as string value in struct literal (typecheck)
}

for _, tc := range testCases {
Expand Down Expand Up @@ -79,8 +79,8 @@ func TestFirmwareInstallFromInterfaces(t *testing.T) {
providerName string
badImplementation bool
}{
{"success with metadata", common.SlugBIOS, constants.FirmwareApplyOnReset, false, nil, "1234", nil, "foo", false},
{"failure with metadata", common.SlugBIOS, constants.FirmwareApplyOnReset, false, nil, "1234", bmclibErrs.ErrProviderImplementation, "foo", true},
{"success with metadata", common.SlugBIOS, constants.OnReset, false, nil, "1234", nil, "foo", false},
{"failure with metadata", common.SlugBIOS, constants.OnReset, false, nil, "1234", bmclibErrs.ErrProviderImplementation, "foo", true},
}

for _, tc := range testCases {
Expand Down
5 changes: 3 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"dario.cat/mergo"
"github.com/bmc-toolbox/bmclib/v2/bmc"
"github.com/bmc-toolbox/bmclib/v2/constants"
"github.com/bmc-toolbox/bmclib/v2/internal/httpclient"
"github.com/bmc-toolbox/bmclib/v2/providers/asrockrack"
"github.com/bmc-toolbox/bmclib/v2/providers/dell"
Expand Down Expand Up @@ -419,8 +420,8 @@ func (c *Client) GetBiosConfiguration(ctx context.Context) (biosConfig map[strin
}

// FirmwareInstall pass through library function to upload firmware and install firmware
func (c *Client) FirmwareInstall(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader) (taskID string, err error) {
taskID, metadata, err := bmc.FirmwareInstallFromInterfaces(ctx, component, applyAt, forceInstall, reader, c.registry().GetDriverInterfaces())
func (c *Client) FirmwareInstall(ctx context.Context, component string, operationApplyTime constants.OperationApplyTime, forceInstall bool, reader io.Reader) (taskID string, err error) {
taskID, metadata, err := bmc.FirmwareInstallFromInterfaces(ctx, component, operationApplyTime, forceInstall, reader, c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return taskID, err
}
Expand Down
21 changes: 19 additions & 2 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ package constants

import "strings"

type (
OperationApplyTime string

// The FirmwareAction identifies each phase of a firmware install process.
FirmwareAction string
)

const (
// Unknown is the constant that defines unknown things
Unknown = "Unknown"
Expand All @@ -27,9 +34,11 @@ const (

// Redfish firmware apply at constants
// FirmwareApplyImmediate sets the firmware to be installed immediately after upload
FirmwareApplyImmediate = "Immediate"
Immediate OperationApplyTime = "Immediate"
//FirmwareApplyOnReset sets the firmware to be install on device power cycle/reset
FirmwareApplyOnReset = "OnReset"
OnReset OperationApplyTime = "OnReset"
// FirmwareOnStartUpdateRequest sets the firmware install to begin after the start request has been sent.
OnStartUpdateRequest OperationApplyTime = "OnStartUpdateRequest"

// Firmware install states returned by bmclib provider FirmwareInstallStatus implementations
//
Expand Down Expand Up @@ -67,6 +76,14 @@ const (

FirmwareInstallUnknown = "unknown"

// FirmwareActionUpload is an identifier for the firmware upload step in the firmware install process.
FirmwareActionUpload = "upload"
FirmwareActionVerifyUpload = "verify-upload"

// FirmwareActionInstall is an identifier for the firmware install step after a firmware has been uploaded.
FirmwareActionInstall = "install"
FirmwareActionVerifyInstall = "verify-install"

// device BIOS/UEFI POST code bmclib identifiers
POSTStateBootINIT = "boot-init/pxe"
POSTStateUEFI = "uefi"
Expand Down
9 changes: 9 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ var (
// ErrFirmwareInstallStatus is returned for firmware install status read
ErrFirmwareInstallStatus = errors.New("error querying firmware install status")

// ErrFirmwareVerifyTaskRunning indicates a firmware verify task is in progress
// the caller is expected to check the verify task is completed successfully before proceeding.
ErrFirmwareVerifyTaskRunning = errors.New("firmware uploaded and is currently being verified")

ErrFirmwareVerifyTaskFailed = errors.New("firmware verify task failed")

// ErrRedfishUpdateService is returned on redfish update service errors
ErrRedfishUpdateService = errors.New("redfish update service error")

Expand Down Expand Up @@ -105,6 +111,9 @@ var (
// ErrSessionExpired is returned when the BMC session is not valid
// the receiver can then choose to request a new session.
ErrSessionExpired = errors.New("session expired")

// ErrSystemVendorModel is returned when the system vendor, model attributes could not be identified.
ErrSystemVendorModel = errors.New("error identifying system vendor, model attributes")
)

type ErrUnsupportedHardware struct {
Expand Down
Loading

0 comments on commit b05f9c2

Please sign in to comment.