Skip to content

Commit 56e86d9

Browse files
authored
Make installing the current firmware version a no-op (#47)
* check desired versions at the plan stage * instrument plan execution * review comment cleanup
1 parent 4fd4f9a commit 56e86d9

File tree

6 files changed

+153
-47
lines changed

6 files changed

+153
-47
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ endif
3030
"-X $(LDFLAG_LOCATION).GitCommit=$(GIT_COMMIT) \
3131
-X $(LDFLAG_LOCATION).GitBranch=$(GIT_BRANCH) \
3232
-X $(LDFLAG_LOCATION).GitSummary=$(GIT_SUMMARY) \
33-
-X $(LDFLAG_LOCATION).Version=$(VERSION) \
33+
-X $(LDFLAG_LOCATION).AppVersion=$(VERSION) \
3434
-X $(LDFLAG_LOCATION).BuildDate=$(BUILD_DATE)"
3535

3636

@@ -44,7 +44,7 @@ endif
4444
"-X $(LDFLAG_LOCATION).GitCommit=$(GIT_COMMIT) \
4545
-X $(LDFLAG_LOCATION).GitBranch=$(GIT_BRANCH) \
4646
-X $(LDFLAG_LOCATION).GitSummary=$(GIT_SUMMARY) \
47-
-X $(LDFLAG_LOCATION).Version=$(VERSION) \
47+
-X $(LDFLAG_LOCATION).AppVersion=$(VERSION) \
4848
-X $(LDFLAG_LOCATION).BuildDate=$(BUILD_DATE)"
4949

5050

internal/outofband/actions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func transitionRules() []sw.TransitionRule {
188188
PostTransition: handler.PublishStatus,
189189
Documentation: sw.TransitionRuleDoc{
190190
Name: "Check installed firmware",
191-
Description: "Check firmware installed on component - if its equal - returns error, unless Task.Parameters.Force=true.",
191+
Description: "Check firmware installed on component",
192192
},
193193
},
194194
{

internal/statemachine/task.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ type TaskTransitioner interface {
104104
// Plan creates a set of task actions to be executed.
105105
Plan(task sw.StateSwitch, args sw.TransitionArgs) error
106106

107-
// ValidatePlan is called before invoking Run.
108-
ValidatePlan(task sw.StateSwitch, args sw.TransitionArgs) (bool, error)
109-
110107
// Run executes the task actions.
111108
Run(task sw.StateSwitch, args sw.TransitionArgs) error
112109

@@ -194,7 +191,7 @@ func NewTaskStateMachine(handler TaskTransitioner) (*TaskStateMachine, error) {
194191
TransitionType: TransitionTypeRun,
195192
SourceStates: sw.States{model.StateActive},
196193
DestinationState: model.StateSucceeded,
197-
Condition: handler.ValidatePlan,
194+
Condition: nil,
198195
Transition: handler.Run,
199196
PostTransition: handler.PublishStatus,
200197
Documentation: sw.TransitionRuleDoc{

internal/worker/task_handler.go

Lines changed: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package worker
22

33
import (
4+
"fmt"
45
"sort"
56
"strings"
67
"time"
@@ -22,7 +23,6 @@ var (
2223
ErrTaskTypeAssertion = errors.New("error asserting Task type")
2324
errTaskQueryInventory = errors.New("error in task query inventory for installed firmware")
2425
errTaskPlanActions = errors.New("error in task action planning")
25-
errTaskPlanValidate = errors.New("error in task plan validation")
2626
)
2727

2828
// taskHandler implements the taskTransitionHandler methods
@@ -44,10 +44,7 @@ func (h *taskHandler) Query(t sw.StateSwitch, args sw.TransitionArgs) error {
4444
return errors.Wrap(errTaskQueryInventory, ErrTaskTypeAssertion.Error())
4545
}
4646

47-
// asset has component inventory
48-
if len(tctx.Asset.Components) > 0 {
49-
return nil
50-
}
47+
tctx.Logger.Debug("run query step")
5148

5249
// attempt to fetch component inventory from the device
5350
components, err := h.queryFromDevice(tctx)
@@ -76,6 +73,8 @@ func (h *taskHandler) Plan(t sw.StateSwitch, args sw.TransitionArgs) error {
7673
return errors.Wrap(ErrSaveTask, ErrTaskTypeAssertion.Error())
7774
}
7875

76+
tctx.Logger.Debug("create the plan")
77+
7978
switch task.FirmwarePlanMethod {
8079
case model.FromFirmwareSet:
8180
return h.planFromFirmwareSet(tctx, task)
@@ -86,30 +85,6 @@ func (h *taskHandler) Plan(t sw.StateSwitch, args sw.TransitionArgs) error {
8685
}
8786
}
8887

89-
func (h *taskHandler) ValidatePlan(t sw.StateSwitch, args sw.TransitionArgs) (bool, error) {
90-
tctx, ok := args.(*sm.HandlerContext)
91-
if !ok {
92-
return false, sm.ErrInvalidtaskHandlerContext
93-
}
94-
95-
task, ok := t.(*model.Task)
96-
if !ok {
97-
return false, errors.Wrap(ErrSaveTask, ErrTaskTypeAssertion.Error())
98-
}
99-
100-
// validate task has action plans listed
101-
if len(task.ActionsPlanned) == 0 {
102-
return false, errors.Wrap(errTaskPlanValidate, "no task actions planned")
103-
}
104-
105-
// validate task context has action statemachines for execution
106-
if len(tctx.ActionStateMachines) == 0 {
107-
return false, errors.Wrap(errTaskPlanValidate, "task action plan empty")
108-
}
109-
110-
return true, nil
111-
}
112-
11388
func (h *taskHandler) registerActionMetrics(startTS time.Time, action *model.Action, state string) {
11489
metrics.ActionRuntimeSummary.With(
11590
prometheus.Labels{
@@ -131,8 +106,13 @@ func (h *taskHandler) Run(t sw.StateSwitch, args sw.TransitionArgs) error {
131106
return errors.Wrap(ErrSaveTask, ErrTaskTypeAssertion.Error())
132107
}
133108

109+
tctx.Logger.Debug("running the plan")
110+
134111
// each actionSM (state machine) corresponds to a firmware to be installed
135112
for _, actionSM := range tctx.ActionStateMachines {
113+
tctx.Logger.WithFields(logrus.Fields{
114+
"statemachineID": actionSM.ActionID(),
115+
}).Debug("state machine start")
136116
startTS := time.Now()
137117

138118
// fetch action attributes from task
@@ -160,8 +140,12 @@ func (h *taskHandler) Run(t sw.StateSwitch, args sw.TransitionArgs) error {
160140
}
161141

162142
h.registerActionMetrics(startTS, action, string(cptypes.Succeeded))
143+
tctx.Logger.WithFields(logrus.Fields{
144+
"statemachineID": actionSM.ActionID(),
145+
}).Debug("state machine end")
163146
}
164147

148+
tctx.Logger.Debug("plan finished")
165149
return nil
166150
}
167151

@@ -214,7 +198,8 @@ func (h *taskHandler) planFromFirmwareSet(tctx *sm.HandlerContext, task *model.T
214198
}
215199

216200
if len(applicable) == 0 {
217-
return errors.Wrap(errTaskPlanActions, "planFromFirmwareSet(): no applicable firmware identified")
201+
// XXX: why not just short-circuit success here on the GIGO theory?
202+
return errors.Wrap(errTaskPlanActions, "planFromFirmwareSet(): firmware set lacks any members")
218203
}
219204

220205
// plan actions based and update task action list
@@ -266,23 +251,34 @@ func (h *taskHandler) queryFromDevice(tctx *sm.HandlerContext) (model.Components
266251
// planInstall sets up the firmware install plan
267252
//
268253
// This returns a list of actions to added to the task and a list of action state machines for those actions.
269-
func (h *taskHandler) planInstall(_ *sm.HandlerContext, task *model.Task, firmwares []*model.Firmware) (sm.ActionStateMachines, model.Actions, error) {
254+
func (h *taskHandler) planInstall(hCtx *sm.HandlerContext, task *model.Task, firmwares []*model.Firmware) (sm.ActionStateMachines, model.Actions, error) {
270255
actionMachines := make(sm.ActionStateMachines, 0)
271256
actions := make(model.Actions, 0)
272257

273258
// final is set to true in the final action
274259
var final bool
275260

276-
sortFirmwareByInstallOrder(firmwares)
261+
hCtx.Logger.WithFields(logrus.Fields{
262+
"condition.id": task.ID,
263+
"requested.firmware.count": fmt.Sprintf("%d", len(firmwares)),
264+
}).Info("checking against current inventory")
265+
266+
toInstall := firmwares
267+
268+
if !task.Parameters.ForceInstall {
269+
toInstall = removeFirmwareAlreadyAtDesiredVersion(hCtx, firmwares)
270+
}
271+
272+
if len(toInstall) == 0 {
273+
hCtx.Logger.Info("no action required for this task")
274+
return actionMachines, actions, nil
275+
}
277276

277+
sortFirmwareByInstallOrder(toInstall)
278278
// each firmware applicable results in an ActionPlan and an Action
279-
for idx, firmware := range firmwares {
279+
for idx, firmware := range toInstall {
280280
// set final bool when its the last firmware in the slice
281-
if len(firmwares) > 1 {
282-
final = (idx == len(firmwares)-1)
283-
} else {
284-
final = true
285-
}
281+
final = (idx == len(toInstall)-1)
286282

287283
// generate an action ID
288284
actionID := sm.ActionID(task.ID.String(), firmware.Component, idx)
@@ -337,3 +333,41 @@ func sortFirmwareByInstallOrder(firmwares []*model.Firmware) {
337333
return model.FirmwareInstallOrder[slugi] < model.FirmwareInstallOrder[slugj]
338334
})
339335
}
336+
337+
func removeFirmwareAlreadyAtDesiredVersion(hCtx *sm.HandlerContext, fws []*model.Firmware) []*model.Firmware {
338+
var toInstall []*model.Firmware
339+
340+
// only iterate the inventory once
341+
invMap := make(map[string]string)
342+
for _, cmp := range hCtx.Asset.Components {
343+
invMap[strings.ToLower(cmp.Slug)] = cmp.FirmwareInstalled
344+
}
345+
346+
// XXX: this will drop firmware for components that are specified in
347+
// the firmware set but not in the inventory. This is consistent with the
348+
// desire of users to not require a force or a re-run to accomplish an
349+
// attainable goal.
350+
for _, fw := range fws {
351+
currentVersion, ok := invMap[strings.ToLower(fw.Component)]
352+
353+
switch {
354+
case !ok:
355+
hCtx.Logger.WithFields(logrus.Fields{
356+
"component": fw.Component,
357+
}).Warn("inventory missing component")
358+
case strings.EqualFold(currentVersion, fw.Version):
359+
hCtx.Logger.WithFields(logrus.Fields{
360+
"component": fw.Component,
361+
"version": fw.Version,
362+
}).Debug("inventory firmware version matches set")
363+
default:
364+
hCtx.Logger.WithFields(logrus.Fields{
365+
"component": fw.Component,
366+
"installed.version": currentVersion,
367+
"mandated.version": fw.Version,
368+
}).Debug("firmware queued for install")
369+
toInstall = append(toInstall, fw)
370+
}
371+
}
372+
return toInstall
373+
}

internal/worker/task_handler_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package worker
33
import (
44
"testing"
55

6+
"github.com/google/uuid"
67
"github.com/metal-toolbox/flasher/internal/model"
8+
sm "github.com/metal-toolbox/flasher/internal/statemachine"
9+
"github.com/sirupsen/logrus"
710
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"go.hollow.sh/toolbox/events/registry"
813
)
914

1015
func Test_sortFirmwareByInstallOrde(t *testing.T) {
@@ -66,3 +71,68 @@ func Test_sortFirmwareByInstallOrde(t *testing.T) {
6671

6772
assert.Equal(t, expected, have)
6873
}
74+
75+
func TestRemoveFirmwareAlreadyAtDesiredVersion(t *testing.T) {
76+
t.Parallel()
77+
fwSet := []*model.Firmware{
78+
{
79+
Version: "2.6.6",
80+
URL: "https://dl.dell.com/FOLDER08105057M/1/BIOS_C4FT0_WN64_2.6.6.EXE",
81+
FileName: "BIOS_C4FT0_WN64_2.6.6.EXE",
82+
Models: []string{"r6515"},
83+
Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c6368b6c5f4",
84+
Component: "bios",
85+
},
86+
{
87+
Version: "DL6R",
88+
URL: "https://downloads.dell.com/FOLDER06303849M/1/Serial-ATA_Firmware_Y1P10_WN32_DL6R_A00.EXE",
89+
FileName: "Serial-ATA_Firmware_Y1P10_WN32_DL6R_A00.EXE",
90+
Models: []string{"r6515"},
91+
Checksum: "4189d3cb123a781d09a4f568bb686b23c6d8e6b82038eba8222b91c380a25281",
92+
Component: "drive",
93+
},
94+
{
95+
Version: "20.5.13",
96+
URL: "https://dl.dell.com/FOLDER08105057M/1/Network_Firmware_NVXX9_WN64_20.5.13_A00.EXE",
97+
FileName: "Network_Firmware_NVXX9_WN64_20.5.13_A00.EXE",
98+
Models: []string{"r6515"},
99+
Checksum: "b445417d7869bdbdffe7bad69ce32dc19fa29adc61f8e82a324545cabb53f30a",
100+
Component: "nic",
101+
},
102+
}
103+
serverID := uuid.MustParse("fa125199-e9dd-47d4-8667-ce1d26f58c4a")
104+
ctx := &sm.HandlerContext{
105+
Logger: logrus.NewEntry(logrus.New()),
106+
Asset: &model.Asset{
107+
ID: serverID,
108+
Components: model.Components{
109+
{
110+
Slug: "BiOs",
111+
FirmwareInstalled: "2.6.6",
112+
},
113+
{
114+
Slug: "nic",
115+
FirmwareInstalled: "some-different-version",
116+
},
117+
},
118+
},
119+
Task: &model.Task{
120+
ID: serverID, // it just needs to be a UUID
121+
},
122+
WorkerID: registry.GetID("test-app"),
123+
}
124+
expected := []*model.Firmware{
125+
{
126+
Version: "20.5.13",
127+
URL: "https://dl.dell.com/FOLDER08105057M/1/Network_Firmware_NVXX9_WN64_20.5.13_A00.EXE",
128+
FileName: "Network_Firmware_NVXX9_WN64_20.5.13_A00.EXE",
129+
Models: []string{"r6515"},
130+
Checksum: "b445417d7869bdbdffe7bad69ce32dc19fa29adc61f8e82a324545cabb53f30a",
131+
Component: "nic",
132+
},
133+
}
134+
135+
got := removeFirmwareAlreadyAtDesiredVersion(ctx, fwSet)
136+
require.Equal(t, 1, len(got))
137+
require.Equal(t, expected[0], got[0])
138+
}

internal/worker/worker.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/metal-toolbox/flasher/internal/metrics"
1515
"github.com/metal-toolbox/flasher/internal/model"
1616
"github.com/metal-toolbox/flasher/internal/store"
17+
"github.com/metal-toolbox/flasher/internal/version"
1718
"github.com/nats-io/nats.go"
1819
"github.com/pkg/errors"
1920
"github.com/prometheus/client_golang/prometheus"
@@ -116,8 +117,12 @@ func (o *Worker) Run(ctx context.Context) {
116117

117118
o.startWorkerLivenessCheckin(ctx)
118119

120+
v := version.Current()
119121
o.logger.WithFields(
120122
logrus.Fields{
123+
"version": v.AppVersion,
124+
"commit": v.GitCommit,
125+
"branch": v.GitBranch,
121126
"replica-count": o.replicaCount,
122127
"concurrency": o.concurrency,
123128
"dry-run": o.dryrun,
@@ -396,8 +401,8 @@ func (o *Worker) runTaskWithMonitor(ctx context.Context, task *model.Task, asset
396401
FacilityCode: o.facilityCode,
397402
Logger: l.WithFields(
398403
logrus.Fields{
399-
"workerID": o.id,
400-
"conditionID": task.ID,
404+
"workerID": o.id.String(),
405+
"conditionID": task.ID.String(),
401406
"assetID": asset.ID.String(),
402407
"bmc": asset.BmcAddress.String(),
403408
},

0 commit comments

Comments
 (0)