From 54dde2dda60ed8eabdbf9cb04381b2ebe9721d16 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 28 Sep 2023 14:46:21 -0600 Subject: [PATCH] Added ability to update endpoint when conflicting with existing + processor info Signed-off-by: David J. Allen --- cmd/collect.go | 8 ++- internal/api/smd/smd.go | 30 ++++++++-- internal/collect.go | 126 +++++++++++++++++++++++++++++++--------- internal/util/util.go | 3 +- 4 files changed, 130 insertions(+), 37 deletions(-) diff --git a/cmd/collect.go b/cmd/collect.go index b379ce9..e54be33 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -10,6 +10,10 @@ import ( "github.com/spf13/cobra" ) +var ( + forceUpdate bool +) + var collectCmd = &cobra.Command{ Use: "collect", Short: "Query information about BMC", @@ -36,6 +40,7 @@ var collectCmd = &cobra.Command{ Verbose: verbose, WithSecureTLS: withSecureTLS, OutputPath: outputPath, + ForceUpdate: forceUpdate, } magellan.CollectInfo(&probeStates, l, q) @@ -50,10 +55,11 @@ var collectCmd = &cobra.Command{ func init() { collectCmd.PersistentFlags().StringSliceVar(&drivers, "driver", []string{"redfish"}, "set the driver(s) and fallback drivers to use") collectCmd.PersistentFlags().StringVar(&smd.Host, "host", smd.Host, "set the host to the smd API") - collectCmd.PersistentFlags().IntVar(&smd.Port, "port", smd.Port, "set the port to the smd API") + 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().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") collectCmd.PersistentFlags().StringVar(&ipmitoolPath, "ipmitool.path", "/usr/bin/ipmitool", "set the path for ipmitool") collectCmd.PersistentFlags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS") diff --git a/internal/api/smd/smd.go b/internal/api/smd/smd.go index fe8c548..a8369ea 100644 --- a/internal/api/smd/smd.go +++ b/internal/api/smd/smd.go @@ -5,6 +5,7 @@ package smd // https://github.com/alexlovelltroy/hms-smd import ( "fmt" + "net/http" "github.com/bikeshack/magellan/internal/util" // hms "github.com/alexlovelltroy/hms-smd" @@ -47,17 +48,34 @@ func AddRedfishEndpoint(data []byte, headers map[string]string) error { return fmt.Errorf("could not add redfish endpoint: no data found") } - // var ep hms.RedfishEP - // _ = ep // Add redfish endpoint via POST `/hsm/v2/Inventory/RedfishEndpoints` endpoint url := makeEndpointUrl("/Inventory/RedfishEndpoints") - res, body, _ := util.MakeRequest(url, "POST", data, headers) + res, body, err := util.MakeRequest(url, "POST", data, headers) fmt.Printf("smd url: %v\n", url) - fmt.Printf("res: %v %v\n", res.StatusCode, res.Status) + fmt.Printf("res: %v\n", res.Status) fmt.Printf("body: %v\n", string(body)) - return nil + if res != nil { + if res.StatusCode != http.StatusOK { + return fmt.Errorf("could not add redfish endpoint") + } + } + return err } -func UpdateRedfishEndpoint() { +func UpdateRedfishEndpoint(xname string, data []byte, headers map[string]string) error { + if data == nil { + return fmt.Errorf("could not add redfish endpoint: no data found") + } // 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)) + if res != nil { + if res.StatusCode != http.StatusOK { + return fmt.Errorf("could not update redfish endpoint") + } + } + return err } diff --git a/internal/collect.go b/internal/collect.go index 30aa5c4..9ca35b9 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -55,6 +55,7 @@ type QueryParams struct { Verbose bool IpmitoolPath string OutputPath string + ForceUpdate bool } func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { @@ -120,14 +121,6 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e return fmt.Errorf("no probe states found") } - // generate custom xnames for bmcs - node := xnames.Node{ - Cabinet: 1000, - Chassis: 1, - ComputeModule: 7, - NodeBMC: -1, - } - // make the output directory to store files outputPath := path.Clean(q.OutputPath) outputPath, err := util.MakeOutputDirectory(outputPath) @@ -139,6 +132,14 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e done := make(chan struct{}, q.Threads+1) chanProbeState := make(chan BMCProbeResult, q.Threads+1) + // generate custom xnames for bmcs + node := xnames.Node{ + Cabinet: 1000, + Chassis: 1, + ComputeModule: 7, + NodeBMC: -1, + } + // collect bmc information asynchronously var wg sync.WaitGroup wg.Add(q.Threads) @@ -159,6 +160,8 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e continue } + node.NodeBMC += 1 + // data to be sent to smd data := make(map[string]any) data["ID"] = fmt.Sprintf("%v", node.String()[:len(node.String())-2]) @@ -167,6 +170,8 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e data["FQDN"] = ps.Host data["User"] = q.User data["Password"] = q.Pass + data["IPAddr"] = "" + data["MACAddr"] = "" data["RediscoverOnUpdate"] = false // unmarshal json to send in correct format @@ -198,6 +203,17 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e json.Unmarshal(interfaces, &rm) data["Interface"] = rm["Interface"] + // get MAC address of first interface (for now...) + if len(rm["Interface"]) > 0 { + var i map[string]interface{} + json.Unmarshal(rm["Interface"], &i) + data["MACAddr"] = i["MACAddress"] + data["IPAddr"] = i["IPAddress"] + if i["FQDN"] != "" { + data["FQDN"] = rm["FQDN"] + } + } + // storage // storage, err := QueryStorage(q) // if err != nil { @@ -207,6 +223,15 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e // json.Unmarshal(storage, &rm) // data["Storage"] = rm["Storage"] + // get specific processor info + procs, err := QueryProcessors(q) + if err != nil { + l.Log.Errorf("could not query processors: %v", err) + } + var p map[string]interface{} + json.Unmarshal(procs, &p) + data["Processors"] = rm["Processors"] + // systems systems, err := QuerySystems(q) if err != nil { @@ -215,6 +240,15 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e json.Unmarshal(systems, &rm) data["Systems"] = rm["Systems"] + // add other fields from systems + if len(rm["Systems"]) > 0 { + var s map[string][]interface{} + json.Unmarshal(rm["Systems"], &s) + data["Name"] = s["Name"] + } + + // data["Type"] = rm[""] + // registries // registries, err := QueryRegisteries(q) // if err != nil { @@ -223,8 +257,6 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e // json.Unmarshal(registries, &rm) // data["Registries"] = rm["Registries"] - node.NodeBMC += 1 - headers := make(map[string]string) headers["Content-Type"] = "application/json" @@ -242,29 +274,17 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e // add all endpoints to smd err = smd.AddRedfishEndpoint(b, headers) if err != nil { - l.Log.Errorf("could not add redfish endpoint: %v", err) + l.Log.Error(err) // try updating instead + if q.ForceUpdate { + err = smd.UpdateRedfishEndpoint(data["ID"].(string), b, headers) + if err != nil { + l.Log.Error(err) + } + } } - // users - // user, err := magellan.QueryUsers(client, l, &q) - // if err != nil { - // l.Log.Errorf("could not query users: %v\n", err) - // } - // users = append(users, user) - - // bios - // _, err = magellan.QueryBios(client, l, &q) - // if err != nil { - // l.Log.Errorf("could not query bios: %v\n", err) - // } - - // _, err = magellan.QueryPowerState(client, l, &q) - // if err != nil { - // l.Log.Errorf("could not query power state: %v\n", err) - // } - // got host information, so add to list of already probed hosts found = append(found, ps.Host) } @@ -567,6 +587,54 @@ func QueryRegisteries(q *QueryParams) ([]byte, error) { return b, nil } +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" + res, body, err := util.MakeRequest(url, "GET", nil, nil) + if err != nil { + return nil, fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return nil, fmt.Errorf("no response returned (url: %s)", url) + } else if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("returned status code %d", res.StatusCode) + } + + // convert to not get base64 string + var procs map[string]json.RawMessage + var members []map[string]any + json.Unmarshal(body, &procs) + json.Unmarshal(procs["Members"], &members) + + // request data about each processor member on node + for _, member := range members { + var oid = member["@odata.id"].(string) + var infoUrl = baseUrl + oid + res, _, err := util.MakeRequest(infoUrl, "GET", nil, nil) + if err != nil { + return nil, fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return nil, fmt.Errorf("no response returned (url: %s)", url) + } else if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("returned status code %d", res.StatusCode) + } + } + + data := map[string]any{"Processors": procs } + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + return nil, fmt.Errorf("could not marshal JSON: %v", err) + } + + if q.Verbose { + fmt.Printf("%v\n", string(b)) + } + return b, nil +} + func connectGofish(q *QueryParams) (*gofish.APIClient, error) { config := makeGofishConfig(q) c, err := gofish.Connect(config) diff --git a/internal/util/util.go b/internal/util/util.go index 8164e68..661d2ba 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -2,6 +2,7 @@ package util import ( "bytes" + "crypto/tls" "fmt" "io" "net/http" @@ -17,7 +18,7 @@ func PathExists(path string) (bool, error) { } func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) { - // url := getSmdEndpointUrl(endpoint) + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} req, _ := http.NewRequest(httpMethod, url, bytes.NewBuffer(body)) req.Header.Add("User-Agent", "magellan") for k, v := range headers {