Skip to content

Commit a086784

Browse files
committed
WIP
1 parent b496772 commit a086784

File tree

15 files changed

+1281
-742
lines changed

15 files changed

+1281
-742
lines changed

bmc/firmware.go

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func firmwareInstall(ctx context.Context, component, operationApplyTime string,
5353
taskID, vErr := elem.FirmwareInstall(ctx, component, operationApplyTime, forceInstall, reader)
5454
if vErr != nil {
5555
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
56-
err = multierror.Append(err, vErr)
56+
metadata.FailedProviderDetail[elem.name] = err.Error()
5757
continue
5858

5959
}
@@ -134,7 +134,7 @@ func firmwareInstallStatus(ctx context.Context, installVersion, component, taskI
134134
status, vErr := elem.FirmwareInstallStatus(ctx, installVersion, component, taskID)
135135
if vErr != nil {
136136
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
137-
err = multierror.Append(err, vErr)
137+
metadata.FailedProviderDetail[elem.name] = err.Error()
138138
continue
139139

140140
}
@@ -175,7 +175,82 @@ func FirmwareInstallStatusFromInterfaces(ctx context.Context, installVersion, co
175175
return firmwareInstallStatus(ctx, installVersion, component, taskID, implementations)
176176
}
177177

178-
// FirmwareInstallerWithOpts defines an interface to install firmware that was previously uploaded with FirmwareUpload
178+
// FirmwareInstallProvider defines an interface to upload and initiate a firmware install in the same implementation method
179+
//
180+
// Its intended to deprecate the FirmwareInstall interface
181+
type FirmwareInstallProvider interface {
182+
// FirmwareInstallUploadAndInitiate uploads _and_ initiates the firmware install process.
183+
//
184+
// return values:
185+
// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.
186+
FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error)
187+
}
188+
189+
// firmwareInstallProvider is an internal struct to correlate an implementation/provider and its name
190+
type firmwareInstallProvider struct {
191+
name string
192+
FirmwareInstallProvider
193+
}
194+
195+
// firmwareInstall uploads and initiates firmware update for the component
196+
func firmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File, generic []firmwareInstallProvider) (taskID string, metadata Metadata, err error) {
197+
var metadataLocal Metadata
198+
199+
for _, elem := range generic {
200+
if elem.FirmwareInstallProvider == nil {
201+
continue
202+
}
203+
select {
204+
case <-ctx.Done():
205+
err = multierror.Append(err, ctx.Err())
206+
207+
return taskID, metadata, err
208+
default:
209+
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
210+
taskID, vErr := elem.FirmwareInstallUploadAndInitiate(ctx, component, file)
211+
if vErr != nil {
212+
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
213+
metadata.FailedProviderDetail[elem.name] = err.Error()
214+
continue
215+
}
216+
metadataLocal.SuccessfulProvider = elem.name
217+
return taskID, metadataLocal, nil
218+
}
219+
}
220+
221+
return taskID, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareInstallUploadAndInitiate"))
222+
}
223+
224+
// FirmwareInstallUploadAndInitiateFromInterfaces identifies implementations of the FirmwareInstallProvider interface and passes the found implementations to the firmwareInstallUploadAndInitiate() wrapper
225+
func FirmwareInstallUploadAndInitiateFromInterfaces(ctx context.Context, component string, file *os.File, generic []interface{}) (taskID string, metadata Metadata, err error) {
226+
metadata = newMetadata()
227+
228+
implementations := make([]firmwareInstallProvider, 0)
229+
for _, elem := range generic {
230+
temp := firmwareInstallProvider{name: getProviderName(elem)}
231+
switch p := elem.(type) {
232+
case FirmwareInstallProvider:
233+
temp.FirmwareInstallProvider = p
234+
implementations = append(implementations, temp)
235+
default:
236+
e := fmt.Sprintf("not a FirmwareInstallProvider implementation: %T", p)
237+
err = multierror.Append(err, errors.New(e))
238+
}
239+
}
240+
if len(implementations) == 0 {
241+
return taskID, metadata, multierror.Append(
242+
err,
243+
errors.Wrap(
244+
bmclibErrs.ErrProviderImplementation,
245+
("no FirmwareInstallProvider implementations found"),
246+
),
247+
)
248+
}
249+
250+
return firmwareInstallUploadAndInitiate(ctx, component, file, implementations)
251+
}
252+
253+
// FirmwareInstallerUploaded defines an interface to install firmware that was previously uploaded with FirmwareUpload
179254
type FirmwareInstallerUploaded interface {
180255
// FirmwareInstallUploaded uploads firmware update payload to the BMC returning the firmware install task ID
181256
//
@@ -213,7 +288,7 @@ func firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string
213288
installTaskID, vErr = elem.FirmwareInstallUploaded(ctx, component, uploadTaskID)
214289
if vErr != nil {
215290
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
216-
err = multierror.Append(err, vErr)
291+
metadata.FailedProviderDetail[elem.name] = err.Error()
217292
continue
218293

219294
}
@@ -310,7 +385,7 @@ func firmwareInstallSteps(ctx context.Context, component string, generic []firmw
310385
steps, vErr := elem.FirmwareInstallSteps(ctx, component)
311386
if vErr != nil {
312387
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
313-
err = multierror.Append(err, vErr)
388+
metadata.FailedProviderDetail[elem.name] = err.Error()
314389
continue
315390

316391
}
@@ -362,7 +437,7 @@ func FirmwareUploadFromInterfaces(ctx context.Context, component string, file *o
362437
}
363438

364439
func firmwareUpload(ctx context.Context, component string, file *os.File, generic []firmwareUploaderProvider) (taskID string, metadata Metadata, err error) {
365-
var metadataLocal Metadata
440+
metadata = newMetadata()
366441

367442
for _, elem := range generic {
368443
if elem.FirmwareUploader == nil {
@@ -374,20 +449,20 @@ func firmwareUpload(ctx context.Context, component string, file *os.File, generi
374449

375450
return taskID, metadata, err
376451
default:
377-
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
452+
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)
378453
taskID, vErr := elem.FirmwareUpload(ctx, component, file)
379454
if vErr != nil {
380455
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
381-
err = multierror.Append(err, vErr)
456+
metadata.FailedProviderDetail[elem.name] = err.Error()
382457
continue
383458

384459
}
385-
metadataLocal.SuccessfulProvider = elem.name
386-
return taskID, metadataLocal, nil
460+
metadata.SuccessfulProvider = elem.name
461+
return taskID, metadata, nil
387462
}
388463
}
389464

390-
return taskID, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareUpload"))
465+
return taskID, metadata, multierror.Append(err, errors.New("failure in FirmwareUpload"))
391466
}
392467

393468
// FirmwareTaskVerifier defines an interface to check the status for firmware related tasks queued on the BMC.
@@ -417,7 +492,7 @@ type firmwareTaskVerifierProvider struct {
417492

418493
// firmwareTaskStatus returns the status of the firmware upload process.
419494
func firmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string, generic []firmwareTaskVerifierProvider) (state constants.TaskState, status string, metadata Metadata, err error) {
420-
var metadataLocal Metadata
495+
metadata = newMetadata()
421496

422497
for _, elem := range generic {
423498
if elem.FirmwareTaskVerifier == nil {
@@ -429,20 +504,20 @@ func firmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, c
429504

430505
return state, status, metadata, err
431506
default:
432-
metadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)
507+
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)
433508
state, status, vErr := elem.FirmwareTaskStatus(ctx, kind, component, taskID, installVersion)
434509
if vErr != nil {
435510
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
436-
err = multierror.Append(err, vErr)
511+
metadata.FailedProviderDetail[elem.name] = err.Error()
437512
continue
438-
439513
}
440-
metadataLocal.SuccessfulProvider = elem.name
441-
return state, status, metadataLocal, nil
514+
515+
metadata.SuccessfulProvider = elem.name
516+
return state, status, metadata, nil
442517
}
443518
}
444519

445-
return state, status, metadataLocal, multierror.Append(err, errors.New("failure in FirmwareTaskStatus"))
520+
return state, status, metadata, multierror.Append(err, errors.New("failure in FirmwareTaskStatus"))
446521
}
447522

