From 89d1970262e6bd37d6fc85d407e13b051a5abb9c Mon Sep 17 00:00:00 2001 From: ibrahimkk-moideen Date: Wed, 14 Aug 2024 13:56:26 -0400 Subject: [PATCH 1/4] ilo5 ver3.x include stor_controller logical_drive and physical disk metrics --- exporter/exporter.go | 16 ++++++++++------ exporter/handlers.go | 16 +++++++++------- exporter/helpers.go | 18 +++++++++++++++--- exporter/metrics.go | 2 +- oem/drive.go | 33 +++++++++++++++++++-------------- oem/storage_ctrl.go | 36 +++++++++++++++++++++++++++--------- 6 files changed, 81 insertions(+), 40 deletions(-) diff --git a/exporter/exporter.go b/exporter/exporter.go index 6c3eb9a..bef5fee 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -255,8 +255,12 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud } if controllerOutput.Volumes.URL != "" { url := appendSlash(controllerOutput.Volumes.URL) - if checkUnique(sysEndpoints.volumes, url) { - sysEndpoints.volumes = append(sysEndpoints.volumes, url) + if reg, ok := excludes["drive"]; ok { + if !reg.(*regexp.Regexp).MatchString(url) { + if checkUnique(sysEndpoints.volumes, url) { + sysEndpoints.volumes = append(sysEndpoints.volumes, url) + } + } } } } @@ -343,15 +347,15 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud // skip if SmartStorage URL is not present var driveEndpointsResp DriveEndpoints if ss != "" { - driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+ss, target, retryClient) + driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+ss, target, retryClient, excludes) if err != nil { log.Error("error when getting drive endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) return nil, err } } - if len(sysEndpoints.storageController) == 0 && ss == "" { - driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+sysEndpoints.systems[0]+"Storage/", target, retryClient) + if (len(sysEndpoints.storageController) == 0 && ss == "") || (len(sysEndpoints.drives) == 0 && len(driveEndpointsResp.physicalDriveURLs) == 0) { + driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+sysEndpoints.systems[0]+"Storage/", target, retryClient, excludes) if err != nil { log.Error("error when getting drive endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) return nil, err @@ -391,7 +395,7 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud } for _, url := range driveEndpointsResp.physicalDriveURLs { - tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+url, target, profile, retryClient), exp.url+url, handle(&exp, DISKDRIVE))) + tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+url, target, profile, retryClient), exp.url+url, handle(&exp, UNKNOWN_DRIVE))) } // drives from this list could either be NVMe or physical SAS/SATA diff --git a/exporter/handlers.go b/exporter/handlers.go index 261d7c4..69ba65c 100644 --- a/exporter/handlers.go +++ b/exporter/handlers.go @@ -276,7 +276,9 @@ func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { return fmt.Errorf("Error Unmarshalling DiskDriveMetrics - " + err.Error()) } // Check physical drive is enabled then check status and convert string to numeric values - if dlphysical.Status.State == "Enabled" { + if dlphysical.Status.State == "Absent" { + return nil + } else if dlphysical.Status.State == "Enabled" { if dlphysical.Status.Health == "OK" { state = OK } else { @@ -356,8 +358,8 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { } // Check nvme drive is enabled then check status and convert string to numeric values - if dlnvme.Oem.Hpe.DriveStatus.State == "Enabled" { - if dlnvme.Oem.Hpe.DriveStatus.Health == "OK" { + if dlnvme.Status.State == "Enabled" || dlnvme.Oem.Hpe.DriveStatus.State == "Enabled" { + if dlnvme.Status.Health == "OK" || dlnvme.Oem.Hpe.DriveStatus.Health == "OK" { state = OK } else { state = BAD @@ -384,7 +386,7 @@ func (e *Exporter) exportUnknownDriveMetrics(body []byte) error { if err != nil { return fmt.Errorf("Error Unmarshalling NVMeDriveMetrics - " + err.Error()) } - } else if protocol.Protocol != "" { + } else { err = e.exportPhysicalDriveMetrics(body) if err != nil { return fmt.Errorf("Error Unmarshalling DiskDriveMetrics - " + err.Error()) @@ -414,18 +416,18 @@ func (e *Exporter) exportStorageControllerMetrics(body []byte) error { } else { state = BAD } - (*drv)["storageControllerStatus"].WithLabelValues(scm.Name, e.ChassisSerialNumber, e.Model, sc.FirmwareVersion, sc.Model).Set(state) + (*drv)["storageControllerStatus"].WithLabelValues(scm.Name, e.ChassisSerialNumber, e.Model, sc.FirmwareVersion, sc.Model, sc.Location.Location).Set(state) } } if len(scm.StorageController.StorageController) == 0 { - if scm.Status.State == "Enabled" { + if scm.Status.State == "Enabled" && scm.Status.Health != "" { if scm.Status.Health == "OK" { state = OK } else { state = BAD } - (*drv)["storageControllerStatus"].WithLabelValues(scm.Name, e.ChassisSerialNumber, e.Model, scm.ControllerFirmware.FirmwareVersion, scm.Model).Set(state) + (*drv)["storageControllerStatus"].WithLabelValues(scm.Name, e.ChassisSerialNumber, e.Model, scm.ControllerFirmware.FirmwareVersion, scm.Model, scm.Location.Location).Set(state) } } diff --git a/exporter/helpers.go b/exporter/helpers.go index 9c84ff1..a18eaf0 100644 --- a/exporter/helpers.go +++ b/exporter/helpers.go @@ -324,7 +324,7 @@ func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.Gener return drive, nil } -func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, client *retryablehttp.Client) (DriveEndpoints, error) { +func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, client *retryablehttp.Client, excludes Excludes) (DriveEndpoints, error) { var driveEndpoints DriveEndpoints // Get initial JSON return of /redfish/v1/Systems/XXXX/SmartStorage/ArrayControllers/ set to output @@ -348,7 +348,13 @@ func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, cl // /redfish/v1/Systems/XXXX/Storage/XXXXX/ if len(arrayCtrlResp.StorageDrives) > 0 { for _, member := range arrayCtrlResp.StorageDrives { - driveEndpoints.physicalDriveURLs = append(driveEndpoints.physicalDriveURLs, appendSlash(member.URL)) + if reg, ok := excludes["drive"]; ok { + if !reg.(*regexp.Regexp).MatchString(member.URL) { + if checkUnique(driveEndpoints.physicalDriveURLs, member.URL) { + driveEndpoints.physicalDriveURLs = append(driveEndpoints.physicalDriveURLs, appendSlash(member.URL)) + } + } + } } // If Volumes are present, parse volumes endpoint until all urls are found @@ -360,7 +366,13 @@ func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, cl } for _, member := range volumeOutput.Members { - driveEndpoints.logicalDriveURLs = append(driveEndpoints.logicalDriveURLs, appendSlash(member.URL)) + if reg, ok := excludes["drive"]; ok { + if !reg.(*regexp.Regexp).MatchString(member.URL) { + if checkUnique(driveEndpoints.logicalDriveURLs, member.URL) { + driveEndpoints.logicalDriveURLs = append(driveEndpoints.logicalDriveURLs, appendSlash(member.URL)) + } + } + } } } diff --git a/exporter/metrics.go b/exporter/metrics.go index 0c8b850..ba2c201 100644 --- a/exporter/metrics.go +++ b/exporter/metrics.go @@ -84,7 +84,7 @@ func NewDeviceMetrics() *map[string]*metrics { } StorageControllerMetrics = &metrics{ - "storageControllerStatus": newServerMetric("redfish_storage_controller_status", "Current storage controller status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "chassisModel", "firmwareVersion", "model"}), + "storageControllerStatus": newServerMetric("redfish_storage_controller_status", "Current storage controller status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "chassisModel", "firmwareVersion", "model", "location"}), } MemoryMetrics = &metrics{ diff --git a/oem/drive.go b/oem/drive.go index 88814bc..8cf99ed 100644 --- a/oem/drive.go +++ b/oem/drive.go @@ -44,20 +44,25 @@ type NVMeDriveMetrics struct { // Logical Drives // /redfish/v1/Systems/X/SmartStorage/ArrayControllers/X/LogicalDrives/X/ type LogicalDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - DisplayName string `json:"DisplayName"` - InterfaceType string `json:"InterfaceType"` - Identifiers []Identifiers `json:"Identifiers"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - RaidType string `json:"RAIDType"` - Status Status `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + DisplayName string `json:"DisplayName"` + InterfaceType string `json:"InterfaceType"` + Identifiers []Identifiers `json:"Identifiers"` + Links DriveCollection `json:"Links"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + RaidType string `json:"RAIDType"` + Status Status `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` +} + +type DriveCollection struct { + DrivesCount int `json:"Drives@odata.count"` } type Identifiers struct { diff --git a/oem/storage_ctrl.go b/oem/storage_ctrl.go index 1ee908c..39db249 100644 --- a/oem/storage_ctrl.go +++ b/oem/storage_ctrl.go @@ -23,22 +23,24 @@ import ( // /redfish/v1/Systems/WZPXXXXX/Storage/MRAID type StorageControllerMetrics struct { + ControllerFirmware FirmwareVersionWrapper `json:"FirmwareVersion"` + Drives []Drive `json:"Drives"` + Location StorCtrlLocationWrapper `json:"Location"` + Model string `json:"Model"` Name string `json:"Name"` StorageController StorageControllerWrapper `json:"StorageControllers"` - Drives []Drive `json:"Drives"` Status Status `json:"Status"` - Model string `json:"Model"` - ControllerFirmware FirmwareVersionWrapper `json:"FirmwareVersion"` } // StorageController contains status metadata of the C220 chassis storage controller type StorageController struct { - Status Status `json:"Status"` - MemberId string `json:"MemberId"` - Manufacturer string `json:"Manufacturer,omitempty"` - Model string `json:"Model"` - Name string `json:"Name"` - FirmwareVersion string `json:"FirmwareVersion"` + Status Status `json:"Status"` + MemberId string `json:"MemberId"` + Manufacturer string `json:"Manufacturer,omitempty"` + Model string `json:"Model"` + Name string `json:"Name"` + FirmwareVersion string `json:"FirmwareVersion"` + Location StorCtrlLocationWrapper `json:"Location,omitempty"` } type StorageControllerSlice struct { @@ -108,3 +110,19 @@ type ExtendedInfo struct { MessageArg []string `json:"MessageArgs"` Severity string `json:"Severity"` } + +type StorCtrlLocationWrapper struct { + Location string +} + +func (w *StorCtrlLocationWrapper) UnmarshalJSON(data []byte) error { + var location PhysicalLocation + err := json.Unmarshal(data, &location) + if err == nil { + w.Location = location.PartLocation.ServiceLabel + } else { + return json.Unmarshal(data, &w.Location) + } + + return nil +} From 9eeba55d33534c71fa74ff2954d2f1ae90655df2 Mon Sep 17 00:00:00 2001 From: ibrahimkk-moideen Date: Fri, 23 Aug 2024 13:00:24 -0400 Subject: [PATCH 2/4] merge changes from supermciro branch --- CHANGELOG.md | 1 + exporter/exporter.go | 118 +++++++++++++++++++++++-------------------- exporter/handlers.go | 14 +++-- exporter/helpers.go | 112 +++++++++++++++++++++------------------- oem/chassis.go | 86 +++++++++++++++++++------------ oem/drive.go | 16 +++--- oem/system.go | 25 ++++++--- 7 files changed, 213 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21191a4..e53e967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ log is based on the [Keep a CHANGELOG](http://keepachangelog.com/) project. - Ignore CPU metrics if Processor is Absent [#79](https://github.com/Comcast/fishymetrics/issues/79) - Added support for metrics collection from Dell servers [#77](https://github.com/Comcast/fishymetrics/issues/77) - Added support for firmware metrics collection from all supported servers and iLO versions from a single universal exporter [#83](https://github.com/Comcast/fishymetrics/issues/83) +- Added support for Supermicro models metrics collection [#87](https://github.com/Comcast/fishymetrics/issues/87) ## Fixed diff --git a/exporter/exporter.go b/exporter/exporter.go index bef5fee..b65679f 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -253,18 +253,21 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud log.Error("error when getting storage controller metadata", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) return nil, err } - if controllerOutput.Volumes.URL != "" { - url := appendSlash(controllerOutput.Volumes.URL) - if reg, ok := excludes["drive"]; ok { - if !reg.(*regexp.Regexp).MatchString(url) { - if checkUnique(sysEndpoints.volumes, url) { - sysEndpoints.volumes = append(sysEndpoints.volumes, url) + if len(controllerOutput.Volumes.LinksURLSlice) > 0 { + for _, volume := range controllerOutput.Volumes.LinksURLSlice { + url := appendSlash(volume) + if reg, ok := excludes["drive"]; ok { + if !reg.(*regexp.Regexp).MatchString(url) { + if checkUnique(sysEndpoints.volumes, url) { + sysEndpoints.volumes = append(sysEndpoints.volumes, url) + } } } } } } } + if len(sysEndpoints.volumes) > 0 { for _, volume := range sysEndpoints.volumes { virtualDrives, err := getMemberUrls(exp.url+volume, target, retryClient) @@ -316,10 +319,13 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud handle(&exp, MEMORY_SUMMARY, STORAGEBATTERY))) // DIMM endpoints array - dimms, err = getDIMMEndpoints(exp.url+sysEndpoints.systems[0]+"Memory/", target, retryClient) - if err != nil { - log.Error("error when getting DIMM endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) - return nil, err + var systemMemoryEndpoint = GetMemoryURL(sysResp) + if systemMemoryEndpoint != "" { + dimms, err = getDIMMEndpoints(exp.url+systemMemoryEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } } // CPU processor metrics @@ -333,18 +339,8 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud } // check for SmartStorage endpoint from either Hp or Hpe - var ss string - if sysResp.Oem.Hpe.Links.SmartStorage.URL != "" { - ss = appendSlash(sysResp.Oem.Hpe.Links.SmartStorage.URL) + "ArrayControllers/" - } else if sysResp.Oem.Hp.Links.SmartStorage.URL != "" { - ss = appendSlash(sysResp.Oem.Hp.Links.SmartStorage.URL) + "ArrayControllers/" - } else if sysResp.Oem.Hpe.LinksLower.SmartStorage.URL != "" { - ss = appendSlash(sysResp.Oem.Hpe.LinksLower.SmartStorage.URL) + "ArrayControllers/" - } else if sysResp.Oem.Hp.LinksLower.SmartStorage.URL != "" { - ss = appendSlash(sysResp.Oem.Hp.LinksLower.SmartStorage.URL) + "ArrayControllers/" - } - // skip if SmartStorage URL is not present + var ss = GetSmartStorageURL(sysResp) var driveEndpointsResp DriveEndpoints if ss != "" { driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+ss, target, retryClient, excludes) @@ -355,10 +351,13 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud } if (len(sysEndpoints.storageController) == 0 && ss == "") || (len(sysEndpoints.drives) == 0 && len(driveEndpointsResp.physicalDriveURLs) == 0) { - driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+sysEndpoints.systems[0]+"Storage/", target, retryClient, excludes) - if err != nil { - log.Error("error when getting drive endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) - return nil, err + if sysResp.Storage.URL != "" { + url := appendSlash(sysResp.Storage.URL) + driveEndpointsResp, err = getAllDriveEndpoints(ctx, exp.url, exp.url+url, target, retryClient, excludes) + if err != nil { + log.Error("error when getting drive endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } } } @@ -367,22 +366,48 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud zap.Strings("physical_drive_endpoints", driveEndpointsResp.physicalDriveURLs), zap.Any("trace_id", ctx.Value("traceID"))) - // Call /redfish/v1/Managers/XXXX/UpdateService/FirmwareInventory/ for firmware inventory - 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 { - 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"))) + //Firmware Inventory - Try the iLo 4 firmware inventory endpoints using sysEndpoints.systems URL + // call /redfish/v1/Systems/XXXX/FirmwareInventory/ + var systemFML = GetFirmwareInventoryURL(sysResp) + var firmwareInventoryEndpoints []string + if systemFML != "" { + tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+systemFML, target, profile, retryClient), exp.url+systemFML, handle(&exp, FIRMWAREINVENTORY))) + } else { + // Check for /redfish/v1/Managers/XXXX/UpdateService/ for firmware inventory URL + rootComponents, err := getSystemsMetadata(exp.url+uri, target, retryClient) + if err != nil { + log.Error("error when getting root components metadata", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) return nil, err } + if rootComponents.UpdateService.URL != "" { + updateServiceEndpoints, err := getSystemsMetadata(exp.url+rootComponents.UpdateService.URL, target, retryClient) + if err != nil { + log.Error("error when getting update service metadata", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + if len(updateServiceEndpoints.FirmwareInventory.LinksURLSlice) == 1 { + firmwareInventoryEndpoints, err = getMemberUrls(exp.url+updateServiceEndpoints.FirmwareInventory.LinksURLSlice[0], target, retryClient) + if err != nil { + log.Error("error when getting firmware inventory endpoints", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + } else if len(updateServiceEndpoints.FirmwareInventory.LinksURLSlice) > 1 { + firmwareInventoryEndpoints = updateServiceEndpoints.FirmwareInventory.LinksURLSlice + } + + if len(firmwareInventoryEndpoints) < 75 { + for _, fwEp := range firmwareInventoryEndpoints { + // 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(fwEp) { + tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+fwEp, target, profile, retryClient), exp.url+fwEp, handle(&exp, FIRMWAREINVENTORY))) + } + } + } + } + } } // Loop through arrayControllerURLs, logicalDriveURLs, physicalDriveURLs, and nvmeDriveURLs and append each URL to the tasks pool @@ -429,21 +454,6 @@ func NewExporter(ctx context.Context, target, uri, profile, model string, exclud pool.NewTask(common.Fetch(exp.url+dimm.URL, target, profile, retryClient), exp.url+dimm.URL, handle(&exp, MEMORY))) } - // Firmware Inventory - // To avoid scraping a large number of firmware endpoints, we will only scrape if there are less than 75 members - if len(firmwareInventoryEndpoints.Members) < 75 { - 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(fwEp.URL) { - tasks = append(tasks, - pool.NewTask(common.Fetch(exp.url+fwEp.URL, target, profile, retryClient), exp.url+fwEp.URL, handle(&exp, FIRMWAREINVENTORY))) - } - } - } - } - // call /redfish/v1/Managers/XXX/ for firmware version and ilo self test metrics tasks = append(tasks, pool.NewTask(common.Fetch(exp.url+mgrEndpointFinal, target, profile, retryClient), diff --git a/exporter/handlers.go b/exporter/handlers.go index 69ba65c..3ebefca 100644 --- a/exporter/handlers.go +++ b/exporter/handlers.go @@ -161,10 +161,14 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { watts = ps.LastPowerOutputWatts.(float64) case string: watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + default: + watts = 9999 } - (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.ChassisSerialNumber, e.Model, strings.TrimRight(ps.Manufacturer, " "), ps.SerialNumber, ps.FirmwareVersion, ps.PowerSupplyType, strconv.Itoa(bay), ps.Model).Set(watts) - if ps.Status.Health == "OK" { + if watts != 9999 { + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.ChassisSerialNumber, e.Model, strings.TrimRight(ps.Manufacturer, " "), ps.SerialNumber, ps.FirmwareVersion, ps.PowerSupplyType, strconv.Itoa(bay), ps.Model).Set(watts) + } + if ps.Status.Health == "OK" || ps.Status.Health == "Ok" { state = OK } else if ps.Status.Health == "" { state = OK @@ -447,10 +451,10 @@ func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { } // Check memory status and convert string to numeric values // Ignore memory summary if status is not present - if dlm.MemorySummary.Status.HealthRollup == "" { - return nil - } else if dlm.MemorySummary.Status.HealthRollup == "OK" { + if dlm.MemorySummary.Status.HealthRollup == "OK" || dlm.MemorySummary.Status.Health == "OK" { state = OK + } else if dlm.MemorySummary.Status.HealthRollup == "" { + return nil } else { state = BAD } diff --git a/exporter/helpers.go b/exporter/helpers.go index a18eaf0..e626702 100644 --- a/exporter/helpers.go +++ b/exporter/helpers.go @@ -358,18 +358,21 @@ func getAllDriveEndpoints(ctx context.Context, fqdn, initialUrl, host string, cl } // If Volumes are present, parse volumes endpoint until all urls are found - if arrayCtrlResp.Volumes.URL != "" { - volumeOutput, err := getDriveEndpoint(fqdn+arrayCtrlResp.Volumes.URL, host, client) - if err != nil { - log.Error("api call "+fqdn+arrayCtrlResp.Volumes.URL+" failed", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) - return driveEndpoints, err - } + if len(arrayCtrlResp.Volumes.LinksURLSlice) > 0 { + for _, volume := range arrayCtrlResp.Volumes.LinksURLSlice { + url := appendSlash(volume) + volumeOutput, err := getDriveEndpoint(fqdn+url, host, client) + if err != nil { + log.Error("api call "+fqdn+url+" failed", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return driveEndpoints, err + } - for _, member := range volumeOutput.Members { - if reg, ok := excludes["drive"]; ok { - if !reg.(*regexp.Regexp).MatchString(member.URL) { - if checkUnique(driveEndpoints.logicalDriveURLs, member.URL) { - driveEndpoints.logicalDriveURLs = append(driveEndpoints.logicalDriveURLs, appendSlash(member.URL)) + for _, member := range volumeOutput.Members { + if reg, ok := excludes["drive"]; ok { + if !reg.(*regexp.Regexp).MatchString(member.URL) { + if checkUnique(driveEndpoints.logicalDriveURLs, member.URL) { + driveEndpoints.logicalDriveURLs = append(driveEndpoints.logicalDriveURLs, appendSlash(member.URL)) + } } } } @@ -453,48 +456,6 @@ 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 @@ -555,3 +516,48 @@ func checkUnique(s []string, str string) bool { } return true } + +// GetFirstNonEmptyURL returns the first non-empty URL from the provided list. +func GetFirstNonEmptyURL(urls ...string) string { + for _, url := range urls { + if url != "" { + return url + } + } + return "" +} + +// GetMemoryURL assigns the appropriate URL to the Memory field. +func GetMemoryURL(sysResp oem.System) string { + return GetFirstNonEmptyURL( + sysResp.Memory.URL, + sysResp.Oem.Hpe.Links.Memory.URL, + sysResp.Oem.Hp.Links.Memory.URL, + sysResp.Oem.Hpe.LinksLower.Memory.URL, + sysResp.Oem.Hp.LinksLower.Memory.URL, + ) +} + +// GetSmartStorageURL assigns the appropriate URL to the SmartStorage field. +func GetSmartStorageURL(sysResp oem.System) string { + ss := GetFirstNonEmptyURL( + sysResp.Oem.Hpe.Links.SmartStorage.URL, + sysResp.Oem.Hp.Links.SmartStorage.URL, + sysResp.Oem.Hpe.LinksLower.SmartStorage.URL, + sysResp.Oem.Hp.LinksLower.SmartStorage.URL, + ) + if ss != "" { + ss = appendSlash(ss) + "ArrayControllers/" + } + return ss +} + +// GetFirmwareInventoryURL assigns the appropriate URL to the FirmwareInventory field. +func GetFirmwareInventoryURL(sysResp oem.System) string { + return GetFirstNonEmptyURL( + sysResp.Oem.Hpe.Links.FirmwareInventory.URL, + sysResp.Oem.Hp.Links.FirmwareInventory.URL, + sysResp.Oem.Hpe.LinksLower.FirmwareInventory.URL, + sysResp.Oem.Hp.LinksLower.FirmwareInventory.URL, + ) +} diff --git a/oem/chassis.go b/oem/chassis.go index cb7d54b..38e8400 100644 --- a/oem/chassis.go +++ b/oem/chassis.go @@ -51,56 +51,80 @@ type ChassisLinks struct { Thermal LinksWrapper `json:"CooledBy"` } -type LinksURL struct { +type LinksWrapper struct { LinksURLSlice []string } -type LinksWrapper struct { - LinksURL +type ChassisStorageBattery struct { + Oem OemSys `json:"Oem"` +} + +type HRef struct { + URL string `json:"href"` +} + +type Link struct { + URL string `json:"@odata.id"` } func (w *LinksWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output between firmware versions we need to account for this - // try to unmarshal as a slice of structs + // Because of a change in output between firmware versions, we need to account for this. + // Try to unmarshal as a slice of structs: // [ // { // "@odata.id": "/redfish/v1/Systems/XXXX" // } // ] + if err := w.UnmarshalLinks(data); err == nil { + return nil + } + + // Next, try to unmarshal as a single Link object. + // { + // "@odata.id":"/redfish/v1/Systems/XXXX" + // } + + if err := w.UnmarshalObject(data); err == nil { + return nil + } + + // Fallback to unmarshal as a slice of strings + // [ + // "/redfish/v1/Systems/XXXX" + // ] + return json.Unmarshal(data, &w.LinksURLSlice) +} + +func (w *LinksWrapper) UnmarshalLinks(data []byte) error { var links []struct { URL string `json:"@odata.id,omitempty"` HRef string `json:"href,omitempty"` } - err := json.Unmarshal(data, &links) - if err == nil { - if len(links) > 0 { - for _, l := range links { - if l.URL != "" { - w.LinksURLSlice = append(w.LinksURLSlice, l.URL) - } else { - w.LinksURLSlice = append(w.LinksURLSlice, l.HRef) - } - } - } - } else { - // try to unmarshal as a slice of strings - // [ - // "/redfish/v1/Systems/XXXX" - // ] - return json.Unmarshal(data, &w.LinksURLSlice) + if err := json.Unmarshal(data, &links); err != nil { + return err + } + for _, link := range links { + w.appendLink(link.URL, link.HRef) } - return nil } -type ChassisStorageBattery struct { - Oem OemSys `json:"Oem"` -} - -type HRef struct { - URL string `json:"href"` +func (w *LinksWrapper) UnmarshalObject(data []byte) error { + var link struct { + URL string `json:"@odata.id,omitempty"` + HRef string `json:"href,omitempty"` + } + if err := json.Unmarshal(data, &link); err != nil { + return err + } + w.appendLink(link.URL, link.HRef) + return nil } -type Link struct { - URL string `json:"@odata.id"` +func (w *LinksWrapper) appendLink(url, href string) { + if url != "" { + w.LinksURLSlice = append(w.LinksURLSlice, url) + } else if href != "" { + w.LinksURLSlice = append(w.LinksURLSlice, href) + } } diff --git a/oem/drive.go b/oem/drive.go index 8cf99ed..ec38895 100644 --- a/oem/drive.go +++ b/oem/drive.go @@ -128,14 +128,14 @@ func (w *LocationWrapper) UnmarshalJSON(data []byte) error { // /redfish/v1/Systems/X/SmartStorage/ArrayControllers/ for Logical and Physical Drives // /redfish/v1/Chassis/X/Drives/ for NVMe Drive(s) type GenericDrive struct { - Members []Members `json:"Members,omitempty"` - LinksUpper LinksUpper `json:"Links,omitempty"` - LinksLower LinksLower `json:"links,omitempty"` - MembersCount int `json:"Members@odata.count,omitempty"` - DriveCount int `json:"Drives@odata.count,omitempty"` - StorageDrives []Link `json:"Drives,omitempty"` - Volumes Link `json:"Volumes,omitempty"` - Controllers Link `json:"Controllers,omitempty"` + Members []Members `json:"Members,omitempty"` + LinksUpper LinksUpper `json:"Links,omitempty"` + LinksLower LinksLower `json:"links,omitempty"` + MembersCount int `json:"Members@odata.count,omitempty"` + DriveCount int `json:"Drives@odata.count,omitempty"` + StorageDrives []Link `json:"Drives,omitempty"` + Volumes LinksWrapper `json:"Volumes,omitempty"` + Controllers Link `json:"Controllers,omitempty"` } type Members struct { diff --git a/oem/system.go b/oem/system.go index 44bd5ff..baa4fa7 100644 --- a/oem/system.go +++ b/oem/system.go @@ -21,12 +21,16 @@ package oem // ServerManager contains the BIOS version and Serial number of the chassis, // we will also collect memory summary and storage battery metrics if present type System struct { - BiosVersion string `json:"BiosVersion"` - SerialNumber string `json:"SerialNumber"` - SystemHostname string `json:"HostName"` - Oem OemSys `json:"Oem"` - MemorySummary MemorySummary `json:"MemorySummary"` - Volumes Link `json:"Volumes"` + BiosVersion string `json:"BiosVersion"` + SerialNumber string `json:"SerialNumber"` + SystemHostname string `json:"HostName"` + Oem OemSys `json:"Oem"` + MemorySummary MemorySummary `json:"MemorySummary"` + Memory Link `json:"Memory"` + Volumes LinksWrapper `json:"Volumes"` + FirmwareInventory LinksWrapper `json:"FirmwareInventory"` + Storage Link `json:"Storage"` + UpdateService Link `json:"UpdateService"` } type OemSys struct { @@ -43,11 +47,15 @@ type HpeSys struct { } type SystemLinksUpper struct { - SmartStorage Link `json:"SmartStorage"` + SmartStorage Link `json:"SmartStorage"` + FirmwareInventory Link `json:"FirmwareInventory"` + Memory Link `json:"Memory"` } type SystemLinksLower struct { - SmartStorage HRef `json:"SmartStorage"` + SmartStorage HRef `json:"SmartStorage"` + FirmwareInventory HRef `json:"FirmwareInventory"` + Memory HRef `json:"Memory"` } type SmartStorageBattery struct { @@ -75,4 +83,5 @@ type MemorySummary struct { // StatusMemory is the variable to determine if the memory is OK or not type StatusMemory struct { HealthRollup string `json:"HealthRollup"` + Health string `json:"Health"` } From 1753b34dd7c61aeacaebea9853254d195b91d200 Mon Sep 17 00:00:00 2001 From: ibrahimkk-moideen Date: Mon, 26 Aug 2024 15:05:12 -0400 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e53e967..5af9e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ log is based on the [Keep a CHANGELOG](http://keepachangelog.com/) project. - HP DL380 module to include CPU metrics and all HP models to include bayNumber in PSU metrics [#57](https://github.com/Comcast/fishymetrics/issues/57) - use standard library for http routing instead of gorilla mux package [#47](https://github.com/Comcast/fishymetrics/issues/47) - Avoid collecting firmware metrics if count of endpoints are 75 or greater [#77] (https://github.com/Comcast/fishymetrics/issues/77) +- Support for physical disk and logical drive metrics collection from iLO5 fw ver.3.0.x [#91](https://github.com/Comcast/fishymetrics/issues/91) ## [0.7.1] From 6057b9ca2ea5c798bc7c431eb5a96574e8edc18c Mon Sep 17 00:00:00 2001 From: Ibrahim Khalilullah Khan KHAJA MOIDEEN <133234901+ibrahimkk-moideen@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:31:25 -0400 Subject: [PATCH 4/4] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af9e32..98f9f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ log is based on the [Keep a CHANGELOG](http://keepachangelog.com/) project. - HP DL380 module to include CPU metrics and all HP models to include bayNumber in PSU metrics [#57](https://github.com/Comcast/fishymetrics/issues/57) - use standard library for http routing instead of gorilla mux package [#47](https://github.com/Comcast/fishymetrics/issues/47) - Avoid collecting firmware metrics if count of endpoints are 75 or greater [#77] (https://github.com/Comcast/fishymetrics/issues/77) -- Support for physical disk and logical drive metrics collection from iLO5 fw ver.3.0.x [#91](https://github.com/Comcast/fishymetrics/issues/91) +- Support for physical disk, logical drive and storage controller metrics collection from iLO5 fw ver.3.0.x [#91](https://github.com/Comcast/fishymetrics/issues/91) ## [0.7.1]