Skip to content

Commit

Permalink
bmc/firmware: defines interface to upload and install firmware in the…
Browse files Browse the repository at this point in the history
… same method
  • Loading branch information
joelrebel committed Nov 28, 2023
1 parent 9f0b439 commit 37d8981
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 2 deletions.
77 changes: 76 additions & 1 deletion bmc/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,82 @@ func FirmwareInstallStatusFromInterfaces(ctx context.Context, installVersion, co
return firmwareInstallStatus(ctx, installVersion, component, taskID, implementations)
}

// FirmwareInstallerWithOpts defines an interface to install firmware that was previously uploaded with FirmwareUpload
// FirmwareInstallProvider defines an interface to upload and initiate a firmware install in the same implementation method
//
// Its intended to deprecate the FirmwareInstall interface
type FirmwareInstallProvider interface {
// FirmwareInstallUploadAndInitiate uploads _and_ initiates the firmware install process.
//
// return values:
// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.
FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error)
}

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

// firmwareInstall uploads and initiates firmware update for the component
func firmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File, generic []firmwareInstallProvider) (taskID string, metadata Metadata, err error) {
metadata = newMetadata()

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

return taskID, metadata, err
default:
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)
taskID, vErr := elem.FirmwareInstallUploadAndInitiate(ctx, component, file)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
metadata.FailedProviderDetail[elem.name] = err.Error()
continue
}
metadata.SuccessfulProvider = elem.name
return taskID, metadata, nil
}
}

return taskID, metadata, multierror.Append(err, errors.New("failure in FirmwareInstallUploadAndInitiate"))
}

// FirmwareInstallUploadAndInitiateFromInterfaces identifies implementations of the FirmwareInstallProvider interface and passes the found implementations to the firmwareInstallUploadAndInitiate() wrapper
func FirmwareInstallUploadAndInitiateFromInterfaces(ctx context.Context, component string, file *os.File, generic []interface{}) (taskID string, metadata Metadata, err error) {
metadata = newMetadata()

implementations := make([]firmwareInstallProvider, 0)
for _, elem := range generic {
temp := firmwareInstallProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case FirmwareInstallProvider:
temp.FirmwareInstallProvider = p
implementations = append(implementations, temp)
default:
e := fmt.Sprintf("not a FirmwareInstallProvider 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 FirmwareInstallProvider implementations found"),
),
)
}

return firmwareInstallUploadAndInitiate(ctx, component, file, implementations)
}

// FirmwareInstallerUploaded defines an interface to install firmware that was previously uploaded with FirmwareUpload
type FirmwareInstallerUploaded interface {
// FirmwareInstallUploaded uploads firmware update payload to the BMC returning the firmware install task ID
//
Expand Down
93 changes: 93 additions & 0 deletions bmc/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,99 @@ func TestFirmwareInstallStatusFromInterfaces(t *testing.T) {
}
}

type firmwareInstallUploadAndInitiateTester struct {
returnTaskID string
returnError error
}

func (f *firmwareInstallUploadAndInitiateTester) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {
return f.returnTaskID, f.returnError
}

func (r *firmwareInstallUploadAndInitiateTester) Name() string {
return "foo"
}

func TestFirmwareInstallUploadAndInitiate(t *testing.T) {
testCases := []struct {
testName string
component string
file *os.File
returnTaskID string
returnError error
ctxTimeout time.Duration
providerName string
providersAttempted int
}{
{"success with metadata", "componentA", &os.File{}, "1234", nil, 5 * time.Second, "foo", 1},
{"failure with metadata", "componentB", &os.File{}, "1234", errors.New("failed to upload and initiate"), 5 * time.Second, "foo", 1},
{"failure with context timeout", "componentC", &os.File{}, "", context.DeadlineExceeded, 1 * time.Nanosecond, "foo", 1},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
testImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}
if tc.ctxTimeout == 0 {
tc.ctxTimeout = time.Second * 3
}
ctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)
defer cancel()
taskID, metadata, err := firmwareInstallUploadAndInitiate(ctx, tc.component, tc.file, []firmwareInstallProvider{{tc.providerName, testImplementation}})
if tc.returnError != nil {
assert.ErrorIs(t, err, tc.returnError)
return
}

if err != nil {
t.Fatal(err)
}
assert.Equal(t, tc.returnTaskID, taskID)
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
assert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))
})
}
}

func TestFirmwareInstallUploadAndInitiateFromInterfaces(t *testing.T) {
testCases := []struct {
testName string
component string
file *os.File
returnTaskID string
returnError error
providerName string
badImplementation bool
}{
{"success with metadata", "componentA", &os.File{}, "1234", nil, "foo", false},
{"failure with bad implementation", "componentB", &os.File{}, "1234", bmclibErrs.ErrProviderImplementation, "foo", true},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
var generic []interface{}
if tc.badImplementation {
badImplementation := struct{}{}
generic = []interface{}{&badImplementation}
} else {
testImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}
generic = []interface{}{testImplementation}
}
taskID, metadata, err := FirmwareInstallUploadAndInitiateFromInterfaces(context.Background(), tc.component, tc.file, generic)
if tc.returnError != nil {
assert.ErrorIs(t, err, tc.returnError)
return
}

if err != nil {
t.Fatal(err)
}

assert.Equal(t, tc.returnTaskID, taskID)
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
})
}
}

type firmwareInstallUploadTester struct {
TaskID string
Err error
Expand Down
11 changes: 11 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,14 @@ func (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadV

return installTaskID, err
}

func (c *Client) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "FirmwareInstallUploadAndInitiate")
defer span.End()

taskID, metadata, err := bmc.FirmwareInstallUploadAndInitiateFromInterfaces(ctx, component, file, c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
metadata.RegisterSpanAttributes(c.Auth.Host, span)

return taskID, err
}
5 changes: 4 additions & 1 deletion providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const (
FeatureClearSystemEventLog registrar.Feature = "clearsystemeventlog"

// FeatureFirmwareInstallSteps means an implementation returns the steps part of the firmware update process.
FeatureFirmwareInstallSteps registrar.Feature = "firmwareinstallactions"
FeatureFirmwareInstallSteps registrar.Feature = "firmwareinstallsteps"

// FeatureFirmwareUpload means an implementation that uploads firmware for installing.
FeatureFirmwareUpload registrar.Feature = "firmwareupload"
Expand All @@ -54,4 +54,7 @@ const (

// FeatureFirmwareTaskStatus identifies an implementaton that can return the status of a firmware upload/install task.
FeatureFirmwareTaskStatus registrar.Feature = "firmwaretaskstatus"

// FeatureFirmwareUploadInitiateInstall identifies an implementation that uploads firmware _and_ initiates the install process.
FeatureFirmwareUploadInitiateInstall registrar.Feature = "uploadandinitiateinstall"
)

0 comments on commit 37d8981

Please sign in to comment.