448523
// FirmwareTaskStatusFromInterfaces identifies implementations of the FirmwareTaskVerifier interface and passes the found implementations to the firmwareTaskStatus() wrapper.

bmc/firmware_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,99 @@ func TestFirmwareInstallStatusFromInterfaces(t *testing.T) {
204204
}
205205
}
206206

207+
type firmwareInstallUploadAndInitiateTester struct {
208+
returnTaskID string
209+
returnError error
210+
}
211+
212+
func (f *firmwareInstallUploadAndInitiateTester) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {
213+
return f.returnTaskID, f.returnError
214+
}
215+
216+
func (r *firmwareInstallUploadAndInitiateTester) Name() string {
217+
return "foo"
218+
}
219+
220+
func TestFirmwareInstallUploadAndInitiate(t *testing.T) {
221+
testCases := []struct {
222+
testName string
223+
component string
224+
file *os.File
225+
returnTaskID string
226+
returnError error
227+
ctxTimeout time.Duration
228+
providerName string
229+
providersAttempted int
230+
}{
231+
{"success with metadata", "componentA", &os.File{}, "1234", nil, 5 * time.Second, "foo", 1},
232+
{"failure with metadata", "componentB", &os.File{}, "1234", errors.New("failed to upload and initiate"), 5 * time.Second, "foo", 1},
233+
{"failure with context timeout", "componentC", &os.File{}, "", context.DeadlineExceeded, 1 * time.Nanosecond, "foo", 1},
234+
}
235+
236+
for _, tc := range testCases {
237+
t.Run(tc.testName, func(t *testing.T) {
238+
testImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}
239+
if tc.ctxTimeout == 0 {
240+
tc.ctxTimeout = time.Second * 3
241+
}
242+
ctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)
243+
defer cancel()
244+
taskID, metadata, err := firmwareInstallUploadAndInitiate(ctx, tc.component, tc.file, []firmwareInstallProvider{{tc.providerName, testImplementation}})
245+
if tc.returnError != nil {
246+
assert.ErrorIs(t, err, tc.returnError)
247+
return
248+
}
249+
250+
if err != nil {
251+
t.Fatal(err)
252+
}
253+
assert.Equal(t, tc.returnTaskID, taskID)
254+
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
255+
assert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))
256+
})
257+
}
258+
}
259+
260+
func TestFirmwareInstallUploadAndInitiateFromInterfaces(t *testing.T) {
261+
testCases := []struct {
262+
testName string
263+
component string
264+
file *os.File
265+
returnTaskID string
266+
returnError error
267+
providerName string
268+
badImplementation bool
269+
}{
270+
{"success with metadata", "componentA", &os.File{}, "1234", nil, "foo", false},
271+
{"failure with bad implementation", "componentB", &os.File{}, "1234", bmclibErrs.ErrProviderImplementation, "foo", true},
272+
}
273+
274+
for _, tc := range testCases {
275+
t.Run(tc.testName, func(t *testing.T) {
276+
var generic []interface{}
277+
if tc.badImplementation {
278+
badImplementation := struct{}{}
279+
generic = []interface{}{&badImplementation}
280+
} else {
281+
testImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}
282+
generic = []interface{}{testImplementation}
283+
}
284+
taskID, metadata, err := FirmwareInstallUploadAndInitiateFromInterfaces(context.Background(), tc.component, tc.file, generic)
285+
if tc.returnError != nil {
286+
assert.ErrorIs(t, err, tc.returnError)
287+
return
288+
}
289+
290+
if err != nil {
291+
t.Fatal(err)
292+
}
293+
294+
assert.Equal(t, tc.returnTaskID, taskID)
295+
assert.Equal(t, tc.providerName, metadata.SuccessfulProvider)
296+
})
297+
}
298+
}
299+
207300
type firmwareInstallUploadTester struct {
208301
TaskID string
209302
Err error

client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,3 +645,14 @@ func (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadV
645645

646646
return installTaskID, err
647647
}
648+
649+
func (c *Client) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {
650+
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "FirmwareInstallUploadAndInitiate")
651+
defer span.End()
652+
653+
taskID, metadata, err := bmc.FirmwareInstallUploadAndInitiateFromInterfaces(ctx, component, file, c.registry().GetDriverInterfaces())
654+
c.setMetadata(metadata)
655+
metadata.RegisterSpanAttributes(c.Auth.Host, span)
656+
657+
return taskID, err
658+
}

