From fba9711431266a050a3c963b9767cfb704d61d43 Mon Sep 17 00:00:00 2001 From: Jake Schuurmans <143427381+jakeschuurmans@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:09:39 -0400 Subject: [PATCH] FS-1729 (#8) * FS-1729; Set/Upload BiosCfg skeleton - move actions to their own file for cleanliness - Update Rivets, add new biosControl action - add new biosControl action to handler - add new functions to store/bmc to support SetBiosConfig - Update bmclib - Add proper call to bmclib for SetBiosConfigurationFromFile * PR changes --- go.mod | 9 +-- go.sum | 18 +++--- internal/bioscfg/action.go | 105 ++++++++++++++++++++++++++++++++ internal/bioscfg/handler.go | 50 ++------------- internal/bioscfg/publish.go | 2 +- internal/bioscfg/task.go | 6 +- internal/store/bmc/bmc.go | 11 +++- internal/store/bmc/dryRun.go | 6 +- internal/store/bmc/interface.go | 3 +- 9 files changed, 146 insertions(+), 64 deletions(-) create mode 100644 internal/bioscfg/action.go diff --git a/go.mod b/go.mod index 156b9f0..7881154 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/metal-toolbox/bioscfg go 1.22.1 require ( - github.com/bmc-toolbox/bmclib/v2 v2.2.4 + github.com/bmc-toolbox/bmclib/v2 v2.3.1 github.com/bombsimon/logrusr/v2 v2.0.1 github.com/coreos/go-oidc v2.2.1+incompatible github.com/equinix-labs/otel-init-go v0.0.9 @@ -12,7 +12,7 @@ require ( github.com/jeremywohl/flatten v1.0.1 github.com/metal-toolbox/ctrl v0.2.9 github.com/metal-toolbox/fleetdb v1.19.5 - github.com/metal-toolbox/rivets v1.3.8 + github.com/metal-toolbox/rivets v1.3.10 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 @@ -36,7 +36,7 @@ require ( github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b // indirect + github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 // indirect github.com/bytedance/sonic v1.12.1 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -112,7 +112,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91 // indirect + github.com/stmcginnis/gofish v0.19.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -144,6 +144,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 43373b7..e321b75 100644 --- a/go.sum +++ b/go.sum @@ -111,10 +111,10 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmc-toolbox/bmclib/v2 v2.2.4 h1:agAQuDLI/NNpKkxU+c+NfPZAu8ENBDE+kcTfz7WTCrw= -github.com/bmc-toolbox/bmclib/v2 v2.2.4/go.mod h1:V2XVg0Scpm16+0gE7WnI+5bU/M0c/o/nPZKHKzyVjAo= -github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b h1:0LHjikaGWlqEMczrCEZ6w1N/ZqcYlx6WRHkhabRUQEk= -github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I= +github.com/bmc-toolbox/bmclib/v2 v2.3.1 h1:cXUfQFlQpDeUHruwXBYOdEkWXmW/nKdXpOIqbH8fz/M= +github.com/bmc-toolbox/bmclib/v2 v2.3.1/go.mod h1:t8If/0fHQTRIK/yKDk2H3SgthDNNj+7z2aeftDFRFrU= +github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE= +github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= @@ -556,8 +556,8 @@ github.com/metal-toolbox/ctrl v0.2.9 h1:Q1Hqpqyb71/gg2PcX/qrfoDE8FlydJt4rPQb7/Z8 github.com/metal-toolbox/ctrl v0.2.9/go.mod h1:QVATUIWFx3dbjOoEX0EnJHtRvypRlXZ9HUGaPLRyTG8= github.com/metal-toolbox/fleetdb v1.19.5 h1:ERgdFAUtWnT/AeVhCGclsENmwPhU88JUcgOZAdxWKYI= github.com/metal-toolbox/fleetdb v1.19.5/go.mod h1:k9MZXQsJX4NfBoANst6g1468papSs0tzsSyzN3gGWuQ= -github.com/metal-toolbox/rivets v1.3.8 h1:BxzBPBYPMGBwJurIe+8Xji2YL7vHZUHbOmMpszWfPYw= -github.com/metal-toolbox/rivets v1.3.8/go.mod h1:8irU6eXgOa3QkjdcGi/aY4vqoMqCkbwVz7iVTYYPCX8= +github.com/metal-toolbox/rivets v1.3.10 h1:UgYQSx/QJF6Yuzf+YSRF/t9soL6QjMY8sLugf/aMmII= +github.com/metal-toolbox/rivets v1.3.10/go.mod h1:HkF9k8yw3MZqrIkxsi7w7EkTP3h2/t08WBpm+WK/Dsk= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -701,8 +701,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -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/stmcginnis/gofish v0.19.0 h1:fmxdRZ5WHfs+4ExArMYoeRfoh+SAxLELKtmoVplBkU4= +github.com/stmcginnis/gofish v0.19.0/go.mod h1:lq2jHj2t8Krg0Gx02ABk8MbK7Dz9jvWpO/TGnVksn00= 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/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1349,6 +1349,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= diff --git a/internal/bioscfg/action.go b/internal/bioscfg/action.go new file mode 100644 index 0000000..2141534 --- /dev/null +++ b/internal/bioscfg/action.go @@ -0,0 +1,105 @@ +package bioscfg + +import ( + "context" + "io" + "net/http" + + "github.com/metal-toolbox/bioscfg/internal/model" + rctypes "github.com/metal-toolbox/rivets/condition" +) + +// handleAction completes the condition task based on the condition action +func (th *TaskHandler) handleAction(ctx context.Context) error { + switch th.task.Parameters.Action { + case rctypes.ResetConfig: + return th.resetBiosConfig(ctx) + case rctypes.SetConfig: + return th.setBiosConfig(ctx) + default: + return th.failedWithError(ctx, string(th.task.Parameters.Action), errUnsupportedAction) + } +} + +// resetBiosConfig resets the bios of the server +func (th *TaskHandler) resetBiosConfig(ctx context.Context) error { + // Get Power State + state, err := th.bmcClient.GetPowerState(ctx) + if err != nil { + return th.failedWithError(ctx, "error getting power state", err) + } + + err = th.publishActivef(ctx, "current power state: %s", state) + if err != nil { + return err + } + + // Reset Bios + err = th.bmcClient.ResetBiosConfig(ctx) + if err != nil { + return th.failedWithError(ctx, "error reseting bios", err) + } + + err = th.publishActive(ctx, "BIOS settings reset") + if err != nil { + return err + } + + // Reboot (if ON) + if state == model.PowerStateOn { + err = th.bmcClient.SetPowerState(ctx, model.PowerStateReset) + if err != nil { + return th.failedWithError(ctx, "failed to reboot server", err) + } + + return th.successful(ctx, "rebooting server") + } + + return th.successful(ctx, "skipping server reboot, not on") +} + +// setBiosConfig sets BIOS Config +func (th *TaskHandler) setBiosConfig(ctx context.Context) error { + var configURL = "" + if th.task.Parameters.BiosConfigURL != nil { + configURL = th.task.Parameters.BiosConfigURL.String() + } + + if configURL == "" { + return th.failed(ctx, "no Bios Configu URL was found") + } + + req, err := http.NewRequest(http.MethodGet, configURL, http.NoBody) + if err != nil { + return th.failedWithError(ctx, "failed to create http request", err) + } + req = req.WithContext(ctx) + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + return th.failedWithError(ctx, "failed to get bios config from url", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return th.failed(ctx, resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return th.failedWithError(ctx, "failed to read file from response body", err) + } + + err = th.publishActive(ctx, "got bios config from url") + if err != nil { + return err + } + + err = th.bmcClient.SetBiosConfigFromFile(ctx, string(body)) + if err != nil { + return th.failedWithError(ctx, "failed to set bios config through the bmc", err) + } + + return th.successful(ctx, "bios set") +} diff --git a/internal/bioscfg/handler.go b/internal/bioscfg/handler.go index 25d911f..a1d42ba 100644 --- a/internal/bioscfg/handler.go +++ b/internal/bioscfg/handler.go @@ -38,7 +38,7 @@ func (th *TaskHandler) HandleTask(ctx context.Context, genTask *rctypes.Task[any th.publisher = publisher // Ungeneric the task - th.task, err = NewTask(genTask) + th.task, err = newTask(genTask) if err != nil { th.logger.WithFields(logrus.Fields{ "conditionID": genTask.ID, @@ -91,10 +91,10 @@ func (th *TaskHandler) HandleTask(ctx context.Context, genTask *rctypes.Task[any } }() - return th.Run(ctx) + return th.run(ctx) } -func (th *TaskHandler) Run(ctx context.Context) error { +func (th *TaskHandler) run(ctx context.Context) error { ctx, span := otel.Tracer(pkgName).Start( ctx, "TaskHandler.Run", @@ -108,47 +108,5 @@ func (th *TaskHandler) Run(ctx context.Context) error { return err } - switch th.task.Parameters.Action { - case rctypes.ResetSettings: - return th.ResetBios(ctx) - default: - return th.failedWithError(ctx, string(th.task.Parameters.Action), errUnsupportedAction) - } -} - -// ResetBios reset the bios of the server -func (th *TaskHandler) ResetBios(ctx context.Context) error { - // Get Power State - state, err := th.bmcClient.GetPowerState(ctx) - if err != nil { - return th.failedWithError(ctx, "error getting power state", err) - } - - err = th.publishActivef(ctx, "current power state: %s", state) - if err != nil { - return err - } - - // Reset Bios - err = th.bmcClient.ResetBios(ctx) - if err != nil { - return th.failedWithError(ctx, "error reseting bios", err) - } - - err = th.publishActive(ctx, "BIOS settings reset") - if err != nil { - return err - } - - // Reboot (if ON) - if state == model.PowerStateOn { - err = th.bmcClient.SetPowerState(ctx, model.PowerStateReset) - if err != nil { - return th.failedWithError(ctx, "failed to reboot server", err) - } - - return th.successful(ctx, "rebooting server") - } - - return th.successful(ctx, "skipping server reboot, not on") + return th.handleAction(ctx) } diff --git a/internal/bioscfg/publish.go b/internal/bioscfg/publish.go index 551ea3a..dc10835 100644 --- a/internal/bioscfg/publish.go +++ b/internal/bioscfg/publish.go @@ -15,7 +15,7 @@ func (th *TaskHandler) publish(ctx context.Context, status string, state rctypes th.task.State = state th.task.Status.Append(status) - genTask, err := th.task.ToGeneric() + genTask, err := th.task.toGeneric() if err != nil { th.logger.WithError(errTaskConv).Error() return err diff --git a/internal/bioscfg/task.go b/internal/bioscfg/task.go index 8f74604..ac14ef6 100644 --- a/internal/bioscfg/task.go +++ b/internal/bioscfg/task.go @@ -11,7 +11,8 @@ import ( type Task rctypes.Task[*rctypes.BiosControlTaskParameters, json.RawMessage] -func NewTask(task *rctypes.Task[any, any]) (*Task, error) { +// newTask converts a Generic Condition Task to a BiosControl Task +func newTask(task *rctypes.Task[any, any]) (*Task, error) { paramsJSON, ok := task.Parameters.(json.RawMessage) if !ok { return nil, errInvalidConditionParams @@ -52,7 +53,8 @@ func NewTask(task *rctypes.Task[any, any]) (*Task, error) { }, nil } -func (task *Task) ToGeneric() (*rctypes.Task[any, any], error) { +// toGeneric converts a BiosControl Task to a Generic Condition Task +func (task *Task) toGeneric() (*rctypes.Task[any, any], error) { paramsJSON, err := task.Parameters.Marshal() if err != nil { return nil, errors.Wrap(errTaskConv, err.Error()+": Task.Parameters") diff --git a/internal/store/bmc/bmc.go b/internal/store/bmc/bmc.go index c441513..8f58dee 100644 --- a/internal/store/bmc/bmc.go +++ b/internal/store/bmc/bmc.go @@ -82,6 +82,7 @@ func (b *Client) Close(traceCtx context.Context) error { // GetPowerState returns the device power status func (b *Client) GetPowerState(ctx context.Context) (string, error) { defer b.tracelog() + return b.client.GetPowerState(ctx) } @@ -148,11 +149,16 @@ func (b *Client) HostBooted(ctx context.Context) (bool, error) { return status == constants.POSTStateOS, nil } -func (b *Client) ResetBios(ctx context.Context) error { +func (b *Client) ResetBiosConfig(ctx context.Context) error { defer b.tracelog() return b.client.ResetBiosConfiguration(ctx) } +func (b *Client) SetBiosConfigFromFile(ctx context.Context, cfg string) error { + defer b.tracelog() + return b.client.SetBiosConfigurationFromFile(ctx, cfg) +} + func (b *Client) tracelog() { pc, _, _, _ := runtime.Caller(1) funcName := path.Base(runtime.FuncForPC(pc).Name()) @@ -240,6 +246,9 @@ func newBmclibClient(asset *model.Asset, l *logrus.Entry) *bmclib.Client { providers.FeatureBootDeviceSet, providers.FeaturePowerSet, providers.FeaturePowerState, + providers.FeatureResetBiosConfiguration, + providers.FeatureSetBiosConfiguration, + providers.FeatureSetBiosConfigurationFromFile, ) // NOTE: remove the .Using("redfish") before this ends up in prod diff --git a/internal/store/bmc/dryRun.go b/internal/store/bmc/dryRun.go index ff2edb1..a9feeef 100644 --- a/internal/store/bmc/dryRun.go +++ b/internal/store/bmc/dryRun.go @@ -119,7 +119,7 @@ func (b *DryRunBMCClient) HostBooted(_ context.Context) (bool, error) { return true, nil } -func (b *DryRunBMCClient) ResetBios(ctx context.Context) error { +func (b *DryRunBMCClient) ResetBiosConfig(ctx context.Context) error { _, ok := serverStates[b.id] if !ok { return errBmcCantFindServer @@ -130,6 +130,10 @@ func (b *DryRunBMCClient) ResetBios(ctx context.Context) error { return b.SetPowerState(ctx, "cycle") } +func (b *DryRunBMCClient) SetBiosConfigFromFile(_ context.Context, _ string) error { + return nil +} + // getServer gets a simulateed server state, and update power status and boot device if required func (b *DryRunBMCClient) getServer() (*server, error) { state, ok := serverStates[b.id] diff --git a/internal/store/bmc/interface.go b/internal/store/bmc/interface.go index 20f3953..f531ee2 100644 --- a/internal/store/bmc/interface.go +++ b/internal/store/bmc/interface.go @@ -14,5 +14,6 @@ type BMC interface { GetBootDevice(ctx context.Context) (device string, persistent, efiBoot bool, err error) PowerCycleBMC(ctx context.Context) error HostBooted(ctx context.Context) (bool, error) - ResetBios(ctx context.Context) error + ResetBiosConfig(ctx context.Context) error + SetBiosConfigFromFile(ctx context.Context, cfg string) error }