Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update requirements getter #175

Merged
merged 7 commits into from
Aug 15, 2024
10 changes: 9 additions & 1 deletion actions/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type Getter interface {
ListAvailableUpdates(ctx context.Context, options *model.UpdateOptions) (*common.Device, error)
// Retrieve BIOS configuration for device
GetBIOSConfiguration(ctx context.Context) (map[string]string, error)
// UpdateRequirements returns requirements to be met before and after a firmware install
UpdateRequirements(ctx context.Context, componentSlug, componentVendor, componentModel string) (model.UpdateRequirements, error)
}

// Utility interface couples the configuration, collection and update interfaces
Expand Down Expand Up @@ -149,6 +151,12 @@ type UEFIVarsCollector interface {

// Updaters

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
type UpdateRequirementsGetter interface {
UpdateRequirements(componentModel string) model.UpdateRequirements
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
}

// DriveUpdater defines an interface to update drive firmware
type DriveUpdater interface {
UtilAttributeGetter
Expand All @@ -158,8 +166,8 @@ type DriveUpdater interface {
// NICUpdater defines an interface to update NIC firmware
type NICUpdater interface {
UtilAttributeGetter
UpdateRequirementsGetter
UpdateNIC(ctx context.Context, updateFile, modelNumber string, force bool) error
UpdateRequirements() model.UpdateRequirements
}

// BMCUpdater defines an interface to update BMC firmware
Expand Down
92 changes: 60 additions & 32 deletions actions/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,75 @@ type Updaters struct {
StorageControllers StorageControllerUpdater
}

// Update runs updates based on given options
func Update(ctx context.Context, device *common.Device, options []*model.UpdateOptions) error {
func UpdateComponent(ctx context.Context, device *common.Device, option *model.UpdateOptions) error {
var err error
switch {
// Update BIOS
case strings.EqualFold(common.SlugBIOS, option.Slug):
err = UpdateBIOS(ctx, device.BIOS, option)
if err != nil {
return errors.Wrap(err, "error updating bios")
}

// Update Drive
case strings.EqualFold(common.SlugDrive, option.Slug):
err = UpdateDrive(ctx, device.Drives, option)
if err != nil {
return errors.Wrap(err, "error updating drive")
}

// Update NIC
case strings.EqualFold(common.SlugNIC, option.Slug):
err = UpdateNIC(ctx, device.NICs, option)
if err != nil {
return errors.Wrap(err, "error updating nic")
}

// Update BMC
case strings.EqualFold(common.SlugBMC, option.Slug):
err = UpdateBMC(ctx, device.BMC, option)
if err != nil {
return errors.Wrap(err, "error updating bmc")
}
default:
return errors.Wrap(errs.ErrNoUpdateHandlerForComponent, "slug: "+option.Slug)
}

return nil
}

// UpdateAll installs all updates based on given options, options acts as a filter
func UpdateAll(ctx context.Context, device *common.Device, options []*model.UpdateOptions) error {
for _, option := range options {
switch {
// Update BIOS
case strings.EqualFold(common.SlugBIOS, option.Slug):
err = UpdateBIOS(ctx, device.BIOS, option)
if err != nil {
return errors.Wrap(err, "error updating bios")
}

// Update Drive
case strings.EqualFold(common.SlugDrive, option.Slug):
err = UpdateDrive(ctx, device.Drives, option)
if err != nil {
return errors.Wrap(err, "error updating drive")
}

// Update NIC
case strings.EqualFold(common.SlugNIC, option.Slug):
err = UpdateNIC(ctx, device.NICs, option)
if err != nil {
return errors.Wrap(err, "error updating nic")
}

// Update BMC
case strings.EqualFold(common.SlugBMC, option.Slug):
err = UpdateBMC(ctx, device.BMC, option)
if err != nil {
return errors.Wrap(err, "error updating bmc")
}
default:
return errors.Wrap(errs.ErrNoUpdateHandlerForComponent, "slug: "+option.Slug)
if err := UpdateComponent(ctx, device, option); err != nil {
return err
}
}

return nil
}

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
func UpdateRequirements(componentSlug, componentVendor, componentModel string) (model.UpdateRequirements, error) {
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
slug := strings.ToUpper(componentSlug)
vendor := common.FormatVendorName(componentVendor)
req := model.UpdateRequirements{}

switch componentSlug {
case common.SlugNIC:
updater, err := GetNICUpdater(vendor)
if err != nil {
return req, err
}

return updater.UpdateRequirements(componentModel), nil

default:
return req, errors.Wrap(errs.ErrNoUpdateHandlerForComponent, "component: "+slug)
}
}

// GetBMCUpdater returns the updater for the given vendor
func GetBMCUpdater(vendor string) (BMCUpdater, error) {
if strings.EqualFold(vendor, common.VendorSupermicro) {
Expand Down
21 changes: 12 additions & 9 deletions errs/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import (
)

var (
ErrNoUpdatesApplicable = errors.New("no updates applicable")
ErrDmiDecodeRun = errors.New("error running dmidecode")
ErrComponentListExpected = errors.New("expected a list of components to apply updates")
ErrDeviceInventory = errors.New("failed to collect device inventory")
ErrUnsupportedDiskVendor = errors.New("unsupported disk vendor")
ErrNoUpdateHandlerForComponent = errors.New("component slug has no update handler declared")
ErrBinNotExecutable = errors.New("bin has no executable bit set")
ErrBinLstat = errors.New("failed to run lstat on bin")
ErrBinLookupPath = errors.New("failed to lookup bin path")
ErrNoUpdatesApplicable = errors.New("no updates applicable")
ErrDmiDecodeRun = errors.New("error running dmidecode")
ErrComponentListExpected = errors.New("expected a list of components to apply updates")
ErrDeviceInventory = errors.New("failed to collect device inventory")
ErrUnsupportedDiskVendor = errors.New("unsupported disk vendor")
ErrNoUpdateHandlerForComponent = errors.New("component slug has no update handler declared")
ErrNoUpdateReqHandlerForComponent = errors.New("component slug has no update requirements handler declared")
ErrNoUpdateReqGetterForComponent = errors.New("component slug has no update requirements getter handler declared")
ErrBinNotExecutable = errors.New("bin has no executable bit set")
ErrBinLstat = errors.New("failed to run lstat on bin")
ErrBinLookupPath = errors.New("failed to lookup bin path")
ErrUpdateReqNotImplemented = errors.New("UpdateRequirementsGetter interface not implemented")
)

// DmiDecodeValueError is returned when a dmidecode value could not be retrieved
Expand Down
6 changes: 4 additions & 2 deletions model/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ type UpdateOptions struct {
BaseURL string // The BaseURL for the updates
}

// UpdateRequirements holds attributes that indicate requirements for before/after a component firmware install
// UpdateRequirements are returned by utilities to help the caller identify actions (if any)
// required before or after a firmware install.
type UpdateRequirements struct {
PostInstallPowerCycle bool
PostInstallReconfiguration bool // The component requires a re-configuration post firmware install
PostInstallHostPowercycle bool // The component requires a host power-cycle post firmware install
}
8 changes: 8 additions & 0 deletions providers/asrockrack/asrockrack.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (

"github.com/bmc-toolbox/common"
"github.com/metal-toolbox/ironlib/actions"
"github.com/metal-toolbox/ironlib/errs"
"github.com/metal-toolbox/ironlib/model"
"github.com/metal-toolbox/ironlib/utils"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -91,6 +93,12 @@ func (a *asrockrack) InstallUpdates(context.Context, *model.UpdateOptions) error
return nil
}

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
func (a *asrockrack) UpdateRequirements(_ context.Context, _, _, _ string) (model.UpdateRequirements, error) {
return model.UpdateRequirements{}, errors.Wrap(errs.ErrUpdateReqNotImplemented, "provider: asrockrack")
}

// GetInventoryOEM collects device inventory using vendor specific tooling
// and updates the given device.OemComponents object with the OEM inventory
func (a *asrockrack) GetInventoryOEM(context.Context, *common.Device, *model.UpdateOptions) error {
Expand Down
6 changes: 6 additions & 0 deletions providers/dell/dell.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ func (d *dell) ListAvailableUpdates(ctx context.Context, options *model.UpdateOp
return d.hw.Device, nil
}

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
func (d *dell) UpdateRequirements(_ context.Context, _, _, _ string) (model.UpdateRequirements, error) {
return model.UpdateRequirements{}, errors.Wrap(errs.ErrUpdateReqNotImplemented, "provider: generic")
}

// InstallUpdates for Dells based on updateOptions
func (d *dell) InstallUpdates(ctx context.Context, options *model.UpdateOptions) error {
d.setUpdateOptions(options)
Expand Down
6 changes: 6 additions & 0 deletions providers/generic/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ func (a *Generic) ListAvailableUpdates(_ context.Context, _ *model.UpdateOptions
return nil, nil
}

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
func (a *Generic) UpdateRequirements(_ context.Context, _, _, _ string) (model.UpdateRequirements, error) {
return model.UpdateRequirements{}, errors.Wrap(errs.ErrUpdateReqNotImplemented, "provider: generic")
}

// InstallUpdates installs updates based on updateOptions
func (a *Generic) InstallUpdates(_ context.Context, _ *model.UpdateOptions) error {
return nil
Expand Down
8 changes: 7 additions & 1 deletion providers/supermicro/supermicro.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ func (s *supermicro) ListAvailableUpdates(context.Context, *model.UpdateOptions)
return nil, nil
}

// UpdateRequirements returns requirements to be met before and after a firmware install,
// the caller may use the information to determine if a powercycle, reconfiguration or other actions are required on the component.
func (s *supermicro) UpdateRequirements(_ context.Context, componentSlug, componentVendor, componentModel string) (model.UpdateRequirements, error) {
return actions.UpdateRequirements(componentSlug, componentVendor, componentModel)
DoctorVin marked this conversation as resolved.
Show resolved Hide resolved
}

// InstallUpdates for Supermicros based on the given options
//
// errors are returned when the updater fails to apply updates
Expand All @@ -127,7 +133,7 @@ func (s *supermicro) InstallUpdates(ctx context.Context, option *model.UpdateOpt
option.Model = s.hw.Device.Model
}

err = actions.Update(ctx, s.hw.Device, []*model.UpdateOptions{option})
err = actions.UpdateComponent(ctx, s.hw.Device, option)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
ErrVersionStrExpectedSemver = errors.New("expected version string to follow semver format")
ErrFakeExecutorInvalidArgs = errors.New("invalid number of args passed to fake executor")
ErrRepositoryBaseURL = errors.New("repository base URL undefined, ensure UpdateOptions.BaseURL OR UPDATE_BASE_URL env var is set")
ErrRebootRequired = errors.New("reboot required")
)

// ExecError is returned when the command exits with an error or a non zero exit status
Expand Down
15 changes: 9 additions & 6 deletions utils/mlxup.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ func setNICFirmware(d *MlxupDevice, firmware *common.Firmware) {
}

// UpdateRequirements implements the actions/NICUpdater interface to return any pre/post firmware install requirements.
func (m *Mlxup) UpdateRequirements() model.UpdateRequirements {
return model.UpdateRequirements{PostInstallPowerCycle: true}
func (m *Mlxup) UpdateRequirements(_ string) model.UpdateRequirements {
return model.UpdateRequirements{PostInstallHostPowercycle: true}
}

// UpdateNIC updates mellanox NIC with the given update file
Expand Down Expand Up @@ -179,12 +179,15 @@ func (m *Mlxup) UpdateNIC(ctx context.Context, updateFile, modelNumber string, f
m.Executor.SetArgs(args...)
result, err := m.Executor.Exec(ctx)
if err != nil {
if result != nil && result.ExitCode != 0 {
resetRequiredStr := "The firmware image was already updated on flash, pending reset"
if result.Stdout != nil && strings.Contains(string(result.Stdout), resetRequiredStr) {
return errors.Wrap(ErrRebootRequired, resetRequiredStr)
}
return newExecError(m.Executor.GetCmd(), result)
}
return err
}

if result.ExitCode != 0 {
return newExecError(m.Executor.GetCmd(), result)
}
}

return nil
Expand Down
Loading