constants/constants.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,9 @@ const (
114114
func ListSupportedVendors() []string {
115115
return []string{HP, Dell, Supermicro}
116116
}
117+
118+
type FirmwareInstallProperties struct {
119+
InstallOrder []FirmwareInstallStep
120+
PreInstallHostPowerOff bool
121+
AcceptsOperationApplyTimes []OperationApplyTime
122+
}

examples/install-firmware/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ func main() {
7070
l.Fatal(err, "bmc login failed")
7171
}
7272

73+
steps, err := cl.FirmwareInstallSteps(ctx, *component)
74+
if err != nil {
75+
l.Fatal(err, "FirmwareInstallSteps returned error")
76+
}
77+
7378
defer cl.Close(ctx)
7479

7580
// open file handle

internal/redfishwrapper/firmware.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ func (c *Client) FirmwareUpload(ctx context.Context, updateFile *os.File, params
106106
return taskIDFromLocationHeader(location)
107107
}
108108

109+
fmt.Println(location)
110+
fmt.Println(string(response))
111+
109112
return taskIDFromResponseBody(response)
110113
}
111114

@@ -193,9 +196,9 @@ func taskIDFromLocationHeader(uri string) (taskID string, err error) {
193196

194197
switch {
195198
// idracs return /redfish/v1/TaskService/Tasks/JID_467696020275
196-
case strings.Contains(uri, "JID_"):
197-
taskID = strings.Split(uri, "JID_")[1]
198-
return taskID, nil
199+
//case strings.Contains(uri, "JID_"):
200+
// taskID = strings.Split(uri, "JID_")[1]
201+
// return taskID, nil
199202

200203
// OpenBMC returns /redfish/v1/TaskService/Tasks/12/Monitor
201204
case strings.Contains(uri, "/Tasks/") && strings.HasSuffix(uri, "/Monitor"):

internal/redfishwrapper/task.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ func (c *Client) TaskStatus(ctx context.Context, taskID string) (constants.TaskS
3737
if err != nil {
3838
return "", "", errors.Wrap(err, "error querying redfish for taskID: "+taskID)
3939
}
40+
4041
taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus)
4142

42-
state := strings.ToLower(string(task.TaskState))
43-
return c.ConvertTaskState(state), taskInfo, nil
43+
s := c.ConvertTaskState(string(task.TaskState))
44+
return s, taskInfo, nil
4445
}
4546

4647
func (c *Client) ConvertTaskState(state string) constants.TaskState {
47-
switch state {
48+
switch strings.ToLower(state) {
4849
case "starting", "downloading", "downloaded":
4950
return constants.Initializing
5051
case "running", "stopping", "cancelling", "scheduling":

0 commit comments

Comments
 (0)