diff --git a/CHANGELOG.md b/CHANGELOG.md index caf2b77..bcf4f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ log is based on the [Keep a CHANGELOG](http://keepachangelog.com/) project. - Firmware gathering endpoint update and add device info to other HP models [#55](https://github.com/Comcast/fishymetrics/issues/55) - C220 drive metrics on hosts with fw < 4.0, psu metrics result and label values [#57](https://github.com/Comcast/fishymetrics/issues/57) - Chassis ComputerSystems field is handled improperly [#68](https://github.com/Comcast/fishymetrics/issues/68) +- Power and Thermal metrics collection for Dell R7xxXD server models [#77](https://github.com/Comcast/fishymetrics/issues/77) +- Firmware metrics and request headers update for Dell iDRAC9 with FW ver.3.xx and 4.xx [#77](https://github.com/Comcast/fishymetrics/issues/77) ## Updated diff --git a/common/util.go b/common/util.go index 098d53b..d484522 100644 --- a/common/util.go +++ b/common/util.go @@ -114,6 +114,8 @@ func BuildRequest(uri, host string) *retryablehttp.Request { req, _ := retryablehttp.NewRequest(http.MethodGet, uri, nil) req.SetBasicAuth(user, password) + // this header is required by iDRAC9 with FW ver. 3.xx and 4.xx + req.Header.Add("Accept", "application/json") return req } diff --git a/exporter/exporter.go b/exporter/exporter.go index c72a90d..992c943 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -301,7 +301,7 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud return nil, err } exp.biosVersion = sysResp.BiosVersion - exp.ChassisSerialNumber = sysResp.SerialNumber + exp.ChassisSerialNumber = strings.TrimRight(sysResp.SerialNumber, " ") exp.systemHostname = sysResp.SystemHostname // call /redfish/v1/Systems/XXXXX/ for memory summary and smart storage batteries @@ -364,20 +364,20 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud zap.Any("trace_id", ctx.Value("traceID"))) // Call /redfish/v1/Managers/XXXX/UpdateService/FirmwareInventory/ for firmware inventory - firmwareInventoryEndpoints, err := getMemberUrls(exp.url+uri+"/UpdateService/FirmwareInventory/", target, retryClient) + firmwareInventoryEndpoints, err := getFirmwareEndpoints(exp.url+uri+"/UpdateService/FirmwareInventory/", target, retryClient) if err != nil { // Try the iLo 4 firmware inventory endpoint // Use the collected sysEndpoints.systems to build url(s) if len(sysEndpoints.systems) > 0 { // call /redfish/v1/Systems/XXXX/FirmwareInventory/ for _, system := range sysEndpoints.systems { - firmwareInventoryEndpoints = append(firmwareInventoryEndpoints, system+"FirmwareInventory/") - } - // Ensure we have at least one firmware inventory endpoint - if len(firmwareInventoryEndpoints) == 0 { - log.Error("error when getting FirmwareInventory url", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) - return nil, err + url := system + "FirmwareInventory/" + tasks = append(tasks, + pool.NewTask(common.Fetch(exp.url+url, target, profile, retryClient), exp.url+url, handle(&exp, FIRMWAREINVENTORY))) } + } else { + log.Error("error when getting Firmware endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err } } @@ -426,12 +426,13 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud } // Firmware Inventory - for _, url := range firmwareInventoryEndpoints { - // this list can potentially be large and cause scrapes to take a long time please + for _, fwEp := range firmwareInventoryEndpoints.Members { + // this list can potentially be large and cause scrapes to take a long time // see the '--collector.firmware.modules-exclude' config in the README for more information if reg, ok := excludes["firmware"]; ok { - if !reg.(*regexp.Regexp).MatchString(url) { - tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+url, target, profile, retryClient), exp.url+url, handle(&exp, FIRMWAREINVENTORY))) + if !reg.(*regexp.Regexp).MatchString(fwEp.URL) { + tasks = append(tasks, + pool.NewTask(common.Fetch(exp.url+fwEp.URL, target, profile, retryClient), exp.url+fwEp.URL, handle(&exp, FIRMWAREINVENTORY))) } } } diff --git a/exporter/helpers.go b/exporter/helpers.go index 7fb8146..9c84ff1 100644 --- a/exporter/helpers.go +++ b/exporter/helpers.go @@ -111,31 +111,50 @@ func getSystemEndpoints(chassisUrls []string, host string, client *retryablehttp } } - for _, power := range chas.Links.Power.LinksURLSlice { - url := appendSlash(power) + if chas.PowerAlt.URL != "" { + url := appendSlash(chas.PowerAlt.URL) if checkUnique(sysEnd.power, url) { sysEnd.power = append(sysEnd.power, url) } } - for _, power := range chas.LinksLower.Power.LinksURLSlice { - url := appendSlash(power) - if checkUnique(sysEnd.power, url) { - sysEnd.power = append(sysEnd.power, url) + if chas.ThermalAlt.URL != "" { + url := appendSlash(chas.ThermalAlt.URL) + if checkUnique(sysEnd.thermal, url) { + sysEnd.thermal = append(sysEnd.thermal, url) } } - for _, thermal := range chas.Links.Thermal.LinksURLSlice { - url := appendSlash(thermal) - if checkUnique(sysEnd.thermal, url) { - sysEnd.thermal = append(sysEnd.thermal, url) + // if power and thermal endpoints are not found in main level, check the nested results in Links/links + if len(sysEnd.power) == 0 { + for _, power := range chas.Links.Power.LinksURLSlice { + url := appendSlash(power) + if checkUnique(sysEnd.power, url) { + sysEnd.power = append(sysEnd.power, url) + } + } + + for _, power := range chas.LinksLower.Power.LinksURLSlice { + url := appendSlash(power) + if checkUnique(sysEnd.power, url) { + sysEnd.power = append(sysEnd.power, url) + } } } - for _, thermal := range chas.LinksLower.Thermal.LinksURLSlice { - url := appendSlash(thermal) - if checkUnique(sysEnd.thermal, url) { - sysEnd.thermal = append(sysEnd.thermal, url) + if len(sysEnd.thermal) == 0 { + for _, thermal := range chas.Links.Thermal.LinksURLSlice { + url := appendSlash(thermal) + if checkUnique(sysEnd.thermal, url) { + sysEnd.thermal = append(sysEnd.thermal, url) + } + } + + for _, thermal := range chas.LinksLower.Thermal.LinksURLSlice { + url := appendSlash(thermal) + if checkUnique(sysEnd.thermal, url) { + sysEnd.thermal = append(sysEnd.thermal, url) + } } } @@ -188,15 +207,6 @@ func getSystemEndpoints(chassisUrls []string, host string, client *retryablehttp } } - // check last resort places for power and thermal endpoints if none were found - if len(sysEnd.power) == 0 && chas.PowerAlt.URL != "" { - sysEnd.power = append(sysEnd.power, appendSlash(chas.PowerAlt.URL)) - } - - if len(sysEnd.thermal) == 0 && chas.ThermalAlt.URL != "" { - sysEnd.thermal = append(sysEnd.thermal, appendSlash(chas.ThermalAlt.URL)) - } - return sysEnd, nil } @@ -431,6 +441,48 @@ func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, cl return driveEndpoints, nil } +func getFirmwareEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var fwEpUrls oem.Collection + var resp *http.Response + var err error + retryCount := 0 + req := common.BuildRequest(url, host) + + resp, err = common.DoRequest(client, req) + if err != nil { + return fwEpUrls, err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusNotFound { + for retryCount < 1 && resp.StatusCode == http.StatusNotFound { + time.Sleep(client.RetryWaitMin) + resp, err = common.DoRequest(client, req) + retryCount = retryCount + 1 + } + if err != nil { + return fwEpUrls, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return fwEpUrls, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return fwEpUrls, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fwEpUrls, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &fwEpUrls) + if err != nil { + return fwEpUrls, fmt.Errorf("Error Unmarshalling Memory Collection struct - " + err.Error()) + } + + return fwEpUrls, nil +} + func getProcessorEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { var processors oem.Collection var resp *http.Response