From 2a6ffd16bbeb87b577db7fed3b04ac667519fffc Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 29 Sep 2023 09:24:18 -0600 Subject: [PATCH 01/16] Refactor some database code and added delete function --- internal/db/sqlite/sqlite.go | 46 ++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/internal/db/sqlite/sqlite.go b/internal/db/sqlite/sqlite.go index 39ff33b..0122b0f 100644 --- a/internal/db/sqlite/sqlite.go +++ b/internal/db/sqlite/sqlite.go @@ -8,12 +8,7 @@ import ( "github.com/jmoiron/sqlx" ) -func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error { - if states == nil { - return fmt.Errorf("states == nil") - } - - // create database if it doesn't already exist +func CreateProbeResultsIfNotExists(path string) (*sqlx.DB, error) { schema := ` CREATE TABLE IF NOT EXISTS magellan_scanned_ports ( host TEXT NOT NULL, @@ -25,9 +20,22 @@ func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error { ` db, err := sqlx.Open("sqlite3", path) if err != nil { - return fmt.Errorf("could not open database: %v", err) + return nil, fmt.Errorf("could not open database: %v", err) } db.MustExec(schema) + return db, nil +} + +func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error { + if states == nil { + return fmt.Errorf("states == nil") + } + + // create database if it doesn't already exist + db, err := CreateProbeResultsIfNotExists(path) + if err != nil { + return err + } // insert all probe states into db tx := db.MustBegin() @@ -46,6 +54,30 @@ func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error { return nil } +func DeleteProbeResults(path string, results *[]magellan.BMCProbeResult) error { + if results == nil { + return fmt.Errorf("no probe results found") + } + db, err := sqlx.Open("sqlite3", path) + if err != nil { + return fmt.Errorf("could not open database: %v", err) + } + tx := db.MustBegin() + for _, state := range *results { + sql := `DELETE FROM magellan_scanned_ports WHERE host = :host, port = :port;` + _, err := tx.NamedExec(sql, &state) + if err != nil { + fmt.Printf("could not execute transaction: %v\n", err) + } + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("could not commit transaction: %v", err) + } + return nil +} + func GetProbeResults(path string) ([]magellan.BMCProbeResult, error) { db, err := sqlx.Open("sqlite3", path) if err != nil { From 9f848113c770e041f2fb6378d39942de3b707921 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 29 Sep 2023 09:32:06 -0600 Subject: [PATCH 02/16] Minor refactor and update `README.md` --- README.md | 17 +++++++++++------ internal/api/smd/smd.go | 8 ++------ internal/collect.go | 3 +++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1f12869..124f80a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Magellan -Magellan is a small tool designed to scan a network and collect BMC information -to load the data into an [`hms-smd`](https://github.com/alexlovelltroy/hms-smd/tree/master) instance. +Magellan is a board management controller discovery tool designed to scan a network +and collect information about a BMC node and load that data into an +[`hms-smd`](https://github.com/bikeshack/smd/tree/master) instance. ## How It Works @@ -13,9 +14,13 @@ Magellan is designed to do three things: Magellan first tries to probe for specified hosts using the [`dora`](https://github.com/bmc-toolbox/dora) API. If that fails, it then tries to use its own built-in, simpler scanner as a fallback. -Next, it tries to query information about the BMC node using `bmclib` functions, but requires -access to a redfish interface on the node to work. Once the BMC information is received, -it is then stored into `hms-smd` using its API. +This is done by sending a raw TCP request to a number of potential hosts over a +network, and noting which requests are successful. At this point, `magellan` sees +no difference between a services. + +Next, it tries to query information about the BMC node using `bmclib` functions, +but requires access to a redfish interface on the node to work. Once the BMC +information is received, it is then stored into `hms-smd` using its API. In summary, `magellan` needs at minimum the following configured to work on each node: @@ -33,7 +38,7 @@ cd magellan go mod tidy && go build ``` -This should find and download all of the required dependencies. Although other +This should find and download all of the required dependencies. Although other versions of Go may work, the project has only been tested with v1.20. ## Usage diff --git a/internal/api/smd/smd.go b/internal/api/smd/smd.go index a8369ea..67a56d1 100644 --- a/internal/api/smd/smd.go +++ b/internal/api/smd/smd.go @@ -51,9 +51,7 @@ func AddRedfishEndpoint(data []byte, headers map[string]string) error { // Add redfish endpoint via POST `/hsm/v2/Inventory/RedfishEndpoints` endpoint url := makeEndpointUrl("/Inventory/RedfishEndpoints") res, body, err := util.MakeRequest(url, "POST", data, headers) - fmt.Printf("smd url: %v\n", url) - fmt.Printf("res: %v\n", res.Status) - fmt.Printf("body: %v\n", string(body)) + fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) if res != nil { if res.StatusCode != http.StatusOK { return fmt.Errorf("could not add redfish endpoint") @@ -69,9 +67,7 @@ func UpdateRedfishEndpoint(xname string, data []byte, headers map[string]string) // Update redfish endpoint via PUT `/hsm/v2/Inventory/RedfishEndpoints` endpoint url := makeEndpointUrl("/Inventory/RedfishEndpoints/" + xname) res, body, err := util.MakeRequest(url, "PUT", data, headers) - fmt.Printf("smd url: %v\n", url) - fmt.Printf("res: %v\n", res.Status) - fmt.Printf("body: %v\n", string(body)) + fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) if res != nil { if res.StatusCode != http.StatusOK { return fmt.Errorf("could not update redfish endpoint") diff --git a/internal/collect.go b/internal/collect.go index 9ca35b9..76dcf06 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -638,6 +638,9 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { func connectGofish(q *QueryParams) (*gofish.APIClient, error) { config := makeGofishConfig(q) c, err := gofish.Connect(config) + if err != nil { + return nil, err + } c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{ ExpandQuery: gofish.Expand{ ExpandAll: true, From b8944775a9f7e60f3cd6c1f3bf7ab9c9c78b0908 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 29 Sep 2023 12:54:38 -0600 Subject: [PATCH 03/16] Add `UpdateFirmware` func and some refactoring --- internal/collect.go | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/internal/collect.go b/internal/collect.go index 76dcf06..3f6e73a 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -178,7 +178,7 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e var rm map[string]json.RawMessage // inventories - inventory, err := QueryInventory(client, l, q) + inventory, err := QueryInventory(client, q) if err != nil { l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err) } @@ -195,7 +195,7 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e data["Chassis"] = rm["Chassis"] // ethernet interfaces - interfaces, err := QueryEthernetInterfaces(client, l, q) + interfaces, err := QueryEthernetInterfaces(client, q) if err != nil { l.Log.Errorf("could not query ethernet interfaces: %v", err) continue @@ -318,7 +318,7 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e return nil } -func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) { // client, err := NewClient(l, q) // open BMC session and update driver registry @@ -352,7 +352,7 @@ func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte return b, nil } -func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) { // open BMC session and update driver registry ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) client.Registry.FilterForCompatible(ctx) @@ -385,7 +385,7 @@ func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byt return b, nil } -func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) { ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) client.Registry.FilterForCompatible(ctx) err := client.PreferProvider(q.Preferred).Open(ctx) @@ -417,7 +417,7 @@ func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]by } -func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) { // open BMC session and update driver registry ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) client.Registry.FilterForCompatible(ctx) @@ -451,7 +451,7 @@ func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, e return b, nil } -func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryBios(client *bmclib.Client, q *QueryParams) ([]byte, error) { // client, err := NewClient(l, q) // if err != nil { // return nil, fmt.Errorf("could not make query: %v", err) @@ -463,7 +463,7 @@ func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, er return b, err } -func QueryEthernetInterfaces(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, error) { c, err := connectGofish(q) if err != nil { return nil, fmt.Errorf("could not connect to bmc: %v", err) @@ -588,12 +588,7 @@ func QueryRegisteries(q *QueryParams) ([]byte, error) { } func QueryProcessors(q *QueryParams) ([]byte, error) { - baseUrl := "https://" - if q.User != "" && q.Pass != "" { - baseUrl += fmt.Sprintf("%s:%s@", q.User, q.Pass) - } - baseUrl += fmt.Sprintf("%s:%d", q.Host, q.Port) - url := baseUrl + "/redfish/v1/Systems" + url := baseUrl(q) + "Systems" res, body, err := util.MakeRequest(url, "GET", nil, nil) if err != nil { return nil, fmt.Errorf("something went wrong: %v", err) @@ -612,7 +607,7 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { // request data about each processor member on node for _, member := range members { var oid = member["@odata.id"].(string) - var infoUrl = baseUrl + oid + var infoUrl = url + oid res, _, err := util.MakeRequest(infoUrl, "GET", nil, nil) if err != nil { return nil, fmt.Errorf("something went wrong: %v", err) @@ -635,6 +630,37 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { return b, nil } +func UpdateFirmware(q *QueryParams) error { + + return nil +} + +func UpdateFirmwareRemote(imageURI string, q *QueryParams) error { + url := baseUrl(q) + "UpdateService/Actions/SimpleUpdate" + b := map[string]any{ + "UpdateComponent": "BMC", + "TransferProtocol": "HTTP", + "ImageURI": imageURI, + } + data, err := json.Marshal(b) + if err != nil { + return fmt.Errorf("could not marshal data: %v", err) + } + headers := map[string]string{ + "Content-Type": "application/json", + "cache-control": "no-cache", + } + res, _, err := util.MakeRequest(url, "POST", data, headers) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", url) + } else if res.StatusCode != http.StatusOK { + return fmt.Errorf("returned status code %d", res.StatusCode) + } + return nil +} + func connectGofish(q *QueryParams) (*gofish.APIClient, error) { config := makeGofishConfig(q) c, err := gofish.Connect(config) @@ -651,12 +677,7 @@ func connectGofish(q *QueryParams) (*gofish.APIClient, error) { } func makeGofishConfig(q *QueryParams) gofish.ClientConfig { - url := "https://" - if q.User != "" && q.Pass != "" { - url += fmt.Sprintf("%s:%s@", q.User, q.Pass) - } - url += fmt.Sprintf("%s:%d", q.Host, q.Port) - + url := baseUrl(q) return gofish.ClientConfig{ Endpoint: url, Username: q.User, @@ -694,3 +715,12 @@ func makeJson(object any) ([]byte, error) { } return []byte(b), nil } + +func baseUrl(q *QueryParams) string { + url := "https://" + if q.User != "" && q.Pass != "" { + url += fmt.Sprintf("%s:%s@", q.User, q.Pass) + } + url += fmt.Sprintf("%s:%d", q.Host, q.Port) + return url + "/redfish/v1/" +} From 372f68e2ff81c04b111c5291f789f98a2be3446d Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 2 Oct 2023 09:22:16 -0600 Subject: [PATCH 04/16] Added bmclib-based firmware update function --- internal/collect.go | 97 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/internal/collect.go b/internal/collect.go index 3f6e73a..2150ab8 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -5,20 +5,25 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "errors" "fmt" "net/http" "os" "path" + "strings" "sync" "time" "github.com/bikeshack/magellan/internal/log" + "github.com/sirupsen/logrus" "github.com/bikeshack/magellan/internal/api/smd" "github.com/bikeshack/magellan/internal/util" "github.com/Cray-HPE/hms-xname/xnames" bmclib "github.com/bmc-toolbox/bmclib/v2" + "github.com/bmc-toolbox/bmclib/v2/constants" + bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/jacobweinstock/registrar" _ "github.com/mattn/go-sqlite3" "github.com/stmcginnis/gofish" @@ -630,7 +635,97 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { return b, nil } -func UpdateFirmware(q *QueryParams) error { +func UpdateFirmware(client bmclib.Client, l log.Logger, q *QueryParams) error { + // open BMC session and update driver registry + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) + client.Registry.FilterForCompatible(ctx) + err := client.Open(ctx) + if err != nil { + ctxCancel() + return fmt.Errorf("could not connect to bmc: %v", err) + } + + defer client.Close(ctx) + + filepath := "" + file, err := os.Open(filepath) + if err != nil { + ctxCancel() + return fmt.Errorf("could not open firmware path: %v", err) + } + + defer file.Close() + + component := "" + taskId, err := client.FirmwareInstall(ctx, component, constants.FirmwareApplyOnReset, true, file) + if err != nil { + ctxCancel() + return fmt.Errorf("could not install firmware: %v", err) + } + + firmwareVersion := "" + for { + if ctx.Err() != nil { + ctxCancel() + return fmt.Errorf("context error: %v", ctx.Err()) + } + + state, err := client.FirmwareInstallStatus(ctx, firmwareVersion, component, taskId) + if err != nil { + // when its under update a connection refused is returned + if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") { + l.Log.Info("BMC refused connection, BMC most likely resetting...") + time.Sleep(2 * time.Second) + + continue + } + + if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") { + err := client.Open(ctx) + if err != nil { + l.Log.Fatal(err, "bmc re-login failed") + } + + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("BMC session expired, logging in...") + + continue + } + + l.Log.Fatal(err) + } + + switch state { + case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing: + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install running") + + case constants.FirmwareInstallFailed: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install failed") + return fmt.Errorf("failed to install firmware") + + case constants.FirmwareInstallComplete: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install completed") + return nil + + case constants.FirmwareInstallPowerCyleHost: + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("host powercycle required") + + if _, err := client.SetPowerState(ctx, "cycle"); err != nil { + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("error power cycling host for install") + return fmt.Errorf("failed to install firmware") + } + + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("host power cycled, all done!") + return nil + default: + l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("unknown state returned") + } + + time.Sleep(2 * time.Second) + } return nil } From 10ba18ade4b166de6db88b6e89d57d5f651af866 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 3 Oct 2023 10:17:46 -0600 Subject: [PATCH 05/16] Modify firmware updating function with HTTP --- internal/collect.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/collect.go b/internal/collect.go index 2150ab8..35b7ce0 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -730,12 +730,12 @@ func UpdateFirmware(client bmclib.Client, l log.Logger, q *QueryParams) error { return nil } -func UpdateFirmwareRemote(imageURI string, q *QueryParams) error { +func UpdateFirmwareV2(serverIP string, imageURI string, component string, q *QueryParams) error { url := baseUrl(q) + "UpdateService/Actions/SimpleUpdate" b := map[string]any{ - "UpdateComponent": "BMC", + "UpdateComponent": component, // BMC, BIOS "TransferProtocol": "HTTP", - "ImageURI": imageURI, + "ImageURI": "http://" + serverIP + "/" + imageURI, } data, err := json.Marshal(b) if err != nil { From b7b31388a7deb24e11e0b0019d55922d2e0ea50f Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 3 Oct 2023 11:58:03 -0600 Subject: [PATCH 06/16] Added `update` command --- cmd/update.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 cmd/update.go diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..6d89e76 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,49 @@ +package cmd + +import ( + magellan "github.com/bikeshack/magellan/internal" + "github.com/bikeshack/magellan/internal/log" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + firmwarePath string + firmwareVersion string + component string +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update BMC node firmware", + Run: func(cmd *cobra.Command, args []string) { + l := log.NewLogger(logrus.New(), logrus.DebugLevel) + q := &magellan.UpdateParams { + FirmwarePath: firmwarePath, + FirmwareVersion: firmwareVersion, + Component: component, + QueryParams: magellan.QueryParams{ + User: user, + Pass: pass, + Timeout: timeout, + }, + } + client, err := magellan.NewClient(l, &q.QueryParams) + if err != nil { + l.Log.Errorf("could not make client: %v", err) + } + err = magellan.UpdateFirmware(client, l, q) + if err != nil { + l.Log.Errorf("could not update firmware: %v", err) + } + }, +} + +func init() { + updateCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user") + updateCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password") + updateCmd.PersistentFlags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") + updateCmd.PersistentFlags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") + updateCmd.PersistentFlags().StringVar(&component, "component", "", "set the component to upgrade") + rootCmd.AddCommand(updateCmd) +} \ No newline at end of file From 62dd01e1cab02f4bb367758109ed49eb2a191716 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 3 Oct 2023 12:01:09 -0600 Subject: [PATCH 07/16] Moved update code from collect.go to update.go --- internal/collect.go | 126 -------------------------------------- internal/update.go | 145 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 126 deletions(-) create mode 100644 internal/update.go diff --git a/internal/collect.go b/internal/collect.go index 35b7ce0..c1781f7 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -5,25 +5,20 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "errors" "fmt" "net/http" "os" "path" - "strings" "sync" "time" "github.com/bikeshack/magellan/internal/log" - "github.com/sirupsen/logrus" "github.com/bikeshack/magellan/internal/api/smd" "github.com/bikeshack/magellan/internal/util" "github.com/Cray-HPE/hms-xname/xnames" bmclib "github.com/bmc-toolbox/bmclib/v2" - "github.com/bmc-toolbox/bmclib/v2/constants" - bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/jacobweinstock/registrar" _ "github.com/mattn/go-sqlite3" "github.com/stmcginnis/gofish" @@ -635,127 +630,6 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { return b, nil } -func UpdateFirmware(client bmclib.Client, l log.Logger, q *QueryParams) error { - // open BMC session and update driver registry - ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) - client.Registry.FilterForCompatible(ctx) - err := client.Open(ctx) - if err != nil { - ctxCancel() - return fmt.Errorf("could not connect to bmc: %v", err) - } - - defer client.Close(ctx) - - filepath := "" - file, err := os.Open(filepath) - if err != nil { - ctxCancel() - return fmt.Errorf("could not open firmware path: %v", err) - } - - defer file.Close() - - component := "" - taskId, err := client.FirmwareInstall(ctx, component, constants.FirmwareApplyOnReset, true, file) - if err != nil { - ctxCancel() - return fmt.Errorf("could not install firmware: %v", err) - } - - firmwareVersion := "" - for { - if ctx.Err() != nil { - ctxCancel() - return fmt.Errorf("context error: %v", ctx.Err()) - } - - state, err := client.FirmwareInstallStatus(ctx, firmwareVersion, component, taskId) - if err != nil { - // when its under update a connection refused is returned - if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") { - l.Log.Info("BMC refused connection, BMC most likely resetting...") - time.Sleep(2 * time.Second) - - continue - } - - if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") { - err := client.Open(ctx) - if err != nil { - l.Log.Fatal(err, "bmc re-login failed") - } - - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("BMC session expired, logging in...") - - continue - } - - l.Log.Fatal(err) - } - - switch state { - case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing: - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install running") - - case constants.FirmwareInstallFailed: - ctxCancel() - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install failed") - return fmt.Errorf("failed to install firmware") - - case constants.FirmwareInstallComplete: - ctxCancel() - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("firmware install completed") - return nil - - case constants.FirmwareInstallPowerCyleHost: - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("host powercycle required") - - if _, err := client.SetPowerState(ctx, "cycle"); err != nil { - ctxCancel() - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("error power cycling host for install") - return fmt.Errorf("failed to install firmware") - } - - ctxCancel() - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("host power cycled, all done!") - return nil - default: - l.Log.WithFields(logrus.Fields{"state": state, "component": component}).Info("unknown state returned") - } - - time.Sleep(2 * time.Second) - } - - return nil -} - -func UpdateFirmwareV2(serverIP string, imageURI string, component string, q *QueryParams) error { - url := baseUrl(q) + "UpdateService/Actions/SimpleUpdate" - b := map[string]any{ - "UpdateComponent": component, // BMC, BIOS - "TransferProtocol": "HTTP", - "ImageURI": "http://" + serverIP + "/" + imageURI, - } - data, err := json.Marshal(b) - if err != nil { - return fmt.Errorf("could not marshal data: %v", err) - } - headers := map[string]string{ - "Content-Type": "application/json", - "cache-control": "no-cache", - } - res, _, err := util.MakeRequest(url, "POST", data, headers) - if err != nil { - return fmt.Errorf("something went wrong: %v", err) - } else if res == nil { - return fmt.Errorf("no response returned (url: %s)", url) - } else if res.StatusCode != http.StatusOK { - return fmt.Errorf("returned status code %d", res.StatusCode) - } - return nil -} - func connectGofish(q *QueryParams) (*gofish.APIClient, error) { config := makeGofishConfig(q) c, err := gofish.Connect(config) diff --git a/internal/update.go b/internal/update.go new file mode 100644 index 0000000..30c61b2 --- /dev/null +++ b/internal/update.go @@ -0,0 +1,145 @@ +package magellan + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strings" + "time" + + "github.com/bikeshack/magellan/internal/log" + "github.com/bikeshack/magellan/internal/util" + bmclib "github.com/bmc-toolbox/bmclib/v2" + "github.com/bmc-toolbox/bmclib/v2/constants" + bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" + "github.com/sirupsen/logrus" +) + + +type UpdateParams struct { + QueryParams + FirmwarePath string + FirmwareVersion string + Component string +} + +func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error { + // open BMC session and update driver registry + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) + client.Registry.FilterForCompatible(ctx) + err := client.Open(ctx) + if err != nil { + ctxCancel() + return fmt.Errorf("could not connect to bmc: %v", err) + } + + defer client.Close(ctx) + + file, err := os.Open(q.FirmwarePath) + if err != nil { + ctxCancel() + return fmt.Errorf("could not open firmware path: %v", err) + } + + defer file.Close() + + taskId, err := client.FirmwareInstall(ctx, q.Component, constants.FirmwareApplyOnReset, true, file) + if err != nil { + ctxCancel() + return fmt.Errorf("could not install firmware: %v", err) + } + + for { + if ctx.Err() != nil { + ctxCancel() + return fmt.Errorf("context error: %v", ctx.Err()) + } + + state, err := client.FirmwareInstallStatus(ctx, q.FirmwareVersion, q.Component, taskId) + if err != nil { + // when its under update a connection refused is returned + if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") { + l.Log.Info("BMC refused connection, BMC most likely resetting...") + time.Sleep(2 * time.Second) + + continue + } + + if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") { + err := client.Open(ctx) + if err != nil { + l.Log.Fatal(err, "bmc re-login failed") + } + + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("BMC session expired, logging in...") + + continue + } + + l.Log.Fatal(err) + } + + switch state { + case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install running") + + case constants.FirmwareInstallFailed: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install failed") + return fmt.Errorf("failed to install firmware") + + case constants.FirmwareInstallComplete: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install completed") + return nil + + case constants.FirmwareInstallPowerCyleHost: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host powercycle required") + + if _, err := client.SetPowerState(ctx, "cycle"); err != nil { + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("error power cycling host for install") + return fmt.Errorf("failed to install firmware") + } + + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host power cycled, all done!") + return nil + default: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("unknown state returned") + } + + time.Sleep(2 * time.Second) + } + + return nil +} + +func UpdateFirmwareV2(serverIP string, imageURI string, component string, q *QueryParams) error { + url := baseUrl(q) + "UpdateService/Actions/SimpleUpdate" + b := map[string]any{ + "UpdateComponent": component, // BMC, BIOS + "TransferProtocol": "HTTP", + "ImageURI": "http://" + serverIP + "/" + imageURI, + } + data, err := json.Marshal(b) + if err != nil { + return fmt.Errorf("could not marshal data: %v", err) + } + headers := map[string]string{ + "Content-Type": "application/json", + "cache-control": "no-cache", + } + res, _, err := util.MakeRequest(url, "POST", data, headers) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", url) + } else if res.StatusCode != http.StatusOK { + return fmt.Errorf("returned status code %d", res.StatusCode) + } + return nil +} \ No newline at end of file From 5b390a65c29420607f017e2371afdc260544d735 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 12:55:16 -0600 Subject: [PATCH 08/16] Modified update command and implementation --- cmd/update.go | 49 ++++++++++++++++++++++++++------ internal/update.go | 70 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index 6d89e76..4894e7d 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -8,9 +8,13 @@ import ( ) var ( + host string + port int firmwarePath string firmwareVersion string component string + transferProtocol string + status bool ) var updateCmd = &cobra.Command{ @@ -22,17 +26,39 @@ var updateCmd = &cobra.Command{ FirmwarePath: firmwarePath, FirmwareVersion: firmwareVersion, Component: component, + TransferProtocol: transferProtocol, QueryParams: magellan.QueryParams{ + Drivers: []string{"redfish"}, + Preferred: "redfish", + Host: host, User: user, Pass: pass, Timeout: timeout, + Port: port, + WithSecureTLS: withSecureTLS, }, } - client, err := magellan.NewClient(l, &q.QueryParams) - if err != nil { - l.Log.Errorf("could not make client: %v", err) + + // check if required params are set + if host == "" || user == "" || pass == "" { + l.Log.Fatal("requires host, user, and pass to be set") } - err = magellan.UpdateFirmware(client, l, q) + + // get status if flag is set and exit + if status { + err := magellan.GetUpdateStatus(q) + if err != nil { + l.Log.Errorf("could not get update status: %v", err) + } + return + } + + // client, err := magellan.NewClient(l, &q.QueryParams) + // if err != nil { + // l.Log.Errorf("could not make client: %v", err) + // } + // err = magellan.UpdateFirmware(client, l, q) + err := magellan.UpdateFirmwareRemote(q) if err != nil { l.Log.Errorf("could not update firmware: %v", err) } @@ -40,10 +66,15 @@ var updateCmd = &cobra.Command{ } func init() { - updateCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user") - updateCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password") - updateCmd.PersistentFlags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") - updateCmd.PersistentFlags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") - updateCmd.PersistentFlags().StringVar(&component, "component", "", "set the component to upgrade") + updateCmd.Flags().StringVar(&host, "host", "", "set the BMC host") + updateCmd.Flags().IntVar(&port, "port", 443, "set the BMC port") + updateCmd.Flags().StringVar(&user, "user", "", "set the BMC user") + updateCmd.Flags().StringVar(&pass, "pass", "", "set the BMC password") + updateCmd.Flags().StringVar(&transferProtocol, "protocol", "HTTP", "set the transfer protocol") + updateCmd.Flags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") + updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") + updateCmd.Flags().StringVar(&component, "component", "", "set the component to upgrade") + updateCmd.Flags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS") + updateCmd.Flags().BoolVar(&status, "status", false, "get the status of the update") rootCmd.AddCommand(updateCmd) } \ No newline at end of file diff --git a/internal/update.go b/internal/update.go index 30c61b2..7881449 100644 --- a/internal/update.go +++ b/internal/update.go @@ -24,9 +24,16 @@ type UpdateParams struct { FirmwarePath string FirmwareVersion string Component string + TransferProtocol string } +// NOTE: Does not work since OpenBMC, whic bmclib uses underneath, does not +// support multipart updates. See issue: https://github.com/bmc-toolbox/bmclib/issues/341 func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error { + if q.Component == "" { + return fmt.Errorf("component is required") + } + // open BMC session and update driver registry ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) client.Registry.FilterForCompatible(ctx) @@ -118,22 +125,36 @@ func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error return nil } -func UpdateFirmwareV2(serverIP string, imageURI string, component string, q *QueryParams) error { - url := baseUrl(q) + "UpdateService/Actions/SimpleUpdate" +func UpdateFirmwareRemote(q *UpdateParams) error { + url := baseRedfishUrl(&q.QueryParams) + "/UpdateService/Actions/SimpleUpdate" + headers := map[string]string { + "Content-Type": "application/json", + "cache-control": "no-cache", + } b := map[string]any{ - "UpdateComponent": component, // BMC, BIOS - "TransferProtocol": "HTTP", - "ImageURI": "http://" + serverIP + "/" + imageURI, + "UpdateComponent": q.Component, // BMC, BIOS + "TransferProtocol": q.TransferProtocol, + "ImageURI": q.FirmwarePath, } data, err := json.Marshal(b) if err != nil { return fmt.Errorf("could not marshal data: %v", err) } - headers := map[string]string{ - "Content-Type": "application/json", - "cache-control": "no-cache", + res, body, err := util.MakeRequest(url, "POST", data, headers) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", url) } - res, _, err := util.MakeRequest(url, "POST", data, headers) + if len(body) > 0 { + fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + } + return nil +} + +func GetUpdateStatus(q *UpdateParams) error { + url := baseRedfishUrl(&q.QueryParams) + "/UpdateService" + res, body, err := util.MakeRequest(url, "GET", nil, nil) if err != nil { return fmt.Errorf("something went wrong: %v", err) } else if res == nil { @@ -141,5 +162,34 @@ func UpdateFirmwareV2(serverIP string, imageURI string, component string, q *Que } else if res.StatusCode != http.StatusOK { return fmt.Errorf("returned status code %d", res.StatusCode) } + if len(body) > 0 { + fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + } return nil -} \ No newline at end of file +} + +// func UpdateFirmwareLocal(q *UpdateParams) error { +// fwUrl := baseUrl(&q.QueryParams) + "" +// url := baseUrl(&q.QueryParams) + "UpdateService/Actions/" +// headers := map[string]string { + +// } + +// // get etag from FW inventory +// response, err := util.MakeRequest() + +// // load file from disk +// file, err := os.ReadFile(q.FirmwarePath) +// if err != nil { +// return fmt.Errorf("could not read file: %v", err) +// } + + + +// switch q.TransferProtocol { +// case "HTTP": +// default: +// return fmt.Errorf("transfer protocol not supported") +// } +// return nil +// } \ No newline at end of file From d8bd169cb7296f718655161604ebae5b45274286 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 12:55:58 -0600 Subject: [PATCH 09/16] Added `protocol` flag --- cmd/collect.go | 2 ++ cmd/root.go | 1 + internal/collect.go | 47 +++++++++++++++++++++------------------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index e54be33..8d9a89b 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -33,6 +33,7 @@ var collectCmd = &cobra.Command{ q := &magellan.QueryParams{ User: user, Pass: pass, + Protocol: protocol, Drivers: drivers, Preferred: preferredDriver, Timeout: timeout, @@ -58,6 +59,7 @@ func init() { collectCmd.PersistentFlags().IntVarP(&smd.Port, "port", "p", smd.Port, "set the port to the smd API") collectCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user") collectCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password") + collectCmd.PersistentFlags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol") collectCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "/tmp/magellan/data/", "set the path to store collection data") collectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", true, "set flag to force update data sent to SMD ") collectCmd.PersistentFlags().StringVar(&preferredDriver, "preferred-driver", "ipmi", "set the preferred driver to use") diff --git a/cmd/root.go b/cmd/root.go index 3fa5217..82d6b4e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,6 +12,7 @@ var ( threads int ports []int hosts []string + protocol string withSecureTLS bool certPoolFile string user string diff --git a/internal/collect.go b/internal/collect.go index c1781f7..5a949fb 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -44,6 +44,7 @@ type BMCProbeResult struct { type QueryParams struct { Host string Port int + Protocol string User string Pass string Drivers []string @@ -59,9 +60,6 @@ type QueryParams struct { } func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { - // NOTE: bmclib.NewClient(host, port, user, pass) - // ...seems like the `port` params doesn't work like expected depending on interface - tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -83,22 +81,20 @@ func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { } // only work if valid cert is provided - if q.WithSecureTLS { - var pool *x509.CertPool - if q.CertPoolFile != "" { - pool = x509.NewCertPool() - data, err := os.ReadFile(q.CertPoolFile) - if err != nil { - return nil, fmt.Errorf("could not read cert pool file: %v", err) - } - pool.AppendCertsFromPEM(data) + if q.WithSecureTLS && q.CertPoolFile != "" { + pool := x509.NewCertPool() + data, err := os.ReadFile(q.CertPoolFile) + if err != nil { + return nil, fmt.Errorf("could not read cert pool file: %v", err) } + pool.AppendCertsFromPEM(data) // a nil pool uses the system certs clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool)) } url := "" + fmt.Println(url) if q.User != "" && q.Pass != "" { - url += fmt.Sprintf("https://%s:%s@%s", q.User, q.Pass, q.Host) + url += fmt.Sprintf("%s://%s:%s@%s", q.Protocol, q.User, q.Pass, q.Host) } else { url += q.Host } @@ -469,7 +465,7 @@ func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, err return nil, fmt.Errorf("could not connect to bmc: %v", err) } - interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/") + interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "") if err != nil { return nil, fmt.Errorf("could not get ethernet interfaces: %v", err) } @@ -588,7 +584,7 @@ func QueryRegisteries(q *QueryParams) ([]byte, error) { } func QueryProcessors(q *QueryParams) ([]byte, error) { - url := baseUrl(q) + "Systems" + url := baseRedfishUrl(q) + "/Systems" res, body, err := util.MakeRequest(url, "GET", nil, nil) if err != nil { return nil, fmt.Errorf("something went wrong: %v", err) @@ -636,17 +632,19 @@ func connectGofish(q *QueryParams) (*gofish.APIClient, error) { if err != nil { return nil, err } - c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{ - ExpandQuery: gofish.Expand{ - ExpandAll: true, - Links: true, - }, + if c.Service != nil { + c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{ + ExpandQuery: gofish.Expand{ + ExpandAll: true, + Links: true, + }, + } } return c, err } func makeGofishConfig(q *QueryParams) gofish.ClientConfig { - url := baseUrl(q) + url := baseRedfishUrl(q) return gofish.ClientConfig{ Endpoint: url, Username: q.User, @@ -685,11 +683,10 @@ func makeJson(object any) ([]byte, error) { return []byte(b), nil } -func baseUrl(q *QueryParams) string { - url := "https://" +func baseRedfishUrl(q *QueryParams) string { + url := fmt.Sprintf("%s://", q.Protocol) if q.User != "" && q.Pass != "" { url += fmt.Sprintf("%s:%s@", q.User, q.Pass) } - url += fmt.Sprintf("%s:%d", q.Host, q.Port) - return url + "/redfish/v1/" + return fmt.Sprintf("%s%s:%d", url, q.Host, q.Port) } From d09b71262ad08b3a9006714c6a2fc1c5b8a5238e Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 12:57:08 -0600 Subject: [PATCH 10/16] Minor formatting --- bin/magellan.sh | 14 +++++++++----- internal/util/util.go | 30 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/bin/magellan.sh b/bin/magellan.sh index 0850f87..676c62d 100644 --- a/bin/magellan.sh +++ b/bin/magellan.sh @@ -1,17 +1,21 @@ function build(){ - go mod tidy && go build -C bin/magellan + go mod tidy && go build -C bin/magellan } function scan() { - ./magellan scan --subnet 172.16.0.0 --port 443 + ./magellan scan --subnet 172.16.0.0 --port 443 } -function list(){ - ./magellan list +function list() { + ./magellan list +} + +function update() { + ./magellan update --user admin --pass password --host 172.16.0.109 --component BMC --protocol HTTP --firmware-path "" } function collect() { - ./magellan collect --user admin --pass password + ./magellan collect --user admin --pass password } diff --git a/internal/util/util.go b/internal/util/util.go index 661d2ba..0efa1b5 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -11,10 +11,10 @@ import ( ) func PathExists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { return true, nil } - if os.IsNotExist(err) { return false, nil } - return false, err + _, err := os.Stat(path) + if err == nil { return true, nil } + if os.IsNotExist(err) { return false, nil } + return false, err } func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) { @@ -37,20 +37,20 @@ func MakeRequest(url string, httpMethod string, body []byte, headers map[string] } func MakeOutputDirectory(path string) (string, error) { - // get the current data + time using Go's stupid formatting - t := time.Now() - dirname := t.Format("2006-01-01 15:04:05") - final := path + "/" + dirname + // get the current data + time using Go's stupid formatting + t := time.Now() + dirname := t.Format("2006-01-01 15:04:05") + final := path + "/" + dirname // check if path is valid and directory - pathExists, err := PathExists(final); - if err != nil { - return final, fmt.Errorf("could not check for existing path: %v", err) - } + pathExists, err := PathExists(final); + if err != nil { + return final, fmt.Errorf("could not check for existing path: %v", err) + } if pathExists { - // make sure it is directory with 0o644 permissions - return final, fmt.Errorf("found existing path: %v", final) - } + // make sure it is directory with 0o644 permissions + return final, fmt.Errorf("found existing path: %v", final) + } // create directory with data + time err = os.MkdirAll(final, 0766) From da648f98b8e1bc10319906eb9a922963bf8323b2 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 13:22:23 -0600 Subject: [PATCH 11/16] Fixed ethernet interfaces not returning correct information --- internal/collect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/collect.go b/internal/collect.go index 5a949fb..3c6941c 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -465,7 +465,7 @@ func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, err return nil, fmt.Errorf("could not connect to bmc: %v", err) } - interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "") + interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/Self/EthernetInterfaces/") if err != nil { return nil, fmt.Errorf("could not get ethernet interfaces: %v", err) } From ac1dc023c01069ea3ef1fbe3d5f0425a4f3b8961 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 13:52:07 -0600 Subject: [PATCH 12/16] Added missing error check to `MakeRequest` function --- internal/util/util.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/util/util.go b/internal/util/util.go index 0efa1b5..990bda9 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -19,7 +19,10 @@ func PathExists(path string) (bool, error) { func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - req, _ := http.NewRequest(httpMethod, url, bytes.NewBuffer(body)) + req, err := http.NewRequest(httpMethod, url, bytes.NewBuffer(body)) + if err != nil { + return nil, nil, fmt.Errorf("could not create new HTTP request: %v", err) + } req.Header.Add("User-Agent", "magellan") for k, v := range headers { req.Header.Add(k, v) From a03a54f35e813d4a0835403053b1ad2e66c42c41 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 13:54:03 -0600 Subject: [PATCH 13/16] Added protocol flag to update command --- cmd/update.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/update.go b/cmd/update.go index 4894e7d..b2b854a 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -71,6 +71,7 @@ func init() { updateCmd.Flags().StringVar(&user, "user", "", "set the BMC user") updateCmd.Flags().StringVar(&pass, "pass", "", "set the BMC password") updateCmd.Flags().StringVar(&transferProtocol, "protocol", "HTTP", "set the transfer protocol") + updateCmd.Flags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol") updateCmd.Flags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") updateCmd.Flags().StringVar(&component, "component", "", "set the component to upgrade") From 4f839a5b61ce9405d77590be8b6f501ddcb76d24 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 5 Oct 2023 13:57:52 -0600 Subject: [PATCH 14/16] Fixed some minor issues --- cmd/update.go | 3 ++- internal/update.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index b2b854a..0df2916 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -30,6 +30,7 @@ var updateCmd = &cobra.Command{ QueryParams: magellan.QueryParams{ Drivers: []string{"redfish"}, Preferred: "redfish", + Protocol: protocol, Host: host, User: user, Pass: pass, @@ -70,7 +71,7 @@ func init() { updateCmd.Flags().IntVar(&port, "port", 443, "set the BMC port") updateCmd.Flags().StringVar(&user, "user", "", "set the BMC user") updateCmd.Flags().StringVar(&pass, "pass", "", "set the BMC password") - updateCmd.Flags().StringVar(&transferProtocol, "protocol", "HTTP", "set the transfer protocol") + updateCmd.Flags().StringVar(&transferProtocol, "transfer-protocol", "HTTP", "set the transfer protocol") updateCmd.Flags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol") updateCmd.Flags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") diff --git a/internal/update.go b/internal/update.go index 7881449..fbfc52e 100644 --- a/internal/update.go +++ b/internal/update.go @@ -126,7 +126,7 @@ func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error } func UpdateFirmwareRemote(q *UpdateParams) error { - url := baseRedfishUrl(&q.QueryParams) + "/UpdateService/Actions/SimpleUpdate" + url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService/Actions/SimpleUpdate" headers := map[string]string { "Content-Type": "application/json", "cache-control": "no-cache", @@ -153,7 +153,7 @@ func UpdateFirmwareRemote(q *UpdateParams) error { } func GetUpdateStatus(q *UpdateParams) error { - url := baseRedfishUrl(&q.QueryParams) + "/UpdateService" + url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService" res, body, err := util.MakeRequest(url, "GET", nil, nil) if err != nil { return fmt.Errorf("something went wrong: %v", err) From 346d886edb90f499fe92db55bc160e915ae7f924 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 10 Oct 2023 11:53:11 -0600 Subject: [PATCH 15/16] Fixed issue `QueryEthernetInterfaces` not returning anything --- internal/collect.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/collect.go b/internal/collect.go index 3c6941c..82ce736 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -465,8 +465,23 @@ func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, err return nil, fmt.Errorf("could not connect to bmc: %v", err) } - interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/Self/EthernetInterfaces/") + systems, err := c.Service.Systems() if err != nil { + return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err) + } + + + var interfaces []*redfish.EthernetInterface + for _, system := range systems { + fmt.Printf("%s\n", system.ID + "/EthernetInterfaces/") + i, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/" + system.ID + "/EthernetInterfaces/") + if err != nil { + continue + } + interfaces = append(interfaces, i...) + } + + if len(interfaces) <= 0 { return nil, fmt.Errorf("could not get ethernet interfaces: %v", err) } From 174e2084fc043f5e1da61cdfe4a48518229b5146 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 10 Oct 2023 11:56:33 -0600 Subject: [PATCH 16/16] Removed extra print in `QueryEthernetInterfaces` --- internal/collect.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/collect.go b/internal/collect.go index d8ba893..28af60f 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -464,10 +464,8 @@ func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, err return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err) } - var interfaces []*redfish.EthernetInterface for _, system := range systems { - fmt.Printf("%s\n", system.ID + "/EthernetInterfaces/") i, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/" + system.ID + "/EthernetInterfaces/") if err != nil { continue