Skip to content

Commit

Permalink
Merge pull request #370 from bmc-toolbox/smc-redfish-fixes
Browse files Browse the repository at this point in the history
[2/4] Supermicro redfish methods
  • Loading branch information
joelrebel authored Nov 29, 2023
2 parents d58aaec + ef9c38c commit ee0f228
Show file tree
Hide file tree
Showing 28 changed files with 949 additions and 577 deletions.
117 changes: 96 additions & 21 deletions bmc/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type firmwareInstallerProvider struct {

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

for _, elem := range generic {
if elem.FirmwareInstaller == nil {
Expand All @@ -49,20 +49,20 @@ func firmwareInstall(ctx context.Context, component, operationApplyTime string,

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

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

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

// FirmwareInstallFromInterfaces identifies implementations of the FirmwareInstaller interface and passes the found implementations to the firmwareInstall() wrapper
Expand Down Expand Up @@ -118,7 +118,7 @@ type firmwareInstallVerifierProvider struct {

// firmwareInstallStatus returns the status of the firmware install process
func firmwareInstallStatus(ctx context.Context, installVersion, component, taskID string, generic []firmwareInstallVerifierProvider) (status string, metadata Metadata, err error) {
var metadataLocal Metadata
metadata = newMetadata()

for _, elem := range generic {
if elem.FirmwareInstallVerifier == nil {
Expand All @@ -130,20 +130,20 @@ func firmwareInstallStatus(ctx context.Context, installVersion, component, taskI

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

}
metadataLocal.SuccessfulProvider = elem.name
return status, metadataLocal, nil
metadata.SuccessfulProvider = elem.name
return status, metadata, nil
}
}

return status, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareInstallStatus"))
return status, metadata, multierror.Append(err, errors.New("failure in FirmwareInstallStatus"))
}

// FirmwareInstallStatusFromInterfaces identifies implementations of the FirmwareInstallVerifier interface and passes the found implementations to the firmwareInstallStatus() wrapper.
Expand Down 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

Check warning on line 201 in bmc/firmware.go

View check run for this annotation

Codecov / codecov/patch

bmc/firmware.go#L201

Added line #L201 was not covered by tests
}
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 All @@ -196,7 +271,7 @@ type firmwareInstallerWithOptionsProvider struct {

// firmwareInstallUploaded uploads and initiates firmware update for the component
func firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string, generic []firmwareInstallerWithOptionsProvider) (installTaskID string, metadata Metadata, err error) {
var metadataLocal Metadata
metadata = newMetadata()

for _, elem := range generic {
if elem.FirmwareInstallerUploaded == nil {
Expand All @@ -208,7 +283,7 @@ func firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string

return installTaskID, metadata, err
default:
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)
var vErr error
installTaskID, vErr = elem.FirmwareInstallUploaded(ctx, component, uploadTaskID)
if vErr != nil {
Expand All @@ -217,12 +292,12 @@ func firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string
continue

}
metadataLocal.SuccessfulProvider = elem.name
return installTaskID, metadataLocal, nil
metadata.SuccessfulProvider = elem.name
return installTaskID, metadata, nil
}
}

return installTaskID, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareInstallUploaded"))
return installTaskID, metadata, multierror.Append(err, errors.New("failure in FirmwareInstallUploaded"))
}

// FirmwareInstallerUploadedFromInterfaces identifies implementations of the FirmwareInstallUploaded interface and passes the found implementations to the firmwareInstallUploaded() wrapper
Expand Down Expand Up @@ -294,7 +369,7 @@ func FirmwareInstallStepsFromInterfaces(ctx context.Context, component string, g
}

func firmwareInstallSteps(ctx context.Context, component string, generic []firmwareInstallStepsGetterProvider) (steps []constants.FirmwareInstallStep, metadata Metadata, err error) {
var metadataLocal Metadata
metadata = newMetadata()

for _, elem := range generic {
if elem.FirmwareInstallStepsGetter == nil {
Expand All @@ -306,20 +381,20 @@ func firmwareInstallSteps(ctx context.Context, component string, generic []firmw

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

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

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

type FirmwareUploader interface {
Expand Down
94 changes: 94 additions & 0 deletions bmc/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/bmc-toolbox/bmclib/v2/constants"
bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
"github.com/bmc-toolbox/common"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -204,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()

Check warning on line 662 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L660-L662

Added lines #L660 - L662 were not covered by tests

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

Check warning on line 666 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L664-L666

Added lines #L664 - L666 were not covered by tests

return taskID, err

Check warning on line 668 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L668

Added line #L668 was not covered by tests
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0
github.com/sirupsen/logrus v1.9.3
github.com/stmcginnis/gofish v0.14.1-0.20231018151402-dddaff9168fb
github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/otel v1.20.0
go.opentelemetry.io/otel/trace v1.20.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stmcginnis/gofish v0.14.1-0.20231018151402-dddaff9168fb h1:+BpzUuFIEAs71bTshedsUHAAq21VZWvuokbN9ABEQeQ=
github.com/stmcginnis/gofish v0.14.1-0.20231018151402-dddaff9168fb/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI=
github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91 h1:WmABtU8y6kTgzoVUn3FWCQGAfyodve3uz3xno28BrRs=
github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Expand Down
Loading

0 comments on commit ee0f228

Please sign in to comment.