From 341bda8437a0f78ba47b4119e687bd502a094bd7 Mon Sep 17 00:00:00 2001 From: Derrick DaCosta <125311989+derrick-dacosta@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:15:54 -0400 Subject: [PATCH 1/4] consolidate oem redfish metric models --- hpe/dl20/exporter.go | 25 +++++-- hpe/dl360/exporter.go | 15 +++- hpe/dl380/exporter.go | 12 +++- hpe/dl560/exporter.go | 5 +- hpe/moonshot/exporter.go | 5 +- hpe/xl420/exporter.go | 5 +- oem/common.go | 23 ++++++ oem/drive.go | 120 ++++++++++++++++++++++++++++++++ oem/memory.go | 49 +++++++++++++ oem/moonshot/power.go | 96 +++++++++++++++++++++++++ oem/moonshot/sw.go | 39 +++++++++++ oem/moonshot/sw_power.go | 54 ++++++++++++++ oem/moonshot/systems_summary.go | 43 ++++++++++++ oem/moonshot/thermal.go | 77 ++++++++++++++++++++ oem/network_adapter.go | 59 ++++++++++++++++ oem/power.go | 78 +++++++++++++++++++++ oem/storage_ctrl.go | 115 ++++++++++++++++++++++++++++++ oem/thermal.go | 51 ++++++++++++++ 18 files changed, 856 insertions(+), 15 deletions(-) create mode 100644 oem/common.go create mode 100644 oem/drive.go create mode 100644 oem/memory.go create mode 100644 oem/moonshot/power.go create mode 100644 oem/moonshot/sw.go create mode 100644 oem/moonshot/sw_power.go create mode 100644 oem/moonshot/systems_summary.go create mode 100644 oem/moonshot/thermal.go create mode 100644 oem/network_adapter.go create mode 100644 oem/power.go create mode 100644 oem/storage_ctrl.go create mode 100644 oem/thermal.go diff --git a/hpe/dl20/exporter.go b/hpe/dl20/exporter.go index 4413ea8..45b3e9e 100644 --- a/hpe/dl20/exporter.go +++ b/hpe/dl20/exporter.go @@ -32,6 +32,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -255,7 +256,7 @@ func (e *Exporter) scrape() { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var dlPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -286,7 +287,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var dlThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { @@ -297,7 +298,14 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(float64(fan.Reading)) + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(fanSpeed) if fan.Status.Health == "OK" { state = OK } else { @@ -311,7 +319,16 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(float64(sensor.ReadingCelsius)) + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { diff --git a/hpe/dl360/exporter.go b/hpe/dl360/exporter.go index 0dcfbbe..332c146 100644 --- a/hpe/dl360/exporter.go +++ b/hpe/dl360/exporter.go @@ -33,6 +33,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -463,7 +464,7 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var dlPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -511,7 +512,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var dlThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { @@ -522,10 +523,18 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + if fan.FanName != "" { (*dlThermal)["fanSpeed"].WithLabelValues(fan.FanName).Set(float64(fan.CurrentReading)) } else { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(float64(fan.Reading)) + (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(fanSpeed) } if fan.Status.Health == "OK" { state = OK diff --git a/hpe/dl380/exporter.go b/hpe/dl380/exporter.go index 6af0013..b6f4ae8 100644 --- a/hpe/dl380/exporter.go +++ b/hpe/dl380/exporter.go @@ -33,6 +33,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -461,7 +462,7 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var dlPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -492,7 +493,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var dlThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { @@ -503,6 +504,13 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(float64(fan.Reading)) if fan.Status.Health == "OK" { state = OK diff --git a/hpe/dl560/exporter.go b/hpe/dl560/exporter.go index 42fb761..9302f4f 100644 --- a/hpe/dl560/exporter.go +++ b/hpe/dl560/exporter.go @@ -33,6 +33,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -378,7 +379,7 @@ func (e *Exporter) scrape() { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var dlPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -409,7 +410,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var dlThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { diff --git a/hpe/moonshot/exporter.go b/hpe/moonshot/exporter.go index b93dd89..66b616f 100644 --- a/hpe/moonshot/exporter.go +++ b/hpe/moonshot/exporter.go @@ -33,6 +33,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem/moonshot" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -342,7 +343,7 @@ func (e *Exporter) scrape() { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm moonshot.PowerMetrics var msPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -368,7 +369,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm moonshot.ThermalMetrics var msThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { diff --git a/hpe/xl420/exporter.go b/hpe/xl420/exporter.go index f650a2b..6ad06d4 100644 --- a/hpe/xl420/exporter.go +++ b/hpe/xl420/exporter.go @@ -33,6 +33,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -386,7 +387,7 @@ func (e *Exporter) scrape() { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var dlPower = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -430,7 +431,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var dlThermal = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { diff --git a/oem/common.go b/oem/common.go new file mode 100644 index 0000000..e0be1bc --- /dev/null +++ b/oem/common.go @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// Status contains metadata for the health of a particular component/module +type Status struct { + Health string `json:"Health"` + State string `json:"State,omitempty"` +} diff --git a/oem/drive.go b/oem/drive.go new file mode 100644 index 0000000..54ee769 --- /dev/null +++ b/oem/drive.go @@ -0,0 +1,120 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// /redfish/v1/Systems/X/SmartStorage/ArrayControllers/ + +// NVME's +// /redfish/v1/chassis/X/ +type NVMeDriveMetrics struct { + ID string `json:"Id"` + Model string `json:"Model"` + Name string `json:"Name"` + MediaType string `json:"MediaType"` + Oem Oem `json:"Oem"` + PhysicalLocation PhysicalLocation `json:"PhysicalLocation"` + Protocol string `json:"Protocol"` + Status Status `json:"Status"` + FailurePredicted bool `json:"FailurePredicted"` + CapacityBytes int `json:"CapacityBytes"` +} + +// 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"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status Status `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` +} + +// Disk Drives +// /redfish/v1/Systems/X/SmartStorage/ArrayControllers/X/DiskDrives/X/ +type DiskDriveMetrics struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status Status `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` +} + +// GenericDrive is used to iterate over differing drive endpoints +// /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"` +} + +type Members struct { + URL string `json:"@odata.id"` +} + +type LinksUpper struct { + Drives []URL `json:"Drives,omitempty"` + LogicalDrives URL `json:"LogicalDrives,omitempty"` + PhysicalDrives URL `json:"PhysicalDrives,omitempty"` +} + +type LinksLower struct { + Drives []HRef `json:"Drives,omitempty"` + LogicalDrives HRef `json:"LogicalDrives,omitempty"` + PhysicalDrives HRef `json:"PhysicalDrives,omitempty"` +} + +type HRef struct { + URL string `json:"href"` +} + +type URL struct { + URL string `json:"@odata.id"` +} + +// PhysicalLocation +type PhysicalLocation struct { + PartLocation PartLocation `json:"PartLocation"` +} + +// PartLocation is a variable that determines the Box and the Bay location of the NVMe drive +type PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` +} + +// Contents of Oem +type Oem struct { + Hpe HpeCont `json:"Hpe"` +} + +// Contents of Hpe +type HpeCont struct { + CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` + DriveStatus Status `json:"DriveStatus"` + NVMeID string `json:"NVMeId"` +} diff --git a/oem/memory.go b/oem/memory.go new file mode 100644 index 0000000..e111aee --- /dev/null +++ b/oem/memory.go @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// /redfish/v1/systems/1/ +// /redfish/v1/Systems/XXXXX/Memory/DIMM_X1 + +// MemoryMetrics is the top level json object for UCS S3260 M4 Memory metadata +// type MemoryMetrics struct { +// Name string `json:"Name"` +// CapacityMiB interface{} `json:"CapacityMiB"` +// Manufacturer string `json:"Manufacturer"` +// MemoryDeviceType string `json:"MemoryDeviceType"` +// PartNumber string `json:"PartNumber"` +// SerialNumber string `json:"SerialNumber"` +// Status interface{} `json:"Status"` +// } + +// MemoryMetrics is the top level json object for Memory metadata +type MemoryMetrics struct { + ID string `json:"Id"` + MemorySummary MemorySummary `json:"MemorySummary"` +} + +// MemorySummary is the json object for MemorySummary metadata +type MemorySummary struct { + Status StatusMemory `json:"Status"` + TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` + TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` +} + +// StatusMemory is the variable to determine if the memory is OK or not +type StatusMemory struct { + HealthRollup string `json:"HealthRollup"` +} diff --git a/oem/moonshot/power.go b/oem/moonshot/power.go new file mode 100644 index 0000000..c0dddc1 --- /dev/null +++ b/oem/moonshot/power.go @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package moonshot + +// /rest/v1/Chassis/1/PowerMetrics + +// PowerMetrics is the top level json object for Power metadata +type PowerMetrics struct { + Name string `json:"Name"` + Oem OemPower `json:"Oem"` + PowerCapacityWatts int `json:"PowerCapacityWatts"` + PowerConsumedWatts int `json:"PowerConsumedWatts"` + PowerSupplies []PowerSupplies `json:"PowerSupplies"` + Type string `json:"Type"` + Links Links `json:"links"` +} + +// OemPower is the top level json object for historical data for wattage +type OemPower struct { + Hp HpPower `json:"Hp"` +} + +// HpPower is the top level json object for the power supplies metadata +type HpPower struct { + PowercapDescription string `json:"PowercapDescription"` + PowercapMode int `json:"PowercapMode"` + InstantWattage int `json:"InstantWattage,omitempty"` + Type string `json:"Type"` + WattageHistoryLevel []WattageHistoryLevel `json:"WattageHistoryLevel"` +} + +// WattageHistoryLevel is the top level json object for all historical Samples metadata +type WattageHistoryLevel struct { + Counter int `json:"Counter"` + Cumulator int `json:"Cumulator"` + SampleType string `json:"SampleType"` + Samples []SamplesPower `json:"Samples"` +} + +// SamplesPower holds the historical data for power wattage +type SamplesPower struct { + Wattage string `json:"Wattage"` +} + +// StatusPower is the variable to determine if a power supply is OK or not +type StatusPower struct { + State string `json:"State"` +} + +// PowerSupplies is the top level json object for metadata on power supply product info +type PowerSupplies struct { + ACInputStatus string `json:"ACInputStatus"` + FirmwareVersion string `json:"FirmwareVersion"` + LastPowerOutputWatts int `json:"LastPowerOutputWatts"` + Model string `json:"Model"` + Name string `json:"Name"` + Oem Oem `json:"Oem"` + PowerCapacityWatts int `json:"PowerCapacityWatts"` + PowerSupplyType string `json:"PowerSupplyType"` + SerialNumber string `json:"SerialNumber"` + SparePartNumber string `json:"SparePartNumber"` + Status StatusPower `json:"Status"` +} + +// Oem namespace layer for Hp json object +type Oem struct { + Hp Hp `json:"Hp"` +} + +// Hp contains metadata on power supply product info +type Hp struct { + BayNumber int `json:"BayNumber"` + HotplugCapable bool `json:"HotplugCapable"` + Type string `json:"Type"` +} + +// Links is a reference to the current REST API URL call +type Links struct { + Self struct { + Href string `json:"href"` + } `json:"self"` +} diff --git a/oem/moonshot/sw.go b/oem/moonshot/sw.go new file mode 100644 index 0000000..f0ffdb5 --- /dev/null +++ b/oem/moonshot/sw.go @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package moonshot + +// /rest/v1/chassis/1/switches/sa + +// Sw is the top level json object for Switch Information metadata +type Sw struct { + Name string `json:"Name"` + Power string `json:"Power,omitempty"` + SerialNumber string `json:"SerialNumber"` + Status SwStatus `json:"Status"` + SwitchInfo SwInfo `json:"SwitchInfo"` + Type string `json:"Type"` +} + +// SwStatus is the top level json object for switch status +type SwStatus struct { + State string `json:"State,omitempty"` +} + +// SwInfo is the top level json object for switch info +type SwInfo struct { + HealthStatus string `json:"HealthStatus,omitempty"` +} diff --git a/oem/moonshot/sw_power.go b/oem/moonshot/sw_power.go new file mode 100644 index 0000000..5064133 --- /dev/null +++ b/oem/moonshot/sw_power.go @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package moonshot + +// /rest/v1/chassis/1/switches/sa/PowerMetrics + +// SwPowerMetrics is the top level json object for Power metadata +type SwPowerMetrics struct { + Name string `json:"Name"` + Oem SwOemPower `json:"Oem"` + PowerSupplies []PowerSupplies `json:"PowerSupplies"` + Type string `json:"Type"` + Links Links `json:"links"` +} + +// SwOemPower is the top level json object for historical data for wattage +type SwOemPower struct { + Hp SwHpPower `json:"Hp"` +} + +// SwHpPower is the top level json object for the power supplies metadata +type SwHpPower struct { + InstantWattage int `json:"InstantWattage"` + MaximumWattage int `json:"MaximumWattage"` + Type string `json:"Type"` + WattageHistoryLevel []SwWattageHistoryLevel `json:"WattageHistoryLevel"` +} + +// SwWattageHistoryLevel is the top level json object for all historical Samples metadata +type SwWattageHistoryLevel struct { + Counter int `json:"Counter"` + Cumulator int `json:"Cumulator"` + SampleType string `json:"SampleType"` + Samples []SwSamplesPower `json:"Samples"` +} + +// SwSamplesPower holds the historical data for power wattage +type SwSamplesPower struct { + Wattage string `json:"Wattage"` +} diff --git a/oem/moonshot/systems_summary.go b/oem/moonshot/systems_summary.go new file mode 100644 index 0000000..eca4286 --- /dev/null +++ b/oem/moonshot/systems_summary.go @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package moonshot + +// /rest/v1/SystemsSummary + +// SystemsSummary is the top level json object for Moonshot summary metadata +type SystemsSummary struct { + Name string `json:"Name"` + Systems []Systems `json:"Systems,omitempty"` + SystemsInChassis int `json:"SystemsInChassis,omitempty"` + Type string `json:"Type"` +} + +// Systems contains metadata on each cartridge that is present in the Moonshot Chassis +type Systems struct { + AssetTag string `json:"AssetTag,omitempty"` + Health string `json:"Health,omitempty"` + HostMACAddress []string `json:"HostMACAddress,omitempty"` + Memory string `json:"Memory,omitempty"` + Model string `json:"Model,omitempty"` + Name string `json:"Name,omitempty"` + Power string `json:"Power,omitempty"` + ProcessorFamily string `json:"ProcessorFamily,omitempty"` + ProcessorManufacturer string `json:"ProcessorManufacturer,omitempty"` + SKU string `json:"SKU,omitempty"` + SerialNumber string `json:"SerialNumber,omitempty"` + UUID string `json:"UUID,omitempty"` +} diff --git a/oem/moonshot/thermal.go b/oem/moonshot/thermal.go new file mode 100644 index 0000000..df00113 --- /dev/null +++ b/oem/moonshot/thermal.go @@ -0,0 +1,77 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package moonshot + +// /rest/v1/Chassis/1/ThermalMetrics + +// HpThermal is hold the metadata for product information +type HpThermal struct { + Location string `json:"Location"` + Type string `json:"Type"` +} + +// OemThermal is the top level json object for Hp product information +type OemThermal struct { + Hp HpThermal `json:"Hp"` +} + +// StatusThermal is the variable to determine if a Fan is OK or not +type StatusThermal struct { + State string `json:"State"` +} + +// SamplesThermal saves historical data on specific intervals +type SamplesThermal struct { + Temperature int `json:"Temperature"` +} + +// Fans gives metadata on each fan +type Fans struct { + CurrentReading int `json:"CurrentReading"` + FanName string `json:"FanName"` + Oem OemThermal `json:"Oem"` + ProductName string `json:"ProductName"` + Status StatusThermal `json:"Status"` + Units string `json:"Units"` +} + +// ThermalMetrics is the top level json object for Fan and Temperatures metadata +type ThermalMetrics struct { + Fans []Fans `json:"Fans"` + Name string `json:"Name"` + Temperatures []Temperatures `json:"Temperatures"` + Type string `json:"Type"` +} + +// Temperatures is the top level json object for temperature metadata +type Temperatures struct { + CurrentReading int `json:"CurrentReading"` + Name string `json:"Name"` + Status StatusThermal `json:"Status"` + TemperatureHistoryLevel []TemperatureHistoryLevel `json:"TemperatureHistoryLevel"` + Units string `json:"Units"` + UpperThresholdCritical int `json:"UpperThresholdCritical,omitempty"` + UpperThresholdNonCritical int `json:"UpperThresholdNonCritical,omitempty"` +} + +// TemperatureHistoryLevel is the top level json object for all historical Samples metadata +type TemperatureHistoryLevel struct { + Counter int `json:"Counter"` + Cumulator int `json:"Cumulator"` + SampleType string `json:"SampleType"` + Samples []SamplesThermal `json:"Samples"` +} diff --git a/oem/network_adapter.go b/oem/network_adapter.go new file mode 100644 index 0000000..9403b4a --- /dev/null +++ b/oem/network_adapter.go @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// /redfish/v1/Systems/X/BaseNetworkAdapters + +// NetworkAdapter is the top level json object for Network Adapter metadata +type NetworkAdapter struct { + ID string `json:"Id"` + Firmware Firmware `json:"Firmware"` + Name string `json:"Name"` + PartNumber string `json:"PartNumber"` + PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` + SerialNumber string `json:"SerialNumber"` + StructuredName string `json:"StructuredName"` + Status Status `json:"Status"` + UEFIDevicePath string `json:"UEFIDevicePath"` +} + +// Firmware is the top level json object for Network Adapter metadata +type Firmware struct { + Current FirmwareCurrent `json:"Current"` +} + +// FirmwareCurrent contains the version in string format +type FirmwareCurrent struct { + Version string `json:"VersionString"` +} + +// PhysicalPorts contains the metadata for the Chassis NICs +type PhysicalPorts struct { + FullDuplex bool `json:"FullDuplex"` + IPv4Addresses []Addr `json:"IPv4Addresses"` + IPv6Addresses []Addr `json:"IPv6Addresses"` + LinkStatus string `json:"LinkStatus"` + MacAddress string `json:"MacAddress"` + Name string `json:"Name"` + SpeedMbps int `json:"SpeedMbps"` + Status Status `json:"Status"` +} + +// Addr contains the IPv4 or IPv6 Address in string format +type Addr struct { + Address string `json:"Address"` +} diff --git a/oem/power.go b/oem/power.go new file mode 100644 index 0000000..dfe1e86 --- /dev/null +++ b/oem/power.go @@ -0,0 +1,78 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// /redfish/v1/Chassis/X/Power/ + +// PowerMetrics is the top level json object for Power metadata +type PowerMetrics struct { + ID string `json:"Id"` + Name string `json:"Name"` + PowerControl []PowerControl `json:"PowerControl"` + PowerSupplies []PowerSupply `json:"PowerSupplies"` +} + +// PowerControl is the top level json object for metadata on power supply consumption +type PowerControl struct { + MemberID string `json:"MemberId"` + PowerCapacityWatts int `json:"PowerCapacityWatts"` + PowerConsumedWatts int `json:"PowerConsumedWatts"` + PowerMetrics PowerMetric `json:"PowerMetrics"` +} + +// PowerMetric contains avg/min/max power metadata +type PowerMetric struct { + AverageConsumedWatts int `json:"AverageConsumedWatts"` + IntervalInMin int `json:"IntervalInMin"` + MaxConsumedWatts int `json:"MaxConsumedWatts"` + MinConsumedWatts int `json:"MinConsumedWatts"` +} + +// PowerSupply is the top level json object for metadata on power supply product info +type PowerSupply struct { + FirmwareVersion string `json:"FirmwareVersion"` + LastPowerOutputWatts int `json:"LastPowerOutputWatts"` + LineInputVoltage int `json:"LineInputVoltage"` + LineInputVoltageType string `json:"LineInputVoltageType"` + Manufacturer string `json:"Manufacturer"` + MemberID string `json:"MemberId"` + Model string `json:"Model"` + Name string `json:"Name"` + Oem OemPower `json:"Oem"` + PowerCapacityWatts int `json:"PowerCapacityWatts"` + PowerSupplyType string `json:"PowerSupplyType"` + SerialNumber string `json:"SerialNumber"` + SparePartNumber string `json:"SparePartNumber"` + Status Status `json:"Status"` +} + +// OemPower is the top level json object for historical data for wattage +type OemPower struct { + Hpe Hpe `json:"Hpe,omitempty"` + Hp Hpe `json:"Hp,omitempty"` +} + +// Hpe contains metadata on power supply product info +type Hpe struct { + AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` + BayNumber int `json:"BayNumber"` + HotplugCapable bool `json:"HotplugCapable"` + MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` + Mismatched bool `json:"Mismatched"` + PowerSupplyStatus Status `json:"PowerSupplyStatus"` + IPDUCapable bool `json:"iPDUCapable"` +} diff --git a/oem/storage_ctrl.go b/oem/storage_ctrl.go new file mode 100644 index 0000000..ce93cae --- /dev/null +++ b/oem/storage_ctrl.go @@ -0,0 +1,115 @@ +/* + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +import ( + "bytes" + "encoding/json" + "encoding/xml" +) + +// /redfish/v1/Systems/WZPXXXXX/Storage/MRAID + +type StorageControllerMetrics struct { + Name string `json:"Name"` + StorageController StorageControllerWrapper `json:"StorageControllers"` + Drives []Drive `json:"Drives"` +} + +// StorageController contains status metadata of the C220 chassis storage controller +type StorageController struct { + Status Status `json:"Status"` + MemberId string `json:"MemberId"` + Model string `json:"Model"` + Name string `json:"Name"` + FirmwareVersion string `json:"FirmwareVersion"` +} + +type StorageControllerSlice struct { + StorageController []StorageController +} + +type StorageControllerWrapper struct { + StorageControllerSlice +} + +func (w *StorageControllerWrapper) UnmarshalJSON(data []byte) error { + // because of a change in output betwen c220 firmware versions we need to account for this + if bytes.Compare([]byte("{"), data[0:1]) == 0 { + var storCtlSlice StorageController + err := json.Unmarshal(data, &storCtlSlice) + if err != nil { + return err + } + s := make([]StorageController, 0) + s = append(s, storCtlSlice) + w.StorageController = s + return nil + } + return json.Unmarshal(data, &w.StorageController) +} + +type Drive struct { + Url string `json:"@odata.id"` +} + +type Error struct { + Error ErrorBody `json:"error"` +} + +type ErrorBody struct { + Code string `json:"code"` + Message string `json:"message"` + ExtendedInfo []ExtendedInfo `json:"@Message.ExtendedInfo"` +} + +type ExtendedInfo struct { + OdataType string `json:"@odata.type"` + MessageID string `json:"MessageId"` + Message string `json:"Message"` + MessageArg []string `json:"MessageArgs"` + Severity string `json:"Severity"` +} + +// /redfish/v1/Systems/WZPXXXXX/Storage/MRAID/Drives/X + +type DriveMetrics struct { + Id string `json:"Id"` + Name string `json:"Name"` + Model string `json:"Model"` + CapacityBytes int `json:"CapacityBytes"` + Status Status `json:"Status"` +} + +// XML class_id="StorageLocalDiskSlotEp" + +type XMLDriveMetrics struct { + XMLName xml.Name `xml:"configResolveClass"` + OutConfigs OutConfigs `xml:"outConfigs"` +} + +type OutConfigs struct { + XMLName xml.Name `xml:"outConfigs"` + Drives []StorageLocalDiskSlotEp `xml:"storageLocalDiskSlotEp"` +} + +type StorageLocalDiskSlotEp struct { + Id string `xml:"id,attr"` + Name string `xml:"dn,attr"` + Operability string `xml:"operability,attr"` + Presence string `xml:"presence,attr"` +} diff --git a/oem/thermal.go b/oem/thermal.go new file mode 100644 index 0000000..f220629 --- /dev/null +++ b/oem/thermal.go @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package oem + +// /redfish/v1/Chassis/X/Thermal/ + +// ThermalMetrics is the top level json object for Thermal metadata +type ThermalMetrics struct { + ID string `json:"Id"` + Fans []Fan `json:"Fans"` + Name string `json:"Name"` + Temperatures []Temperature `json:"Temperatures"` + Url string `json:"@odata.id"` +} + +// Fan is the json object for a fan module +type Fan struct { + MemberID string `json:"MemberId"` + Name string `json:"Name"` + FanName string `json:"FanName"` + Reading interface{} `json:"Reading"` + CurrentReading int `json:"CurrentReading"` + ReadingUnits string `json:"ReadingUnits"` + Status Status `json:"Status"` +} + +// Temperature is the json object for a temperature sensor module +type Temperature struct { + MemberID string `json:"MemberId"` + Name string `json:"Name"` + PhysicalContext string `json:"PhysicalContext"` + ReadingCelsius interface{} `json:"ReadingCelsius"` + SensorNumber int `json:"SensorNumber"` + Status Status `json:"Status"` + UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` + UpperThresholdFatal int `json:"UpperThresholdFatal"` +} From 6b7c044dd57838f277de5bc99585dd4ce714aa47 Mon Sep 17 00:00:00 2001 From: Derrick DaCosta Date: Fri, 29 Mar 2024 22:14:21 -0400 Subject: [PATCH 2/4] remove duplicate struct declarations, rework test cases --- cisco/c220/chassis.go | 82 ---- cisco/c220/drive.go | 115 ------ cisco/c220/drive_test.go | 167 -------- cisco/c220/exporter.go | 82 ++-- cisco/c220/exporter_test.go | 251 ++++++++++++- cisco/c220/memory.go | 30 -- cisco/c220/memory_test.go | 158 -------- cisco/c220/metrics.go | 1 - cisco/c220/power.go | 102 ----- cisco/c220/thermal.go | 45 --- cisco/s3260m4/drive.go | 92 ----- cisco/s3260m4/exporter.go | 109 +++--- cisco/s3260m4/memory.go | 30 -- cisco/s3260m4/metrics.go | 1 - cisco/s3260m4/power.go | 149 -------- cisco/s3260m4/processor.go | 30 -- cisco/s3260m4/thermal.go | 45 --- cisco/s3260m5/chassis.go | 59 --- cisco/s3260m5/drive.go | 61 --- cisco/s3260m5/exporter.go | 211 ++++++++--- cisco/s3260m5/memory.go | 30 -- cisco/s3260m5/metrics.go | 1 - cisco/s3260m5/power.go | 107 ------ cisco/s3260m5/processor.go | 30 -- cisco/s3260m5/thermal.go | 45 --- hpe/dl20/drive.go | 33 -- hpe/dl20/exporter.go | 606 ++++++++++++++++++++++++++++-- hpe/dl20/memory.go | 37 -- hpe/dl20/metrics.go | 53 ++- hpe/dl20/network_adapter.go | 65 ---- hpe/dl20/power.go | 77 ---- hpe/dl20/thermal.go | 48 --- hpe/dl360/drive.go | 124 ------ hpe/dl360/drive_test.go | 562 --------------------------- hpe/dl360/exporter.go | 497 ++++++++++++++++++------ hpe/dl360/exporter_test.go | 368 +++++++++++++++++- hpe/dl360/memory.go | 37 -- hpe/dl360/metrics.go | 31 +- hpe/dl360/network_adapter.go | 65 ---- hpe/dl360/power.go | 78 ---- hpe/dl360/thermal.go | 56 --- hpe/dl380/drive.go | 125 ------ hpe/dl380/drive_test.go | 562 --------------------------- hpe/dl380/exporter.go | 472 ++++++++++++++++++----- hpe/dl380/exporter_test.go | 530 ++++++++++++++++++++++++++ hpe/dl380/memory.go | 37 -- hpe/dl380/metrics.go | 31 +- hpe/dl380/network_adapter.go | 65 ---- hpe/dl380/power.go | 77 ---- hpe/dl380/thermal.go | 54 --- hpe/dl560/drive.go | 121 ------ hpe/dl560/drive_test.go | 562 --------------------------- hpe/dl560/exporter.go | 416 +++++++++++++++++--- hpe/dl560/exporter_test.go | 530 ++++++++++++++++++++++++++ hpe/dl560/memory.go | 37 -- hpe/dl560/metrics.go | 31 +- hpe/dl560/network_adapter.go | 65 ---- hpe/dl560/power.go | 77 ---- hpe/dl560/thermal.go | 54 --- hpe/moonshot/exporter.go | 6 +- hpe/moonshot/power.go | 96 ----- hpe/moonshot/sw.go | 39 -- hpe/moonshot/sw_power.go | 54 --- hpe/moonshot/systems_summary.go | 43 --- hpe/moonshot/thermal.go | 77 ---- hpe/xl420/drive.go | 120 ------ hpe/xl420/drive_test.go | 562 --------------------------- hpe/xl420/exporter.go | 417 ++++++++++++++++---- hpe/xl420/exporter_test.go | 530 ++++++++++++++++++++++++++ hpe/xl420/memory.go | 37 -- hpe/xl420/metrics.go | 31 +- hpe/xl420/network_adapter.go | 65 ---- hpe/xl420/power.go | 78 ---- hpe/xl420/thermal.go | 56 --- {cisco/s3260m4 => oem}/chassis.go | 8 +- oem/common.go | 23 -- oem/memory.go | 27 +- oem/power.go | 93 +++-- {cisco/c220 => oem}/processor.go | 4 +- oem/storage_ctrl.go | 3 +- 80 files changed, 4730 insertions(+), 6255 deletions(-) delete mode 100644 cisco/c220/chassis.go delete mode 100644 cisco/c220/drive.go delete mode 100644 cisco/c220/drive_test.go delete mode 100644 cisco/c220/memory.go delete mode 100644 cisco/c220/memory_test.go delete mode 100644 cisco/c220/power.go delete mode 100644 cisco/c220/thermal.go delete mode 100644 cisco/s3260m4/drive.go delete mode 100644 cisco/s3260m4/memory.go delete mode 100644 cisco/s3260m4/power.go delete mode 100644 cisco/s3260m4/processor.go delete mode 100644 cisco/s3260m4/thermal.go delete mode 100644 cisco/s3260m5/chassis.go delete mode 100644 cisco/s3260m5/drive.go delete mode 100644 cisco/s3260m5/memory.go delete mode 100644 cisco/s3260m5/power.go delete mode 100644 cisco/s3260m5/processor.go delete mode 100644 cisco/s3260m5/thermal.go delete mode 100644 hpe/dl20/drive.go delete mode 100644 hpe/dl20/memory.go delete mode 100644 hpe/dl20/network_adapter.go delete mode 100644 hpe/dl20/power.go delete mode 100644 hpe/dl20/thermal.go delete mode 100644 hpe/dl360/drive.go delete mode 100644 hpe/dl360/drive_test.go delete mode 100644 hpe/dl360/memory.go delete mode 100644 hpe/dl360/network_adapter.go delete mode 100644 hpe/dl360/power.go delete mode 100644 hpe/dl360/thermal.go delete mode 100644 hpe/dl380/drive.go delete mode 100644 hpe/dl380/drive_test.go delete mode 100644 hpe/dl380/memory.go delete mode 100644 hpe/dl380/network_adapter.go delete mode 100644 hpe/dl380/power.go delete mode 100644 hpe/dl380/thermal.go delete mode 100644 hpe/dl560/drive.go delete mode 100644 hpe/dl560/drive_test.go delete mode 100644 hpe/dl560/memory.go delete mode 100644 hpe/dl560/network_adapter.go delete mode 100644 hpe/dl560/power.go delete mode 100644 hpe/dl560/thermal.go delete mode 100644 hpe/moonshot/power.go delete mode 100644 hpe/moonshot/sw.go delete mode 100644 hpe/moonshot/sw_power.go delete mode 100644 hpe/moonshot/systems_summary.go delete mode 100644 hpe/moonshot/thermal.go delete mode 100644 hpe/xl420/drive.go delete mode 100644 hpe/xl420/drive_test.go delete mode 100644 hpe/xl420/memory.go delete mode 100644 hpe/xl420/network_adapter.go delete mode 100644 hpe/xl420/power.go delete mode 100644 hpe/xl420/thermal.go rename {cisco/s3260m4 => oem}/chassis.go (91%) delete mode 100644 oem/common.go rename {cisco/c220 => oem}/processor.go (92%) diff --git a/cisco/c220/chassis.go b/cisco/c220/chassis.go deleted file mode 100644 index 1b3452d..0000000 --- a/cisco/c220/chassis.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -import ( - "bytes" - "encoding/json" -) - -// /redfish/v1/Managers/CIMC - -// Chassis contains the Model Number, Firmware, etc of the chassis -type Chassis struct { - FirmwareVersion string `json:"FirmwareVersion"` - Links struct { - ManagerForServers ServerManagerURLWrapper `json:"ManagerForServers"` - } `json:"Links"` - Model string `json:"Model"` - Description string `json:"Description"` -} - -type ServerManagerURL struct { - ServerManagerURLSlice []string -} - -type ServerManagerURLWrapper struct { - ServerManagerURL -} - -func (w *ServerManagerURLWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen c220 firmware versions we need to account for this - if bytes.Compare([]byte("[{"), data[0:2]) == 0 { - var serMgrTmp []struct { - Url string `json:"@odata.id,omitempty"` - } - err := json.Unmarshal(data, &serMgrTmp) - if len(serMgrTmp) > 0 { - s := make([]string, 0) - s = append(s, serMgrTmp[0].Url) - w.ServerManagerURLSlice = s - return nil - } - return err - } - return json.Unmarshal(data, &w.ServerManagerURLSlice) -} - -// Collection returns an array of the endpoints from the chassis pertaining to a resource type -type Collection struct { - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - MembersCount int `json:"Members@odata.count"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health,omitempty"` - HealthRollup string `json:"HealthRollup,omitempty"` - State string `json:"State,omitempty"` -} - -// /redfish/v1/Systems/XXXXX - -// ServerManager contains the BIOS version of the chassis -type ServerManager struct { - BiosVersion string `json:"BiosVersion"` -} diff --git a/cisco/c220/drive.go b/cisco/c220/drive.go deleted file mode 100644 index fd0e31e..0000000 --- a/cisco/c220/drive.go +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -import ( - "bytes" - "encoding/json" - "encoding/xml" -) - -// /redfish/v1/Systems/WZPXXXXX/Storage/MRAID - -type StorageControllerMetrics struct { - Name string `json:"Name"` - StorageController StorageControllerWrapper `json:"StorageControllers"` - Drives []Drive `json:"Drives"` -} - -// StorageController contains status metadata of the C220 chassis storage controller -type StorageController struct { - Status Status `json:"Status"` - MemberId string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - FirmwareVersion string `json:"FirmwareVersion"` -} - -type StorageControllerSlice struct { - StorageController []StorageController -} - -type StorageControllerWrapper struct { - StorageControllerSlice -} - -func (w *StorageControllerWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen c220 firmware versions we need to account for this - if bytes.Compare([]byte("{"), data[0:1]) == 0 { - var storCtlSlice StorageController - err := json.Unmarshal(data, &storCtlSlice) - if err != nil { - return err - } - s := make([]StorageController, 0) - s = append(s, storCtlSlice) - w.StorageController = s - return nil - } - return json.Unmarshal(data, &w.StorageController) -} - -type Drive struct { - Url string `json:"@odata.id"` -} - -type Error struct { - Error ErrorBody `json:"error"` -} - -type ErrorBody struct { - Code string `json:"code"` - Message string `json:"message"` - ExtendedInfo []ExtendedInfo `json:"@Message.ExtendedInfo"` -} - -type ExtendedInfo struct { - OdataType string `json:"@odata.type"` - MessageID string `json:"MessageId"` - Message string `json:"Message"` - MessageArg []string `json:"MessageArgs"` - Severity string `json:"Severity"` -} - -// /redfish/v1/Systems/WZPXXXXX/Storage/MRAID/Drives/X - -type DriveMetrics struct { - Id string `json:"Id"` - Name string `json:"Name"` - Model string `json:"Model"` - CapacityBytes int `json:"CapacityBytes"` - Status Status `json:"Status"` -} - -// XML class_id="StorageLocalDiskSlotEp" - -type XMLDriveMetrics struct { - XMLName xml.Name `xml:"configResolveClass"` - OutConfigs OutConfigs `xml:"outConfigs"` -} - -type OutConfigs struct { - XMLName xml.Name `xml:"outConfigs"` - Drives []StorageLocalDiskSlotEp `xml:"storageLocalDiskSlotEp"` -} - -type StorageLocalDiskSlotEp struct { - Id string `xml:"id,attr"` - Name string `xml:"dn,attr"` - Operability string `xml:"operability,attr"` - Presence string `xml:"presence,attr"` -} diff --git a/cisco/c220/drive_test.go b/cisco/c220/drive_test.go deleted file mode 100644 index 4b33d07..0000000 --- a/cisco/c220/drive_test.go +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -import ( - "context" - "encoding/json" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - driveHeader = ` - # HELP c220_drive_status Current drive status 1 = OK, 0 = BAD, -1 = DISABLED, 2 = Max sesssions reached - # TYPE c220_drive_status gauge - ` - - scHeader = ` - # HELP c220_storage_controller_status Current storage controller status 1 = OK, 0 = BAD, -1 = DISABLED, 2 = Max sesssions reached - # TYPE c220_storage_controller_status gauge - ` -) - -func Test_C220_Drive_Metrics(t *testing.T) { - - var exporter prometheus.Collector - - assert := assert.New(t) - - HealthyStorageControllerMetricsResponse, _ := json.Marshal(struct { - Name string `json:"Name"` - Drives []Drive `json:"Drives"` - StorageController []StorageController `json:"StorageControllers"` - }{ - Name: "MRAID", - Drives: []Drive{ - { - Url: "/redfish/v1/Systems/WZP1111111/Storage/MRAID/Drives/PD-1", - }, - }, - StorageController: []StorageController{ - { - MemberId: "RAID", - Model: "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", - Name: "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", - FirmwareVersion: "555555", - Status: Status{ - State: "Enabled", - Health: "OK", - HealthRollup: "OK", - }, - }, - }, - }) - - BadStorageControllerMetricsResponse, _ := json.Marshal(struct { - Name string `json:"Name"` - Drives []Drive `json:"Drives"` - StorageController []StorageController `json:"StorageControllers"` - }{ - Name: "MRAID", - Drives: []Drive{}, - StorageController: []StorageController{ - { - MemberId: "RAID", - Model: "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", - Name: "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", - FirmwareVersion: "555555", - Status: Status{ - State: "Enabled", - Health: "BAD", - HealthRollup: "BAD", - }, - }, - }, - }) - - // MissingStorageControllerMetricsResponse, _ := json.Marshal(Error{ - // Error: ErrorBody{ - // Code: "Base.1.4.GeneralError", - // Message: "See ExtendedInfo for more information.", - // ExtendedInfo: []ExtendedInfo{ - // { - // OdataType: "Message.v1_0_6.Message", - // MessageID: "Base.1.4.ResourceNotFound", - // Message: "The resource 'MRAID' does not exist.", - // MessageArg: []string{"MRAID"}, - // Severity: "Critical", - // }, - // }, - // }, - // }) - - metrx := NewDeviceMetrics() - - exporter = &Exporter{ - ctx: context.Background(), - host: "fishymetrics.com", - biosVersion: "C220M5.4.0.4i.0.zzzzzzzzz", - chassisSerialNumber: "SN78901", - deviceMetrics: metrx, - } - - prometheus.MustRegister(exporter) - - tests := []struct { - name string - response []byte - metricHeader string - expected string - }{ - { - name: "Healthy Storage Controller Metrics", - response: HealthyStorageControllerMetricsResponse, - metricHeader: scHeader, - expected: ` - c220_storage_controller_status{chassisSerialNumber="SN78901",firmwareVersion="555555",memberId="RAID",model="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)",name="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)"} 1 - `, - }, - { - name: "Bad Storage Controller Metrics", - response: BadStorageControllerMetricsResponse, - metricHeader: scHeader, - expected: ` - c220_storage_controller_status{chassisSerialNumber="SN78901",firmwareVersion="555555",memberId="RAID",model="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)",name="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)"} 0 - `, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := exporter.(*Exporter).exportStorageControllerMetrics(test.response) - if err != nil { - t.Error(err) - } - - drv := (*exporter.(*Exporter).deviceMetrics)["driveMetrics"] - drvMetrics := (*drv)["storageControllerStatus"] - - assert.Empty(testutil.CollectAndCompare(drvMetrics, strings.NewReader(test.metricHeader+test.expected), "c220_storage_controller_status")) - - drvMetrics.Reset() - - }) - } - - prometheus.Unregister(exporter) - -} diff --git a/cisco/c220/exporter.go b/cisco/c220/exporter.go index c8592ea..5653695 100644 --- a/cisco/c220/exporter.go +++ b/cisco/c220/exporter.go @@ -35,6 +35,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -65,6 +66,8 @@ const ( OK = 1.0 // BAD is a string representation of the float 0.0 for device status BAD = 0.0 + // DISABLED is a string representation of the float -1.0 for device status + DISABLED = -1.0 ) var ( @@ -348,7 +351,7 @@ func (e *Exporter) scrape() { // exportFirmwareMetrics collects the Cisco UCS C220's device metrics in json format and sets the prometheus gauges func (e *Exporter) exportFirmwareMetrics(body []byte) error { - var chas Chassis + var chas oem.Chassis var dm = (*e.deviceMetrics)["deviceInfo"] err := json.Unmarshal(body, &chas) if err != nil { @@ -364,7 +367,7 @@ func (e *Exporter) exportFirmwareMetrics(body []byte) error { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -391,7 +394,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) } } - (*pow)["supplyTotalConsumed"].WithLabelValues(pm.Url, e.chassisSerialNumber).Set(watts) + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) } for _, pv := range pm.Voltages { @@ -425,7 +428,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { case string: watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) } - (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.PartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) if ps.Status.Health == "OK" { state = OK } else if ps.Status.Health == "" { @@ -437,7 +440,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { state = BAD } - (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.PartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) } return nil @@ -447,40 +450,40 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling C220 ThermalMetrics - " + err.Error()) } - if tm.Status.State == "Enabled" { - if tm.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*therm)["thermalSummary"].WithLabelValues(tm.Url, e.chassisSerialNumber).Set(state) - } - // Iterate through fans for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { - var celsFan float64 + var fanSpeed float64 switch fan.Reading.(type) { case string: - celsFan, _ = strconv.ParseFloat(fan.Reading.(string), 32) + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) case float64: - celsFan = fan.Reading.(float64) + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) } - (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(celsFan) if fan.Status.Health == "OK" { state = OK } else { state = BAD } - (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } } } @@ -488,16 +491,16 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - var celsSensor float64 + var celsTemp float64 switch sensor.ReadingCelsius.(type) { case string: - celsSensor, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) case int: - celsSensor = float64(sensor.ReadingCelsius.(int)) + celsTemp = float64(sensor.ReadingCelsius.(int)) case float64: - celsSensor = sensor.ReadingCelsius.(float64) + celsTemp = sensor.ReadingCelsius.(float64) } - (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsSensor) + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { @@ -514,7 +517,7 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { func (e *Exporter) exportMemoryMetrics(body []byte) error { var state float64 - var mm MemoryMetrics + var mm oem.MemoryMetrics var mem = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &mm) if err != nil { @@ -578,7 +581,7 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { var state float64 var totThreads string - var pm ProcessorMetrics + var pm oem.ProcessorMetrics var proc = (*e.deviceMetrics)["processorMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -591,15 +594,20 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { totThreads = pm.TotalThreads.(string) case float64: totThreads = strconv.Itoa(int(pm.TotalThreads.(float64))) + case int: + totThreads = strconv.Itoa(pm.TotalThreads.(int)) } if pm.Status.Health == "OK" { state = OK } else { state = BAD } - (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, totThreads).Set(state) + } else { + state = DISABLED } + (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, totThreads).Set(state) + return nil } @@ -607,7 +615,7 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { func (e *Exporter) exportStorageControllerMetrics(body []byte) error { var state float64 - var scm StorageControllerMetrics + var scm oem.StorageControllerMetrics var drv = (*e.deviceMetrics)["driveMetrics"] err := json.Unmarshal(body, &scm) if err != nil { @@ -633,7 +641,7 @@ func (e *Exporter) exportDriveMetrics(body []byte) error { var state float64 var cap string - var dm DriveMetrics + var dm oem.DriveMetrics var drv = (*e.deviceMetrics)["driveMetrics"] err := json.Unmarshal(body, &dm) if err != nil { @@ -657,7 +665,7 @@ func (e *Exporter) exportDriveMetrics(body []byte) error { func (e *Exporter) exportXMLDriveMetrics(body []byte) error { var state float64 - var dm XMLDriveMetrics + var dm oem.XMLDriveMetrics var drv = (*e.deviceMetrics)["driveMetrics"] err := xml.Unmarshal(body, &dm) if err != nil { @@ -679,7 +687,7 @@ func (e *Exporter) exportXMLDriveMetrics(body []byte) error { } func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { - var chas Chassis + var chas oem.Chassis var urlFinal string req := common.BuildRequest(url, host) @@ -714,7 +722,7 @@ func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, } func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { - var biosVer ServerManager + var biosVer oem.ServerManager req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -739,8 +747,8 @@ func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, err return biosVer.BiosVersion, nil } -func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collection, error) { - var dimms Collection +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms oem.Collection var resp *http.Response var err error retryCount := 0 @@ -781,8 +789,8 @@ func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collectio return dimms, nil } -func checkRaidController(url, host string, client *retryablehttp.Client) (StorageControllerMetrics, bool, error) { - var scm StorageControllerMetrics +func checkRaidController(url, host string, client *retryablehttp.Client) (oem.StorageControllerMetrics, bool, error) { + var scm oem.StorageControllerMetrics var resp *http.Response var err error retryCount := 0 diff --git a/cisco/c220/exporter_test.go b/cisco/c220/exporter_test.go index eb6fd07..4c64b35 100644 --- a/cisco/c220/exporter_test.go +++ b/cisco/c220/exporter_test.go @@ -30,11 +30,41 @@ import ( ) const ( - up2Response = ` + up2Expected = ` # HELP up was the last scrape of fishymetrics successful. # TYPE up gauge up 2 ` + HealthyStorageControllerMetricsExpected = ` + # HELP c220_storage_controller_status Current storage controller status 1 = OK, 0 = BAD, -1 = DISABLED, 2 = Max sesssions reached + # TYPE c220_storage_controller_status gauge + c220_storage_controller_status{chassisSerialNumber="SN98765",firmwareVersion="555555",memberId="RAID",model="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)",name="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)"} 1 + ` + BadStorageControllerMetricsExpected = ` + # HELP c220_storage_controller_status Current storage controller status 1 = OK, 0 = BAD, -1 = DISABLED, 2 = Max sesssions reached + # TYPE c220_storage_controller_status gauge + c220_storage_controller_status{chassisSerialNumber="SN98765",firmwareVersion="555555",memberId="RAID",model="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)",name="Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)"} 0 + ` + HealthyFW4_0_4h_MemoryMetricsExpected = ` + # HELP c220_memory_dimm_status Current dimm status 1 = OK, 0 = BAD + # TYPE c220_memory_dimm_status gauge + c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN98765",manufacturer="0x2C00",name="DIMM_A1",partNumber="36ASF4G72PZ-2G6D1",serialNumber="SN123456"} 1 + ` + BadFW4_0_4h_MemoryMetricsExpected = ` + # HELP c220_memory_dimm_status Current dimm status 1 = OK, 0 = BAD + # TYPE c220_memory_dimm_status gauge + c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN98765",manufacturer="0x2C00",name="DIMM_A1",partNumber="36ASF4G72PZ-2G6D1",serialNumber="SN123456"} 0 + ` + HealthyFW4_1_2a_MemoryMetricsExpected = ` + # HELP c220_memory_dimm_status Current dimm status 1 = OK, 0 = BAD + # TYPE c220_memory_dimm_status gauge + c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN98765",manufacturer="0xAD00",name="DIMM_A1",partNumber="HMA84GR7DJR4N-WM",serialNumber="SN123456"} 1 + ` + BadFW4_1_2a_MemoryMetricsExpected = ` + # HELP c220_memory_dimm_status Current dimm status 1 = OK, 0 = BAD + # TYPE c220_memory_dimm_status gauge + c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN98765",manufacturer="0xAD00",name="DIMM_A1",partNumber="HMA84GR7DJR4N-WM",serialNumber="SN123456"} 0 + ` ) type TestErrorResponse struct { @@ -99,7 +129,7 @@ func Test_C220_Exporter(t *testing.T) { metricName: "up", metricRef1: "up", metricRef2: "up", - expected: up2Response, + expected: up2Expected, }, } @@ -119,7 +149,224 @@ func Test_C220_Exporter(t *testing.T) { assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) prometheus.Unregister(exporter) + }) + } +} + +// MissingStorageControllerMetricsResponse, _ := json.Marshal(Error{ +// Error: ErrorBody{ +// Code: "Base.1.4.GeneralError", +// Message: "See ExtendedInfo for more information.", +// ExtendedInfo: []ExtendedInfo{ +// { +// OdataType: "Message.v1_0_6.Message", +// MessageID: "Base.1.4.ResourceNotFound", +// Message: "The resource 'MRAID' does not exist.", +// MessageArg: []string{"MRAID"}, +// Severity: "Critical", +// }, +// }, +// }, +// }) + +func Test_C220_Metrics_Handling(t *testing.T) { + + var HealthyStorageControllerMetricsResponse = []byte(`{ + "Drives": [{ + "@odata.id": "/redfish/v1/Systems/WZP1111111/Storage/MRAID/Drives/PD-1" + }], + "Name": "MRAID", + "StorageControllers": [{ + "MemberId": "RAID", + "Model": "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", + "Name": "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", + "FirmwareVersion": "555555", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + } + }] + }`) + + var BadStorageControllerMetricsResponse = []byte(`{ + "Drives": [], + "Name": "MRAID", + "StorageControllers": [{ + "MemberId": "RAID", + "Model": "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", + "Name": "Cisco 12G Modular Raid Controller with 2GB cache (max 16 drives)", + "FirmwareVersion": "555555", + "Status": { + "State": "Enabled", + "Health": "BAD", + "HealthRollup": "BAD" + } + }] + }`) + + var HealthyFW4_0_4h_MemoryMetricsResponse = []byte(`{ + "SerialNumber": "SN123456", + "MemoryDeviceType": "DDR4", + "Status": { + "State": "Enabled" + }, + "Name": "DIMM_A1", + "PartNumber": "36ASF4G72PZ-2G6D1 ", + "Manufacturer": "0x2C00", + "CapacityMiB": 32768 + }`) + + var BadFW4_0_4h_MemoryMetricsResponse = []byte(`{ + "SerialNumber": "SN123456", + "MemoryDeviceType": "DDR4", + "Status": { + "State": "Bad" + }, + "Name": "DIMM_A1", + "PartNumber": "36ASF4G72PZ-2G6D1 ", + "Manufacturer": "0x2C00", + "CapacityMiB": 32768 + }`) + + var HealthyFW4_1_2a_MemoryMetricsResponse = []byte(`{ + "SerialNumber": "SN123456", + "MemoryDeviceType": "DDR4", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "Name": "DIMM_A1", + "PartNumber": "HMA84GR7DJR4N-WM ", + "Manufacturer": "0xAD00", + "CapacityMiB": 32768 + }`) + + var BadFW4_1_2a_MemoryMetricsResponse = []byte(`{ + "SerialNumber": "SN123456", + "MemoryDeviceType": "DDR4", + "Status": { + "State": "Enabled", + "Health": "NOPE" + }, + "Name": "DIMM_A1", + "PartNumber": "HMA84GR7DJR4N-WM ", + "Manufacturer": "0xAD00", + "CapacityMiB": 32768 + }`) + + var exporter prometheus.Collector + + assert := assert.New(t) + + metrx := NewDeviceMetrics() + + exporter = &Exporter{ + ctx: context.Background(), + host: "fishymetrics.com", + biosVersion: "C220M5.4.0.4i.0.zzzzzzzzz", + chassisSerialNumber: "SN98765", + deviceMetrics: metrx, + } + + prometheus.MustRegister(exporter) + + memDimmMetrics := func(exp *Exporter, resp []byte) error { + err := exp.exportMemoryMetrics(resp) + if err != nil { + return err + } + return nil + } + + storCtrlMetrics := func(exp *Exporter, resp []byte) error { + err := exp.exportStorageControllerMetrics(resp) + if err != nil { + return err + } + return nil + } + tests := []struct { + name string + metricName string + metricRef1 string + metricRef2 string + handleFunc func(*Exporter, []byte) error + response []byte + expected string + }{ + { + name: "Healthy FW 4.0.4h", + metricName: "c220_storage_controller_status", + metricRef1: "driveMetrics", + metricRef2: "storageControllerStatus", + handleFunc: storCtrlMetrics, + response: HealthyStorageControllerMetricsResponse, + expected: HealthyStorageControllerMetricsExpected, + }, + { + name: "Healthy FW 4.0.4h", + metricName: "c220_storage_controller_status", + metricRef1: "driveMetrics", + metricRef2: "storageControllerStatus", + handleFunc: storCtrlMetrics, + response: BadStorageControllerMetricsResponse, + expected: BadStorageControllerMetricsExpected, + }, + { + name: "Healthy FW 4.0.4h", + metricName: "c220_memory_dimm_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryStatus", + handleFunc: memDimmMetrics, + response: HealthyFW4_0_4h_MemoryMetricsResponse, + expected: HealthyFW4_0_4h_MemoryMetricsExpected, + }, + { + name: "Bad FW 4.0.4h", + metricName: "c220_memory_dimm_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryStatus", + handleFunc: memDimmMetrics, + response: BadFW4_0_4h_MemoryMetricsResponse, + expected: BadFW4_0_4h_MemoryMetricsExpected, + }, + { + name: "Healthy FW 4.1.2a", + metricName: "c220_memory_dimm_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryStatus", + handleFunc: memDimmMetrics, + response: HealthyFW4_1_2a_MemoryMetricsResponse, + expected: HealthyFW4_1_2a_MemoryMetricsExpected, + }, + { + name: "Bad FW 4.1.2a", + metricName: "c220_memory_dimm_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryStatus", + handleFunc: memDimmMetrics, + response: BadFW4_1_2a_MemoryMetricsResponse, + expected: BadFW4_1_2a_MemoryMetricsExpected, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.handleFunc(exporter.(*Exporter), test.response) + if err != nil { + t.Error(err) + } + + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + + assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) + + m.Reset() }) } + + prometheus.Unregister(exporter) } diff --git a/cisco/c220/memory.go b/cisco/c220/memory.go deleted file mode 100644 index 2ff5756..0000000 --- a/cisco/c220/memory.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -// /redfish/v1/Systems/WZPXXXXX/Memory/DIMM_X1 - -// MemoryMetrics is the top level json object for UCS C220 Memory metadata -type MemoryMetrics struct { - Name string `json:"Name"` - CapacityMiB interface{} `json:"CapacityMiB"` - Manufacturer string `json:"Manufacturer"` - MemoryDeviceType string `json:"MemoryDeviceType"` - PartNumber string `json:"PartNumber"` - SerialNumber string `json:"SerialNumber"` - Status interface{} `json:"Status"` -} diff --git a/cisco/c220/memory_test.go b/cisco/c220/memory_test.go deleted file mode 100644 index 53d7663..0000000 --- a/cisco/c220/memory_test.go +++ /dev/null @@ -1,158 +0,0 @@ -// /* -// * Copyright 2024 Comcast Cable Communications Management, LLC -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ - -package c220 - -import ( - "context" - "encoding/json" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - dimmHeader = ` - # HELP c220_memory_dimm_status Current dimm status 1 = OK, 0 = BAD - # TYPE c220_memory_dimm_status gauge - ` -) - -func Test_C220_Memory_Metrics(t *testing.T) { - - var exporter prometheus.Collector - - assert := assert.New(t) - - HealthyFW4_0_4h_MemoryMetricsResponse, _ := json.Marshal(MemoryMetrics{ - SerialNumber: "SN123456", - MemoryDeviceType: "DDR4", - PartNumber: "36ASF4G72PZ-2G6D1 ", - CapacityMiB: 32768, - Name: "DIMM_A1", - Manufacturer: "0x2C00", - Status: Status{ - State: "Enabled", - }, - }) - - BadFW4_0_4h_MemoryMetricsResponse, _ := json.Marshal(MemoryMetrics{ - SerialNumber: "SN123456", - MemoryDeviceType: "DDR4", - PartNumber: "36ASF4G72PZ-2G6D1 ", - CapacityMiB: 32768, - Name: "DIMM_A1", - Manufacturer: "0x2C00", - Status: Status{ - State: "Bad", - }, - }) - - HealthyFW4_1_2a_MemoryMetricsResponse, _ := json.Marshal(MemoryMetrics{ - SerialNumber: "SN123456", - MemoryDeviceType: "DDR4", - PartNumber: "HMA84GR7DJR4N-WM ", - CapacityMiB: 32768, - Name: "DIMM_A1", - Manufacturer: "0xAD00", - Status: Status{ - State: "Enabled", - Health: "OK", - }, - }) - - BadFW4_1_2a_MemoryMetricsResponse, _ := json.Marshal(MemoryMetrics{ - SerialNumber: "SN123456", - MemoryDeviceType: "DDR4", - PartNumber: "HMA84GR7DJR4N-WM ", - CapacityMiB: 32768, - Name: "DIMM_A1", - Manufacturer: "0xAD00", - Status: Status{ - State: "Enabled", - Health: "NOPE", - }, - }) - - metrx := NewDeviceMetrics() - - exporter = &Exporter{ - ctx: context.Background(), - host: "fishymetrics.com", - biosVersion: "C220M5.4.0.4i.0.zzzzzzzzz", - chassisSerialNumber: "SN78901", - deviceMetrics: metrx, - } - - prometheus.MustRegister(exporter) - - tests := []struct { - name string - response []byte - expected string - }{ - { - name: "Healthy FW 4.0.4h", - response: HealthyFW4_0_4h_MemoryMetricsResponse, - expected: ` - c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN78901",manufacturer="0x2C00",name="DIMM_A1",partNumber="36ASF4G72PZ-2G6D1",serialNumber="SN123456"} 1 - `, - }, - { - name: "Bad FW 4.0.4h", - response: BadFW4_0_4h_MemoryMetricsResponse, - expected: ` - c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN78901",manufacturer="0x2C00",name="DIMM_A1",partNumber="36ASF4G72PZ-2G6D1",serialNumber="SN123456"} 0 - `, - }, - { - name: "Healthy FW 4.1.2a", - response: HealthyFW4_1_2a_MemoryMetricsResponse, - expected: ` - c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN78901",manufacturer="0xAD00",name="DIMM_A1",partNumber="HMA84GR7DJR4N-WM",serialNumber="SN123456"} 1 - `, - }, - { - name: "Bad FW 4.1.2a", - response: BadFW4_1_2a_MemoryMetricsResponse, - expected: ` - c220_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN78901",manufacturer="0xAD00",name="DIMM_A1",partNumber="HMA84GR7DJR4N-WM",serialNumber="SN123456"} 0 - `, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := exporter.(*Exporter).exportMemoryMetrics(tt.response) - if err != nil { - t.Error(err) - } - - mem := (*exporter.(*Exporter).deviceMetrics)["memoryMetrics"] - memMetrics := (*mem)["memoryStatus"] - - assert.Empty(testutil.CollectAndCompare(memMetrics, strings.NewReader(dimmHeader+tt.expected), "c220_memory_dimm_status")) - - memMetrics.Reset() - - }) - } - - prometheus.Unregister(exporter) -} diff --git a/cisco/c220/metrics.go b/cisco/c220/metrics.go index 17c445a..b841ca2 100644 --- a/cisco/c220/metrics.go +++ b/cisco/c220/metrics.go @@ -44,7 +44,6 @@ func NewDeviceMetrics() *map[string]*metrics { "fanStatus": newServerMetric("c220_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), "sensorTemperature": newServerMetric("c220_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), "sensorStatus": newServerMetric("c220_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "thermalSummary": newServerMetric("c220_thermal_summary_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"url", "chassisSerialNumber"}), } PowerMetrics = &metrics{ diff --git a/cisco/c220/power.go b/cisco/c220/power.go deleted file mode 100644 index 4b07221..0000000 --- a/cisco/c220/power.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -import ( - "bytes" - "encoding/json" -) - -// JSON -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - Name string `json:"Name"` - PowerControl PowerControlWrapper `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` - Voltages []Voltages `json:"Voltages"` - Url string `json:"@odata.id"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - PowerConsumedWatts interface{} `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -type PowerControlSlice struct { - PowerControl []PowerControl -} - -type PowerControlWrapper struct { - PowerControlSlice -} - -func (w *PowerControlWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen c220 firmware versions we need to account for this - if bytes.Compare([]byte("{"), data[0:1]) == 0 { - var powCtlSlice PowerControl - err := json.Unmarshal(data, &powCtlSlice) - if err != nil { - return err - } - s := make([]PowerControl, 0) - s = append(s, powCtlSlice) - w.PowerControl = s - return nil - } - return json.Unmarshal(data, &w.PowerControl) -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts interface{} `json:"AverageConsumedWatts"` - MaxConsumedWatts interface{} `json:"MaxConsumedWatts"` - MinConsumedWatts interface{} `json:"MinConsumedWatts"` -} - -// Voltages contains current/lower/upper voltage and power supply status metadata -type Voltages struct { - Name string `json:"Name"` - ReadingVolts interface{} `json:"ReadingVolts"` - Status Status `json:"Status"` - UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts interface{} `json:"LastPowerOutputWatts"` - LineInputVoltage interface{} `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - InputRanges []InputRange `json:"InputRanges,omitempty"` - Manufacturer string `json:"Manufacturer"` - Model string `json:"Model"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// InputRange is the top level json object for input voltage metadata -type InputRange struct { - InputType string `json:"InputType"` - OutputWattage int `json:"OutputWattage"` -} diff --git a/cisco/c220/thermal.go b/cisco/c220/thermal.go deleted file mode 100644 index 160ee9f..0000000 --- a/cisco/c220/thermal.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package c220 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for UCS C220 Thermal metadata -type ThermalMetrics struct { - Status Status `json:"Status"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` - Url string `json:"@odata.id"` -} - -// Fan is the json object for a UCS C220 fan module -type Fan struct { - Name string `json:"Name"` - Reading interface{} `json:"Reading"` - ReadingUnits string `json:"ReadingUnits"` - Status Status `json:"Status"` -} - -// Temperature is the json object for a UCS C220 temperature sensor module -type Temperature struct { - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius interface{} `json:"ReadingCelsius"` - Status Status `json:"Status"` - UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` -} diff --git a/cisco/s3260m4/drive.go b/cisco/s3260m4/drive.go deleted file mode 100644 index 3036c58..0000000 --- a/cisco/s3260m4/drive.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m4 - -import ( - "bytes" - "encoding/json" -) - -// /redfish/v1/Systems/XXXXX/Storage/SBMezzX - -type StorageControllerMetrics struct { - Name string `json:"Name,omitempty"` - StorageController StorageControllerWrapper `json:"StorageControllers"` - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - Url string `json:"@odata.id"` -} - -// StorageController contains storage controller status metadata -type StorageController struct { - Name string `json:"Name"` - Status Status `json:"Status"` - Manufacturer string `json:"Manufacturer"` - FirmwareVersion string `json:"FirmwareVersion"` -} - -type StorageControllerSlice struct { - StorageController []StorageController -} - -type StorageControllerWrapper struct { - StorageControllerSlice -} - -func (w *StorageControllerWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen s3260m4 firmware versions we need to account for this - if bytes.Compare([]byte("{"), data[0:1]) == 0 { - var storCtlSlice StorageController - err := json.Unmarshal(data, &storCtlSlice) - if err != nil { - return err - } - s := make([]StorageController, 0) - s = append(s, storCtlSlice) - w.StorageController = s - return nil - } - return json.Unmarshal(data, &w.StorageController) -} - -// Drive contains disk status metadata -type Drive struct { - Name string `json:"Name"` - SerialNumber string `json:"SerialNumber"` - Protocol string `json:"Protocol"` - MediaType string `json:"MediaType"` - Status Status `json:"Status"` - CapableSpeedGbs string `json:"CapableSpeedGbs"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes string `json:"CapacityBytes"` -} - -// /redfish/v1/Systems/XXXXX/SimpleStorage/SBMezzX -// /redfish/v1/Systems/XXXXX/SimpleStorage/IOEMezz1 - -// DriveMetrics contains drive status information all in one API call -type DriveMetrics struct { - Devices []DriveStatus `json:"Devices"` -} - -type DriveStatus struct { - Name string `json:"Name"` - Status Status `json:"Status"` - CapacityBytes int `json:"CapacityBytes,omitempty"` - Manufacturer string `json:"Manufacturer,omitempty"` -} diff --git a/cisco/s3260m4/exporter.go b/cisco/s3260m4/exporter.go index fe04b46..3b59b0d 100644 --- a/cisco/s3260m4/exporter.go +++ b/cisco/s3260m4/exporter.go @@ -34,6 +34,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -346,7 +347,7 @@ func (e *Exporter) scrape() { // exportFirmwareMetrics collects the Cisco UCS S3260M4's device metrics in json format and sets the prometheus gauges func (e *Exporter) exportFirmwareMetrics(body []byte) error { - var chas Chassis + var chas oem.Chassis var dm = (*e.deviceMetrics)["deviceInfo"] err := json.Unmarshal(body, &chas) if err != nil { @@ -362,7 +363,7 @@ func (e *Exporter) exportFirmwareMetrics(body []byte) error { func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var pm PowerMetrics + var pm oem.PowerMetrics var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -390,7 +391,7 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) } } - (*pow)["supplyTotalConsumed"].WithLabelValues(pm.Url, e.chassisSerialNumber).Set(watts) + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) } for _, pv := range pm.Voltages { @@ -428,40 +429,40 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling S3260M4 ThermalMetrics - " + err.Error()) } - if tm.Status.State == "Enabled" { - if tm.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*therm)["thermalSummary"].WithLabelValues(tm.Url, e.chassisSerialNumber).Set(state) - } - // Iterate through fans for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { - var celsFan float64 + var fanSpeed float64 switch fan.Reading.(type) { case string: - celsFan, _ = strconv.ParseFloat(fan.Reading.(string), 32) + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) case float64: - celsFan = fan.Reading.(float64) + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) } - (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(celsFan) if fan.Status.Health == "OK" { state = OK } else { state = BAD } - (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } } } @@ -469,16 +470,16 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - var celsSensor float64 + var celsTemp float64 switch sensor.ReadingCelsius.(type) { case string: - celsSensor, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) case int: - celsSensor = float64(sensor.ReadingCelsius.(int)) + celsTemp = float64(sensor.ReadingCelsius.(int)) case float64: - celsSensor = sensor.ReadingCelsius.(float64) + celsTemp = sensor.ReadingCelsius.(float64) } - (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsSensor) + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { @@ -495,7 +496,7 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { func (e *Exporter) exportMemoryMetrics(body []byte) error { var state float64 - var mm MemoryMetrics + var mm oem.MemoryMetrics var mem = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &mm) if err != nil { @@ -525,21 +526,30 @@ func (e *Exporter) exportMemoryMetrics(body []byte) error { } default: if s, ok := mm.Status.(map[string]interface{}); ok { - if s["State"].(string) == "Enabled" { - if s["Health"].(string) == "OK" { - state = OK - } else if s["Health"].(string) == "" { - state = OK + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil } else { state = BAD } - } else { - state = BAD } } } - - (*mem)["memoryStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, mm.PartNumber, mm.SerialNumber).Set(state) + (*mem)["memoryStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) } return nil @@ -550,7 +560,7 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { var state float64 var totThreads string - var pm ProcessorMetrics + var pm oem.ProcessorMetrics var proc = (*e.deviceMetrics)["processorMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -563,15 +573,20 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { totThreads = pm.TotalThreads.(string) case float64: totThreads = strconv.Itoa(int(pm.TotalThreads.(float64))) + case int: + totThreads = strconv.Itoa(pm.TotalThreads.(int)) } if pm.Status.Health == "OK" { state = OK } else { state = BAD } - (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, totThreads).Set(state) + } else { + state = DISABLED } + (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, totThreads).Set(state) + return nil } @@ -579,7 +594,7 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { func (e *Exporter) exportDriveMetrics(body []byte) error { var state float64 - var scm StorageControllerMetrics + var scm oem.StorageControllerMetrics var dlDrive = (*e.deviceMetrics)["driveMetrics"] err := json.Unmarshal(body, &scm) if err != nil { @@ -597,14 +612,14 @@ func (e *Exporter) exportDriveMetrics(body []byte) error { state = DISABLED } - (*dlDrive)["storageControllerStatus"].WithLabelValues(path.Base(scm.Url), e.chassisSerialNumber, sc.FirmwareVersion, sc.Manufacturer, sc.Name).Set(state) + (*dlDrive)["storageControllerStatus"].WithLabelValues(sc.Name, e.chassisSerialNumber, sc.FirmwareVersion, sc.Manufacturer, sc.Model).Set(state) } return nil } -func getManagerEndpoint(url, host string, client *retryablehttp.Client) (Chassis, error) { - var chas Chassis +func getManagerEndpoint(url, host string, client *retryablehttp.Client) (oem.Chassis, error) { + var chas oem.Chassis req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -634,7 +649,7 @@ func getManagerEndpoint(url, host string, client *retryablehttp.Client) (Chassis } func getChassisSerialNumber(url, host string, client *retryablehttp.Client) (string, error) { - var chassSN ChassisSerialNumber + var chassSN oem.ChassisSerialNumber req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -659,8 +674,8 @@ func getChassisSerialNumber(url, host string, client *retryablehttp.Client) (str return chassSN.SerialNumber, nil } -func getChassisEndpoint(url, host string, client *retryablehttp.Client) (Collection, error) { - var chas Collection +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var chas oem.Collection req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -686,7 +701,7 @@ func getChassisEndpoint(url, host string, client *retryablehttp.Client) (Collect } func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { - var biosVer ServerManager + var biosVer oem.ServerManager req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -711,8 +726,8 @@ func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, err return biosVer.BiosVersion, nil } -func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collection, error) { - var dimms Collection +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms oem.Collection var resp *http.Response var err error retryCount := 0 @@ -753,8 +768,8 @@ func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collectio return dimms, nil } -func getRaidEndpoint(url, host string, client *retryablehttp.Client) (Collection, error) { - var rcontrollers Collection +func getRaidEndpoint(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var rcontrollers oem.Collection var resp *http.Response var err error retryCount := 0 diff --git a/cisco/s3260m4/memory.go b/cisco/s3260m4/memory.go deleted file mode 100644 index 6a5e939..0000000 --- a/cisco/s3260m4/memory.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m4 - -// /redfish/v1/Systems/XXXXX/Memory/DIMM_X1 - -// MemoryMetrics is the top level json object for UCS S3260 M4 Memory metadata -type MemoryMetrics struct { - Name string `json:"Name"` - CapacityMiB interface{} `json:"CapacityMiB"` - Manufacturer string `json:"Manufacturer"` - MemoryDeviceType string `json:"MemoryDeviceType"` - PartNumber string `json:"PartNumber"` - SerialNumber string `json:"SerialNumber"` - Status interface{} `json:"Status"` -} diff --git a/cisco/s3260m4/metrics.go b/cisco/s3260m4/metrics.go index 2fc6574..1beee57 100644 --- a/cisco/s3260m4/metrics.go +++ b/cisco/s3260m4/metrics.go @@ -44,7 +44,6 @@ func NewDeviceMetrics() *map[string]*metrics { "fanStatus": newServerMetric("s3260m4_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), "sensorTemperature": newServerMetric("s3260m4_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), "sensorStatus": newServerMetric("s3260m4_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "thermalSummary": newServerMetric("s3260m4_thermal_summary_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"url", "chassisSerialNumber"}), } PowerMetrics = &metrics{ diff --git a/cisco/s3260m4/power.go b/cisco/s3260m4/power.go deleted file mode 100644 index c443a67..0000000 --- a/cisco/s3260m4/power.go +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m4 - -import ( - "bytes" - "encoding/json" - "encoding/xml" -) - -// XML classId='equipmentPsu' - -type XMLPowerMetrics struct { - XMLName xml.Name `xml:"configResolveClass"` - OutConfigs OutConfigs `xml:"outConfigs"` -} - -type OutConfigs struct { - XMLName xml.Name `xml:"outConfigs"` - PSUs []EquipmentPsu `xml:"equipmentPsu"` -} - -type EquipmentPsu struct { - ID string `xml:"id,attr"` - Name string `xml:"dn,attr"` - Pid string `xml:"pid,attr"` - Model string `xml:"mode,attr"` - Operability string `xml:"operability,attr"` - Power string `xml:"power,attr"` - Presence string `xml:"presence,attr"` - Serial string `xml:"serial,attr"` - Thermal string `xml:"thermal,attr"` - Voltage string `xml:"voltage,attr"` - Input string `xml:"input,attr"` - MaxOutput string `xml:"maxOutput,attr"` - FirmwareVersion string `xml:"fwVersion,attr"` -} - -// JSON -// /redfish/v1/Chassis/XXXXX/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - Name string `json:"Name"` - PowerControl PowerControlWrapper `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies,omitempty"` - Voltages []Voltages `json:"Voltages"` - Url string `json:"@odata.id"` -} - -type PowerControlSlice struct { - PowerControl []PowerControl -} - -type PowerControlWrapper struct { - PowerControlSlice -} - -func (w *PowerControlWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen s3260m4 firmware versions we need to account for this - if bytes.Compare([]byte("{"), data[0:1]) == 0 { - var powCtlSlice PowerControl - err := json.Unmarshal(data, &powCtlSlice) - if err != nil { - return err - } - s := make([]PowerControl, 0) - s = append(s, powCtlSlice) - w.PowerControl = s - return nil - } - return json.Unmarshal(data, &w.PowerControl) -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - PowerLimit PowerLimitWrapper `json:"PowerLimit"` - PowerConsumedWatts interface{} `json:"PowerConsumedWatts,omitempty"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -type PowerLimit struct { - LimitInWatts interface{} `json:"LimitInWatts,omitempty"` -} - -type PowerLimitWrapper struct { - PowerLimit -} - -func (w *PowerLimitWrapper) UnmarshalJSON(data []byte) error { - if bytes.Compare([]byte("[]"), data) == 0 { - w.PowerLimit = PowerLimit{} - return nil - } - return json.Unmarshal(data, &w.PowerLimit) -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts interface{} `json:"AverageConsumedWatts"` - MaxConsumedWatts interface{} `json:"MaxConsumedWatts"` - MinConsumedWatts interface{} `json:"MinConsumedWatts"` -} - -// Voltages contains current/lower/upper voltage and power supply status metadata -type Voltages struct { - Name string `json:"Name"` - ReadingVolts interface{} `json:"ReadingVolts"` - Status Status `json:"Status"` - UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts interface{} `json:"LastPowerOutputWatts"` - LineInputVoltage interface{} `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - InputRanges []InputRange `json:"InputRanges,omitempty"` - Manufacturer string `json:"Manufacturer"` - Model string `json:"Model"` - Name string `json:"Name"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status struct { - State string `json:"state"` - } `json:"Status"` -} - -// InputRange is the top level json object for input voltage metadata -type InputRange struct { - InputType string `json:"InputType"` - OutputWattage int `json:"OutputWattage"` -} diff --git a/cisco/s3260m4/processor.go b/cisco/s3260m4/processor.go deleted file mode 100644 index 9d76efb..0000000 --- a/cisco/s3260m4/processor.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m4 - -// /redfish/v1/Systems/XXXXX/Processors/CPUX - -// ProcessorMetrics is the top level json object for UCS S3260 M4 Processor metadata -type ProcessorMetrics struct { - Name string `json:"Name"` - Description string `json:"Description"` - Status Status `json:"Status"` - ProcessorArchitecture string `json:"ProcessorArchitecture"` - TotalThreads interface{} `json:"TotalThreads"` - TotalCores interface{} `json:"TotalCores"` - Model string `json:"Model"` -} diff --git a/cisco/s3260m4/thermal.go b/cisco/s3260m4/thermal.go deleted file mode 100644 index 2ad79c7..0000000 --- a/cisco/s3260m4/thermal.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m4 - -// /redfish/v1/Chassis/XXXX/Thermal/ - -// ThermalMetrics is the top level json object for UCS S3260 M4 Thermal metadata -type ThermalMetrics struct { - Status Status `json:"Status"` - Fans []Fan `json:"Fans,omitempty"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` - Url string `json:"@odata.id"` -} - -// Fan is the json object for a UCS S3260 M4 fan module -type Fan struct { - Name string `json:"Name"` - Reading interface{} `json:"Reading"` - ReadingUnits string `json:"ReadingUnits"` - Status Status `json:"Status"` -} - -// Temperature is the json object for a UCS S3260 M4 temperature sensor module -type Temperature struct { - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius interface{} `json:"ReadingCelsius"` - Status Status `json:"Status"` - UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` -} diff --git a/cisco/s3260m5/chassis.go b/cisco/s3260m5/chassis.go deleted file mode 100644 index 03bcfc9..0000000 --- a/cisco/s3260m5/chassis.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -// /redfish/v1/Managers/BMC1 - -// Chassis contains the Model Number, Firmware, etc of the chassis -type Chassis struct { - FirmwareVersion string `json:"FirmwareVersion"` - Links struct { - ServerManager []struct { - URL string `json:"@odata.id"` - } `json:"ManagerForServers"` - } `json:"Links"` - Model string `json:"Model"` - Description string `json:"Description"` -} - -// Collection returns an array of the endpoints from the chassis pertaining to a resource type -type Collection struct { - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - MembersCount int `json:"Members@odata.count"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health,omitempty"` - State string `json:"State,omitempty"` -} - -// /redfish/v1/Systems/XXXXX - -// ServerManager contains the BIOS version of the chassis -type ServerManager struct { - BiosVersion string `json:"BiosVersion"` -} - -// /redfish/v1/Chassis/CMC - -// Chassis contains the Model Number, Firmware, etc of the chassis -type ChassisSerialNumber struct { - SerialNumber string `json:"SerialNumber"` -} diff --git a/cisco/s3260m5/drive.go b/cisco/s3260m5/drive.go deleted file mode 100644 index 25db8c6..0000000 --- a/cisco/s3260m5/drive.go +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -// /redfish/v1/Systems/XXXXX/Storage/SBMezzX - -type StorageControllerMetrics struct { - Name string `json:"Name,omitempty"` - StorageControllers []StorageController `json:"StorageControllers"` - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` -} - -// StorageController contains storage controller status metadata -type StorageController struct { - Name string `json:"Name"` - Status Status `json:"Status"` - Manufacturer string `json:"Manufacturer"` - FirmwareVersion string `json:"FirmwareVersion"` -} - -// Drive contains disk status metadata -type Drive struct { - Name string `json:"Name"` - SerialNumber string `json:"SerialNumber"` - Protocol string `json:"Protocol"` - MediaType string `json:"MediaType"` - Status Status `json:"Status"` - CapableSpeedGbs int `json:"CapableSpeedGbs"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` -} - -// /redfish/v1/Systems/XXXXX/SimpleStorage/SBMezzX - -// DrivesMetrics contains drive status information all in one API call -type DriveMetrics struct { - Devices []DriveStatus `json:"Devices"` -} - -type DriveStatus struct { - Name string `json:"Name"` - Status Status `json:"Status"` - CapacityBytes int `json:"CapacityBytes,omitempty"` - Manufacturer string `json:"Manufacturer,omitempty"` -} diff --git a/cisco/s3260m5/exporter.go b/cisco/s3260m5/exporter.go index 7b75912..1433506 100644 --- a/cisco/s3260m5/exporter.go +++ b/cisco/s3260m5/exporter.go @@ -34,6 +34,7 @@ import ( "github.com/comcast/fishymetrics/common" "github.com/comcast/fishymetrics/config" + "github.com/comcast/fishymetrics/oem" "github.com/comcast/fishymetrics/pool" "go.uber.org/zap" @@ -162,8 +163,8 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return nil, err } - if len(mgrEndpoints.Links.ServerManager) > 0 { - mgr = mgrEndpoints.Links.ServerManager[0].URL + if len(mgrEndpoints.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + mgr = mgrEndpoints.Links.ManagerForServers.ServerManagerURLSlice[0] } // BMC Firmware major.minor @@ -364,7 +365,7 @@ func (e *Exporter) scrape() { // exportFirmwareMetrics collects the Cisco UCS S3260M5's device metrics in json format and sets the prometheus gauges func (e *Exporter) exportFirmwareMetrics(body []byte) error { - var chas Chassis + var chas oem.Chassis var dm = (*e.deviceMetrics)["deviceInfo"] err := json.Unmarshal(body, &chas) if err != nil { @@ -379,44 +380,80 @@ func (e *Exporter) exportFirmwareMetrics(body []byte) error { // exportPowerMetrics collects the Cisco UCS S3260M5's power metrics in json format and sets the prometheus gauges func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 - var watts float64 - var pm PowerMetrics + var pm oem.PowerMetrics var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { return fmt.Errorf("Error Unmarshalling S3260M5 PowerMetrics - " + err.Error()) } - for _, pc := range pm.PowerControl { - if pc.PowerConsumedWatts == 0 { + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: // use the AverageConsumedWatts if PowerConsumedWatts is not present - watts = float64(pc.PowerMetrics.AverageConsumedWatts) - } else { - watts = float64(pc.PowerConsumedWatts) + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } } - (*pow)["supplyTotalConsumed"].WithLabelValues(pm.Url, e.chassisSerialNumber).Set(watts) + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) } for _, pv := range pm.Voltages { if pv.Status.State == "Enabled" { - (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(pv.ReadingVolts) + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) if pv.Status.Health == "OK" { state = OK } else { state = BAD } - (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) + } else { + state = BAD } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) } for _, ps := range pm.PowerSupplies { if ps.Status.State == "Enabled" { - state = OK - (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SerialNumber, ps.FirmwareVersion, ps.PowerSupplyType, ps.Model).Set(float64(ps.LastPowerOutputWatts)) + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) + if ps.Status.Health == "OK" { + state = OK + } else if ps.Status.Health == "" { + state = OK + } else { + state = BAD + } } else { - state = DISABLED + state = BAD } - (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SerialNumber, ps.FirmwareVersion, ps.PowerSupplyType, ps.Model).Set(state) + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) } return nil @@ -424,35 +461,41 @@ func (e *Exporter) exportPowerMetrics(body []byte) error { // exportThermalMetrics collects the Cisco UCS S3260M5's thermal and fan metrics in json format and sets the prometheus gauges func (e *Exporter) exportThermalMetrics(body []byte) error { - var state float64 - var tm ThermalMetrics + var tm oem.ThermalMetrics var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling S3260M5 ThermalMetrics - " + err.Error()) } - if tm.Status.State == "Enabled" { - if tm.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*therm)["thermalSummary"].WithLabelValues(tm.Url, e.chassisSerialNumber).Set(state) - } - // Iterate through fans for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { - (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(float64(fan.Reading)) + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) + } if fan.Status.Health == "OK" { state = OK } else { state = BAD } - (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } } } @@ -460,7 +503,16 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(sensor.ReadingCelsius) + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { @@ -475,28 +527,62 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { // exportMemoryMetrics collects the Cisco UCS S3260M5's memory metrics in json format and sets the prometheus gauges func (e *Exporter) exportMemoryMetrics(body []byte) error { - var state float64 - var mm MemoryMetrics + var mm oem.MemoryMetrics var mem = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &mm) if err != nil { return fmt.Errorf("Error Unmarshalling S3260M5 MemoryMetrics - " + err.Error()) } - if mm.Status.State != "" { - if mm.Status.State == "Absent" { - return nil - } else if mm.Status.State == "Enabled" && mm.Status.Health == "OK" { - state = OK - } else { - state = BAD + if mm.Status != "" { + var memCap string + var status string + + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) } - } else { - state = DISABLED - } - (*mem)["memoryStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, strconv.Itoa(mm.CapacityMiB), mm.Manufacturer, mm.PartNumber, mm.SerialNumber).Set(state) + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { + state = OK + } else { + state = BAD + } + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } + } + } + (*mem)["memoryStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) + } return nil } @@ -505,7 +591,8 @@ func (e *Exporter) exportMemoryMetrics(body []byte) error { func (e *Exporter) exportProcessorMetrics(body []byte) error { var state float64 - var pm ProcessorMetrics + var totThreads string + var pm oem.ProcessorMetrics var proc = (*e.deviceMetrics)["processorMetrics"] err := json.Unmarshal(body, &pm) if err != nil { @@ -513,6 +600,14 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { } if pm.Status.State == "Enabled" { + switch pm.TotalThreads.(type) { + case string: + totThreads = pm.TotalThreads.(string) + case float64: + totThreads = strconv.Itoa(int(pm.TotalThreads.(float64))) + case int: + totThreads = strconv.Itoa(pm.TotalThreads.(int)) + } if pm.Status.Health == "OK" { state = OK } else { @@ -522,7 +617,7 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { state = DISABLED } - (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, strconv.Itoa(pm.TotalThreads)).Set(state) + (*proc)["processorStatus"].WithLabelValues(pm.Name, e.chassisSerialNumber, pm.Description, totThreads).Set(state) return nil } @@ -531,14 +626,14 @@ func (e *Exporter) exportProcessorMetrics(body []byte) error { func (e *Exporter) exportDriveMetrics(body []byte) error { var state float64 - var scm StorageControllerMetrics + var scm oem.StorageControllerMetrics var dlDrive = (*e.deviceMetrics)["driveMetrics"] err := json.Unmarshal(body, &scm) if err != nil { return fmt.Errorf("Error Unmarshalling S3260M5 DriveMetrics - " + err.Error()) } // Check logical drive is enabled then check status and convert string to numeric values - for _, sc := range scm.StorageControllers { + for _, sc := range scm.StorageController.StorageController { if sc.Status.State == "Enabled" { if sc.Status.Health == "OK" { state = OK @@ -555,8 +650,8 @@ func (e *Exporter) exportDriveMetrics(body []byte) error { return nil } -func getManagerEndpoint(url, host string, client *retryablehttp.Client) (Chassis, error) { - var chas Chassis +func getManagerEndpoint(url, host string, client *retryablehttp.Client) (oem.Chassis, error) { + var chas oem.Chassis var resp *http.Response var err error retryCount := 0 @@ -600,7 +695,7 @@ func getManagerEndpoint(url, host string, client *retryablehttp.Client) (Chassis } func getChassisSerialNumber(url, host string, client *retryablehttp.Client) (string, error) { - var chassSN ChassisSerialNumber + var chassSN oem.ChassisSerialNumber req := common.BuildRequest(url, host) resp, err := client.Do(req) @@ -625,8 +720,8 @@ func getChassisSerialNumber(url, host string, client *retryablehttp.Client) (str return chassSN.SerialNumber, nil } -func getChassisEndpoint(url, host string, client *retryablehttp.Client) (Collection, error) { - var chas Collection +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var chas oem.Collection var resp *http.Response var err error retryCount := 0 @@ -668,7 +763,7 @@ func getChassisEndpoint(url, host string, client *retryablehttp.Client) (Collect } func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { - var biosVer ServerManager + var biosVer oem.ServerManager var resp *http.Response var err error retryCount := 0 @@ -709,8 +804,8 @@ func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, err return biosVer.BiosVersion, nil } -func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collection, error) { - var dimms Collection +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms oem.Collection var resp *http.Response var err error retryCount := 0 @@ -751,8 +846,8 @@ func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (Collectio return dimms, nil } -func getRaidEndpoint(url, host string, client *retryablehttp.Client) (Collection, error) { - var rcontrollers Collection +func getRaidEndpoint(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var rcontrollers oem.Collection var resp *http.Response var err error retryCount := 0 diff --git a/cisco/s3260m5/memory.go b/cisco/s3260m5/memory.go deleted file mode 100644 index 2501554..0000000 --- a/cisco/s3260m5/memory.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -// /redfish/v1/Systems/XXXXX/Memory/DIMM_X1 - -// MemoryMetrics is the top level json object for UCS S3260 M5 Memory metadata -type MemoryMetrics struct { - Name string `json:"Name"` - CapacityMiB int `json:"CapacityMiB"` - Manufacturer string `json:"Manufacturer"` - MemoryDeviceType string `json:"MemoryDeviceType"` - PartNumber string `json:"PartNumber"` - SerialNumber string `json:"SerialNumber"` - Status Status `json:"Status"` -} diff --git a/cisco/s3260m5/metrics.go b/cisco/s3260m5/metrics.go index c0b7d6a..98368c6 100644 --- a/cisco/s3260m5/metrics.go +++ b/cisco/s3260m5/metrics.go @@ -44,7 +44,6 @@ func NewDeviceMetrics() *map[string]*metrics { "fanStatus": newServerMetric("s3260m5_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), "sensorTemperature": newServerMetric("s3260m5_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), "sensorStatus": newServerMetric("s3260m5_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "thermalSummary": newServerMetric("s3260m5_thermal_summary_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"url", "chassisSerialNumber"}), } PowerMetrics = &metrics{ diff --git a/cisco/s3260m5/power.go b/cisco/s3260m5/power.go deleted file mode 100644 index 6b0c7f9..0000000 --- a/cisco/s3260m5/power.go +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -import ( - "bytes" - "encoding/json" -) - -// JSON -// /redfish/v1/Chassis/XXXXX/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies,omitempty"` - Voltages []Voltages `json:"Voltages"` - Url string `json:"@odata.id"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - PowerLimit PowerLimitWrapper `json:"PowerLimit"` - PowerConsumedWatts int `json:"PowerConsumedWatts,omitempty"` - PowerMetrics PowerMetricWrapper `json:"PowerMetrics"` -} - -type PowerLimit struct { - LimitInWatts int `json:"LimitInWatts,omitempty"` -} - -type PowerLimitWrapper struct { - PowerLimit -} - -func (w *PowerLimitWrapper) UnmarshalJSON(data []byte) error { - if bytes.Compare([]byte("[]"), data) == 0 { - w.PowerLimit = PowerLimit{} - return nil - } - return json.Unmarshal(data, &w.PowerLimit) -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -type PowerMetricWrapper struct { - PowerMetric -} - -func (w *PowerMetricWrapper) UnmarshalJSON(data []byte) error { - // because of a bug in the s3260m5 firmware we need to account for this - if bytes.Compare([]byte("[]"), data) == 0 { - w.PowerMetric = PowerMetric{} - return nil - } - return json.Unmarshal(data, &w.PowerMetric) -} - -// Voltages contains current/lower/upper voltage and power supply status metadata -type Voltages struct { - Name string `json:"Name"` - ReadingVolts float64 `json:"ReadingVolts"` - Status Status `json:"Status"` - UpperThresholdCritical float64 `json:"UpperThresholdCritical"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage string `json:"LineInputVoltage,omitempty"` - LineInputVoltageType string `json:"LineInputVoltageType,omitempty"` - InputRanges []InputRange `json:"InputRanges,omitempty"` - Manufacturer string `json:"Manufacturer"` - Model string `json:"Model"` - Name string `json:"Name"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber,omitempty"` - Status Status `json:"Status"` -} - -// InputRange is the top level json object for input voltage metadata -type InputRange struct { - InputType string `json:"InputType"` - OutputWattage int `json:"OutputWattage"` -} diff --git a/cisco/s3260m5/processor.go b/cisco/s3260m5/processor.go deleted file mode 100644 index 831ca8d..0000000 --- a/cisco/s3260m5/processor.go +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -// /redfish/v1/Systems/XXXXX/Processors/CPUX - -// ProcessorMetrics is the top level json object for UCS C220 Processor metadata -type ProcessorMetrics struct { - Name string `json:"Name"` - Description string `json:"Description"` - Status Status `json:"Status"` - ProcessorArchitecture string `json:"ProcessorArchitecture"` - TotalThreads int `json:"TotalThreads"` - TotalCores int `json:"TotalCores"` - Model string `json:"Model"` -} diff --git a/cisco/s3260m5/thermal.go b/cisco/s3260m5/thermal.go deleted file mode 100644 index c9bb5ab..0000000 --- a/cisco/s3260m5/thermal.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package s3260m5 - -// /redfish/v1/Chassis/XXXX/Thermal/ - -// ThermalMetrics is the top level json object for UCS S3260 M5 Thermal metadata -type ThermalMetrics struct { - Status Status `json:"Status"` - Fans []Fan `json:"Fans,omitempty"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` - Url string `json:"@odata.id"` -} - -// Fan is the json object for a UCS S3260 M5 fan module -type Fan struct { - Name string `json:"Name"` - Reading int `json:"Reading"` - ReadingUnits string `json:"ReadingUnits"` - Status Status `json:"Status"` -} - -// Temperature is the json object for a UCS S3260 M5 temperature sensor module -type Temperature struct { - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius float64 `json:"ReadingCelsius"` - Status Status `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` -} diff --git a/hpe/dl20/drive.go b/hpe/dl20/drive.go deleted file mode 100644 index 9b9b484..0000000 --- a/hpe/dl20/drive.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl20 - -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1 - -// DriveMetrics is the top level json object for DL20 Drive metadata -type DriveMetrics struct { - ID string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status Status `json:"Status"` - StripeSizeBytes int `json:"StripeSizeBytes"` -} diff --git a/hpe/dl20/exporter.go b/hpe/dl20/exporter.go index 45b3e9e..0752fcc 100644 --- a/hpe/dl20/exporter.go +++ b/hpe/dl20/exporter.go @@ -22,9 +22,11 @@ import ( "encoding/json" "errors" "fmt" + "io" "net" "net/http" "net/url" + "path" "strconv" "strings" "sync" @@ -47,10 +49,18 @@ const ( THERMAL = "ThermalMetrics" // POWER represents the power metric endpoint POWER = "PowerMetrics" - // DRIVE represents the logical drive metric endpoints - DRIVE = "DriveMetrics" + // NVME represents the NVMe drive metric endpoint + NVME = "NVMeDriveMetrics" + // DISKDRIVE represents the Disk Drive metric endpoints + DISKDRIVE = "DiskDriveMetrics" + // LOGICALDRIVE represents the Logical drive metric endpoint + LOGICALDRIVE = "LogicalDriveMetrics" // MEMORY represents the memory metric endpoints MEMORY = "MemoryMetrics" + // MEMORY_SUMMARY represents the memory metric endpoints + MEMORY_SUMMARY = "MemorySummaryMetrics" + // FIRMWARE represents the firmware metric endpoints + FIRMWARE = "FirmwareMetrics" // OK is a string representation of the float 1.0 for device status OK = 1.0 // BAD is a string representation of the float 0.0 for device status @@ -66,12 +76,14 @@ var ( // Exporter collects chassis manager stats from the given URI and exports them using // the prometheus metrics package. type Exporter struct { - ctx context.Context - mutex sync.RWMutex - pool *pool.Pool - host string - credProfile string - deviceMetrics *map[string]*metrics + ctx context.Context + mutex sync.RWMutex + pool *pool.Pool + host string + credProfile string + biosVersion string + chassisSerialNumber string + deviceMetrics *map[string]*metrics } // NewExporter returns an initialized Exporter for HPE DL20 device. @@ -133,11 +145,180 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return &exp, nil } + // chassis system endpoint to use for memory, processor, bios version scrapes + sysEndpoint, err := getChassisEndpoint(fqdn.String()+uri+"/Managers/1/", target, retryClient) + if err != nil { + log.Error("error when getting chassis endpoint from "+DL20, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: DL20, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + exp.chassisSerialNumber = path.Base(sysEndpoint) + + // chassis BIOS version + biosVer, err := getBIOSVersion(fqdn.String()+sysEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting BIOS version from "+DL20, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + exp.biosVersion = biosVer + + // vars for drive parsing + var ( + initialURL = "/Systems/1/SmartStorage/ArrayControllers/" + url = initialURL + chassisUrl = "/Chassis/1/" + logicalDriveURLs []string + physicalDriveURLs []string + nvmeDriveURLs []string + ) + + // PARSING DRIVE ENDPOINTS + // Get initial JSON return of /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ set to output + driveResp, err := getDriveEndpoint(fqdn.String()+uri+url, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+uri+url+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: DL20, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + // Loop through Members to get ArrayController URLs + for _, member := range driveResp.Members { + // for each ArrayController URL, get the JSON object + // /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/ + arrayCtrlResp, err := getDriveEndpoint(fqdn.String()+member.URL, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+member.URL+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + // If LogicalDrives is present, parse logical drive endpoint until all urls are found + if arrayCtrlResp.LinksUpper.LogicalDrives.URL != "" { + logicalDriveOutput, err := getDriveEndpoint(fqdn.String()+arrayCtrlResp.LinksUpper.LogicalDrives.URL, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+arrayCtrlResp.LinksUpper.LogicalDrives.URL+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + // loop through each Member in the "LogicalDrive" field + for _, member := range logicalDriveOutput.Members { + // append each URL in the Members array to the logicalDriveURLs array. + logicalDriveURLs = append(logicalDriveURLs, member.URL) + } + } + + // If PhysicalDrives is present, parse physical drive endpoint until all urls are found + if arrayCtrlResp.LinksUpper.PhysicalDrives.URL != "" { + physicalDriveOutput, err := getDriveEndpoint(fqdn.String()+arrayCtrlResp.LinksUpper.PhysicalDrives.URL, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+arrayCtrlResp.LinksUpper.PhysicalDrives.URL+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + for _, member := range physicalDriveOutput.Members { + physicalDriveURLs = append(physicalDriveURLs, member.URL) + } + } + + // If LogicalDrives is present, parse logical drive endpoint until all urls are found + if arrayCtrlResp.LinksLower.LogicalDrives.URL != "" { + logicalDriveOutput, err := getDriveEndpoint(fqdn.String()+arrayCtrlResp.LinksLower.LogicalDrives.URL, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+arrayCtrlResp.LinksLower.LogicalDrives.URL+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + // loop through each Member in the "LogicalDrive" field + for _, member := range logicalDriveOutput.Members { + // append each URL in the Members array to the logicalDriveURLs array. + logicalDriveURLs = append(logicalDriveURLs, member.URL) + } + } + + // If PhysicalDrives is present, parse physical drive endpoint until all urls are found + if arrayCtrlResp.LinksLower.PhysicalDrives.URL != "" { + physicalDriveOutput, err := getDriveEndpoint(fqdn.String()+arrayCtrlResp.LinksLower.PhysicalDrives.URL, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+arrayCtrlResp.LinksLower.PhysicalDrives.URL+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + for _, member := range physicalDriveOutput.Members { + physicalDriveURLs = append(physicalDriveURLs, member.URL) + } + } + } + + // parse to find NVME drives + chassisOutput, err := getDriveEndpoint(fqdn.String()+uri+chassisUrl, target, retryClient) + if err != nil { + log.Error("api call "+fqdn.String()+uri+chassisUrl+" failed - ", zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + // parse through "Links" to find "Drives" array + // loop through drives array and append each odata.id url to nvmeDriveURLs list + for _, drive := range chassisOutput.LinksUpper.Drives { + nvmeDriveURLs = append(nvmeDriveURLs, drive.URL) + } + + // Loop through logicalDriveURLs, physicalDriveURLs, and nvmeDriveURLs and append each URL to the tasks pool + for _, url := range logicalDriveURLs { + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, LOGICALDRIVE, target, profile, retryClient))) + } + + for _, url := range physicalDriveURLs { + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, DISKDRIVE, target, profile, retryClient))) + } + + for _, url := range nvmeDriveURLs { + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, NVME, target, profile, retryClient))) + } + + // DIMM endpoints array + dimms, err := getDIMMEndpoints(fqdn.String()+sysEndpoint+"/Memory", target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints from "+DL20, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Managers/1", FIRMWARE, target, profile, retryClient))) + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Thermal", THERMAL, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Power", POWER, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1", DRIVE, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1", MEMORY, target, profile, retryClient))) + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY_SUMMARY, target, profile, retryClient))) + + // DIMMs + for _, dimm := range dimms.Members { + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+dimm.URL, MEMORY, target, profile, retryClient))) + } exp.pool = pool.NewPool(tasks, 1) @@ -222,14 +403,22 @@ func (e *Exporter) scrape() { } switch task.MetricType { + case FIRMWARE: + err = e.exportFirmwareMetrics(task.Body) case THERMAL: err = e.exportThermalMetrics(task.Body) case POWER: err = e.exportPowerMetrics(task.Body) - case DRIVE: - err = e.exportDriveMetrics(task.Body) + case NVME: + err = e.exportNVMeDriveMetrics(task.Body) + case DISKDRIVE: + err = e.exportPhysicalDriveMetrics(task.Body) + case LOGICALDRIVE: + err = e.exportLogicalDriveMetrics(task.Body) case MEMORY: err = e.exportMemoryMetrics(task.Body) + case MEMORY_SUMMARY: + err = e.exportMemorySummaryMetrics(task.Body) } if err != nil { @@ -252,43 +441,109 @@ func (e *Exporter) scrape() { } -// exportPowerMetrics collects the DL20's power metrics in json format and sets the prometheus gauges +// exportFirmwareMetrics collects the device metrics in json format and sets the prometheus gauges +func (e *Exporter) exportFirmwareMetrics(body []byte) error { + var chas oem.Chassis + var dm = (*e.deviceMetrics)["deviceInfo"] + err := json.Unmarshal(body, &chas) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL20 FirmwareMetrics - " + err.Error()) + } + + (*dm)["deviceInfo"].WithLabelValues(chas.Description, e.chassisSerialNumber, chas.FirmwareVersion, e.biosVersion, DL20).Set(1.0) + + return nil +} + +// exportPowerMetrics collects the power metrics in json format and sets the prometheus gauges func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 var pm oem.PowerMetrics - var dlPower = (*e.deviceMetrics)["powerMetrics"] + var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { return fmt.Errorf("Error Unmarshalling DL20 PowerMetrics - " + err.Error()) } - for _, pc := range pm.PowerControl { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerCapacityWatts)) + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: + // use the AverageConsumedWatts if PowerConsumedWatts is not present + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } + } + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) + } + + for _, pv := range pm.Voltages { + if pv.Status.State == "Enabled" { + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) + if pv.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) } for _, ps := range pm.PowerSupplies { if ps.Status.State == "Enabled" { - (*dlPower)["supplyOutput"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) if ps.Status.Health == "OK" { state = OK + } else if ps.Status.Health == "" { + state = OK } else { state = BAD } - (*dlPower)["supplyStatus"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(state) + } else { + state = BAD } + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) } return nil } -// exportThermalMetrics collects the DL20's thermal and fan metrics in json format and sets the prometheus gauges +// exportThermalMetrics collects the thermal and fan metrics in json format and sets the prometheus gauges func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 var tm oem.ThermalMetrics - var dlThermal = (*e.deviceMetrics)["thermalMetrics"] + var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling DL20 ThermalMetrics - " + err.Error()) @@ -305,13 +560,22 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { case float64: fanSpeed = fan.Reading.(float64) } - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(fanSpeed) + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) + } if fan.Status.Health == "OK" { state = OK } else { state = BAD } - (*dlThermal)["fanStatus"].WithLabelValues(fan.Name).Set(state) + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } } } @@ -328,32 +592,32 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { case float64: celsTemp = sensor.ReadingCelsius.(float64) } - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(celsTemp) + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { state = BAD } - (*dlThermal)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(state) + (*therm)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(state) } } return nil } -// exportDriveMetrics collects the DL20 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportDriveMetrics(body []byte) error { +// exportPhysicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { var state float64 - var dld DriveMetrics - var dlDrive = (*e.deviceMetrics)["driveMetrics"] - err := json.Unmarshal(body, &dld) + var dlphysical oem.DiskDriveMetrics + var dlphysicaldrive = (*e.deviceMetrics)["diskDriveMetrics"] + err := json.Unmarshal(body, &dlphysical) if err != nil { - return fmt.Errorf("Error Unmarshalling DL20 DriveMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL20 DiskDriveMetrics - " + err.Error()) } - // Check logical drive is enabled then check status and convert string to numeric values - if dld.Status.State == "Enabled" { - if dld.Status.Health == "OK" { + // Check physical drive is enabled then check status and convert string to numeric values + if dlphysical.Status.State == "Enabled" { + if dlphysical.Status.Health == "OK" { state = OK } else { state = BAD @@ -362,20 +626,70 @@ func (e *Exporter) exportDriveMetrics(body []byte) error { state = DISABLED } - (*dlDrive)["logicalDriveStatus"].WithLabelValues(dld.Name, strconv.Itoa(dld.LogicalDriveNumber), dld.Raid).Set(state) + // Physical drives need to have a unique identifier like location so as to not overwrite data + // physical drives can have the same ID, but belong to a different ArrayController, therefore need more than just the ID as a unique identifier. + (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, e.chassisSerialNumber, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) + return nil +} +// exportLogicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { + var state float64 + var dllogical oem.LogicalDriveMetrics + var dllogicaldrive = (*e.deviceMetrics)["logicalDriveMetrics"] + err := json.Unmarshal(body, &dllogical) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL20 LogicalDriveMetrics - " + err.Error()) + } + // Check physical drive is enabled then check status and convert string to numeric values + if dllogical.Status.State == "Enabled" { + if dllogical.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + } else { + state = DISABLED + } + + (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, e.chassisSerialNumber, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) return nil } -// exportMemoryMetrics collects the DL20 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportMemoryMetrics(body []byte) error { +// exportNVMeDriveMetrics collects the NVME drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { + var state float64 + var dlnvme oem.NVMeDriveMetrics + var dlnvmedrive = (*e.deviceMetrics)["nvmeMetrics"] + err := json.Unmarshal(body, &dlnvme) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL20 NVMeDriveMetrics - " + err.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" { + state = OK + } else { + state = BAD + } + } else { + state = DISABLED + } + + (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(e.chassisSerialNumber, dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) + return nil +} + +// exportMemorySummaryMetrics collects the memory summary metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { var state float64 - var dlm MemoryMetrics + var dlm oem.MemorySummaryMetrics var dlMemory = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &dlm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL20 MemoryMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL20 MemorySummaryMetrics - " + err.Error()) } // Check memory status and convert string to numeric values if dlm.MemorySummary.Status.HealthRollup == "OK" { @@ -384,7 +698,219 @@ func (e *Exporter) exportMemoryMetrics(body []byte) error { state = BAD } - (*dlMemory)["memoryStatus"].WithLabelValues(strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + (*dlMemory)["memoryStatus"].WithLabelValues(e.chassisSerialNumber, strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + + return nil +} + +// exportMemoryMetrics collects the memory dimm metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemoryMetrics(body []byte) error { + + var state float64 + var mm oem.MemoryMetrics + var mem = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &mm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL20 MemoryMetrics - " + err.Error()) + } + + if mm.Status != "" { + var memCap string + var status string + + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) + } + + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { + state = OK + } else { + state = BAD + } + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } + } + } + (*mem)["memoryDimmStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) + } return nil } + +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { + var chas oem.Chassis + var urlFinal string + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusUnauthorized { + return "", common.ErrInvalidCredential + } else { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &chas) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL20 Chassis struct - " + err.Error()) + } + + if len(chas.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + urlFinal = chas.Links.ManagerForServers.ServerManagerURLSlice[0] + } + + return urlFinal, nil +} + +func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { + var biosVer oem.ServerManager + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &biosVer) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL20 ServerManager struct - " + err.Error()) + } + + return biosVer.BiosVersion, nil +} + +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms 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 dimms, 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 dimms, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return dimms, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &dimms) + if err != nil { + return dimms, fmt.Errorf("Error Unmarshalling DL20 Memory Collection struct - " + err.Error()) + } + + return dimms, nil +} + +// The getDriveEndpoint function is used in a recursive fashion to get the body response +// of any type of drive, NVMe, Physical DiskDrives, or Logical Drives, using the GenericDrive struct +// This is used to find the final drive endpoints to append to the task pool for final scraping. +func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.GenericDrive, error) { + var drive oem.GenericDrive + var resp *http.Response + var err error + retryCount := 0 + req := common.BuildRequest(url, host) + resp, err = common.DoRequest(client, req) + if err != nil { + return drive, err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusNotFound { + for retryCount < 3 && resp.StatusCode == http.StatusNotFound { + time.Sleep(client.RetryWaitMin) + resp, err = common.DoRequest(client, req) + retryCount = retryCount + 1 + } + if err != nil { + return drive, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return drive, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else if resp.StatusCode == http.StatusUnauthorized { + return drive, common.ErrInvalidCredential + } else { + return drive, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return drive, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &drive) + if err != nil { + return drive, fmt.Errorf("Error Unmarshalling DL360 drive struct - " + err.Error()) + } + + return drive, nil +} diff --git a/hpe/dl20/memory.go b/hpe/dl20/memory.go deleted file mode 100644 index 8ac3445..0000000 --- a/hpe/dl20/memory.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl20 - -// /redfish/v1/systems/1/ - -// MemoryMetrics is the top level json object for DL20 Memory metadata -type MemoryMetrics struct { - ID string `json:"Id"` - MemorySummary MemorySummary `json:"MemorySummary"` -} - -// MemorySummary is the json object for DL20 MemorySummary metadata -type MemorySummary struct { - Status StatusMemory `json:"Status"` - TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` - TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` -} - -// StatusMemory is the variable to determine if the memory is OK or not -type StatusMemory struct { - HealthRollup string `json:"HealthRollup"` -} diff --git a/hpe/dl20/metrics.go b/hpe/dl20/metrics.go index 9608031..a915abf 100644 --- a/hpe/dl20/metrics.go +++ b/hpe/dl20/metrics.go @@ -40,33 +40,54 @@ func NewDeviceMetrics() *map[string]*metrics { } ThermalMetrics = &metrics{ - "fanSpeed": newServerMetric("dl20_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name"}), - "fanStatus": newServerMetric("dl20_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name"}), - "sensorTemperature": newServerMetric("dl20_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name"}), - "sensorStatus": newServerMetric("dl20_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name"}), + "fanSpeed": newServerMetric("dl20_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name", "chassisSerialNumber"}), + "fanStatus": newServerMetric("dl20_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "sensorTemperature": newServerMetric("dl20_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), + "sensorStatus": newServerMetric("dl20_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), } PowerMetrics = &metrics{ - "supplyOutput": newServerMetric("dl20_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl20_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "sparePartNumber"}), - "supplyTotalConsumed": newServerMetric("dl20_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId"}), - "supplyTotalCapacity": newServerMetric("dl20_power_supply_total_capacity", "Total output capacity of all the power supplies", nil, []string{"memberId"}), + "voltageOutput": newServerMetric("dl20_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), + "voltageStatus": newServerMetric("dl20_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "supplyOutput": newServerMetric("dl20_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyStatus": newServerMetric("dl20_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyTotalConsumed": newServerMetric("dl20_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } - DriveMetrics = &metrics{ - "logicalDriveStatus": newServerMetric("dl20_logical_drive_status", "Current logical drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "logicalDriveNumber", "raid"}), + // Splitting out the three different types of drives to gather metrics on each (NVMe, Disk Drive, and Logical Drive) + // NVMe Drive Metrics + NVMeDriveMetrics = &metrics{ + "nvmeDriveStatus": newServerMetric("dl20_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"chassisSerialNumber", "protocol", "id", "serviceLabel"}), + } + + // Phyiscal Storage Disk Drive Metrics + DiskDriveMetrics = &metrics{ + "driveStatus": newServerMetric("dl20_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "id", "location", "serialnumber"}), // DiskDriveStatus values + } + + // Logical Disk Drive Metrics + LogicalDriveMetrics = &metrics{ + "raidStatus": newServerMetric("dl20_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value } MemoryMetrics = &metrics{ - "memoryStatus": newServerMetric("dl20_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"totalSystemMemoryGiB"}), + "memoryStatus": newServerMetric("dl20_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"chassisSerialNumber", "totalSystemMemoryGiB"}), + "memoryDimmStatus": newServerMetric("dl20_memory_dimm_status", "Current dimm status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "capacityMiB", "manufacturer", "partNumber", "serialNumber"}), + } + + DeviceMetrics = &metrics{ + "deviceInfo": newServerMetric("device_info", "Current snapshot of device firmware information", nil, []string{"name", "chassisSerialNumber", "firmwareVersion", "biosVersion", "model"}), } Metrics = &map[string]*metrics{ - "up": UpMetric, - "thermalMetrics": ThermalMetrics, - "powerMetrics": PowerMetrics, - "driveMetrics": DriveMetrics, - "memoryMetrics": MemoryMetrics, + "up": UpMetric, + "thermalMetrics": ThermalMetrics, + "powerMetrics": PowerMetrics, + "nvmeMetrics": NVMeDriveMetrics, + "diskDriveMetrics": DiskDriveMetrics, + "logicalDriveMetrics": LogicalDriveMetrics, + "memoryMetrics": MemoryMetrics, + "deviceInfo": DeviceMetrics, } ) diff --git a/hpe/dl20/network_adapter.go b/hpe/dl20/network_adapter.go deleted file mode 100644 index 8bd9073..0000000 --- a/hpe/dl20/network_adapter.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl20 - -// /redfish/v1/Systems/1/BaseNetworkAdapters - -// NetworkAdapter is the top level json object for DL20 Network Adapter metadata -type NetworkAdapter struct { - ID string `json:"Id"` - Firmware Firmware `json:"Firmware"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` - SerialNumber string `json:"SerialNumber"` - StructuredName string `json:"StructuredName"` - Status Status `json:"Status"` - UEFIDevicePath string `json:"UEFIDevicePath"` -} - -// Firmware is the top level json object for DL20 Network Adapter metadata -type Firmware struct { - Current FirmwareCurrent `json:"Current"` -} - -// FirmwareCurrent contains the version in string format -type FirmwareCurrent struct { - Version string `json:"VersionString"` -} - -// PhysicalPorts contains the metadata for the Chassis NICs -type PhysicalPorts struct { - FullDuplex bool `json:"FullDuplex"` - IPv4Addresses []Addr `json:"IPv4Addresses"` - IPv6Addresses []Addr `json:"IPv6Addresses"` - LinkStatus string `json:"LinkStatus"` - MacAddress string `json:"MacAddress"` - Name string `json:"Name"` - SpeedMbps int `json:"SpeedMbps"` - Status Status `json:"Status"` -} - -// Addr contains the IPv4 or IPv6 Address in string format -type Addr struct { - Address string `json:"Address"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/hpe/dl20/power.go b/hpe/dl20/power.go deleted file mode 100644 index 6144762..0000000 --- a/hpe/dl20/power.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl20 - -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hpe Hpe `json:"Hpe"` -} - -// Hpe contains metadata on power supply product info -type Hpe struct { - AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` - Mismatched bool `json:"Mismatched"` - PowerSupplyStatus Status `json:"PowerSupplyStatus"` - IPDUCapable bool `json:"iPDUCapable"` -} diff --git a/hpe/dl20/thermal.go b/hpe/dl20/thermal.go deleted file mode 100644 index 0f2064f..0000000 --- a/hpe/dl20/thermal.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl20 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for DL20 Thermal metadata -type ThermalMetrics struct { - ID string `json:"Id"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` -} - -// Fan is the json object for a DL20 fan module -type Fan struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - Reading int `json:"Reading"` - ReadingUnits string `json:"ReadingUnits"` - Status Status `json:"Status"` -} - -// Temperature is the json object for a DL20 temperature sensor module -type Temperature struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius int `json:"ReadingCelsius"` - SensorNumber int `json:"SensorNumber"` - Status Status `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` - UpperThresholdFatal int `json:"UpperThresholdFatal"` -} diff --git a/hpe/dl360/drive.go b/hpe/dl360/drive.go deleted file mode 100644 index f8d1624..0000000 --- a/hpe/dl360/drive.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -// NVME's -// /redfish/v1/chassis/1/ -type NVMeDriveMetrics struct { - ID string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem Oem `json:"Oem"` - PhysicalLocation PhysicalLocation `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - Status DriveStatus `json:"Status"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` -} - -// Logical Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/LogicalDrives/X/ -type LogicalDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status DriveStatus `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` -} - -// Disk Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/DiskDrives/X/ -type DiskDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status DriveStatus `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` -} - -// NVME, Logical, and Physical Disk Drive Status -type DriveStatus struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} - -// GenericDrive is used to iterate over differing drive endpoints -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ for Logical and Physical Drives -// /redfish/v1/Chassis/1/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"` -} - -type Members struct { - URL string `json:"@odata.id"` -} - -type LinksUpper struct { - Drives []URL `json:"Drives,omitempty"` - LogicalDrives URL `json:"LogicalDrives,omitempty"` - PhysicalDrives URL `json:"PhysicalDrives,omitempty"` -} - -type LinksLower struct { - Drives []HRef `json:"Drives,omitempty"` - LogicalDrives HRef `json:"LogicalDrives,omitempty"` - PhysicalDrives HRef `json:"PhysicalDrives,omitempty"` -} - -type HRef struct { - URL string `json:"href"` -} - -type URL struct { - URL string `json:"@odata.id"` -} - -// PhysicalLocation -type PhysicalLocation struct { - PartLocation PartLocation `json:"PartLocation"` -} - -// PartLocation is a variable that determines the Box and the Bay location of the NVMe drive -type PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` -} - -// Contents of Oem -type Oem struct { - Hpe HpeCont `json:"Hpe"` -} - -// Contents of Hpe -type HpeCont struct { - CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` - DriveStatus DriveStatus `json:"DriveStatus"` - NVMeID string `json:"NVMeId"` -} diff --git a/hpe/dl360/drive_test.go b/hpe/dl360/drive_test.go deleted file mode 100644 index b8a0c1e..0000000 --- a/hpe/dl360/drive_test.go +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - GoodLogicalDriveUpperResponse = ` - # HELP dl360_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl360_logical_drive_status gauge - dl360_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 - ` - GoodDiskDriveUpperResponse = ` - # HELP dl360_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl360_disk_drive_status gauge - dl360_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 - ` - GoodLogicalDriveLowerResponse = ` - # HELP dl360_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl360_logical_drive_status gauge - dl360_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP dl360_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl360_disk_drive_status gauge - dl360_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` - # HELP dl360_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl360_nvme_drive_status gauge - dl360_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 - ` -) - -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - -func Test_DL360_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() - - ctx := context.Background() - assert := assert.New(t) - - logicalDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportLogicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - physDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportPhysicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - nvmeDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportNVMeDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - tests := []struct { - name string - uri string - metricName string - metricRef1 string - metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte - expected string - }{ - { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl360_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, - }, - { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl360_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl360_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl360_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, - }, - { - name: "Good Nvme Drive", - uri: "/redfish/v1/good", - metricName: "dl360_nvme_drive_status", - metricRef1: "nvmeMetrics", - metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) - - err = test.exportFunc(exporter.(*Exporter), test.payload) - if err != nil { - t.Error(err) - } - - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - - }) - } -} diff --git a/hpe/dl360/exporter.go b/hpe/dl360/exporter.go index 332c146..76f0bfe 100644 --- a/hpe/dl360/exporter.go +++ b/hpe/dl360/exporter.go @@ -26,6 +26,7 @@ import ( "net" "net/http" "net/url" + "path" "strconv" "strings" "sync" @@ -56,6 +57,10 @@ const ( LOGICALDRIVE = "LogicalDriveMetrics" // MEMORY represents the memory metric endpoints MEMORY = "MemoryMetrics" + // MEMORY_SUMMARY represents the memory metric endpoints + MEMORY_SUMMARY = "MemorySummaryMetrics" + // FIRMWARE represents the firmware metric endpoints + FIRMWARE = "FirmwareMetrics" // OK is a string representation of the float 1.0 for device status OK = 1.0 // BAD is a string representation of the float 0.0 for device status @@ -71,12 +76,14 @@ var ( // Exporter collects chassis manager stats from the given URI and exports them using // the prometheus metrics package. type Exporter struct { - ctx context.Context - mutex sync.RWMutex - pool *pool.Pool - host string - credProfile string - deviceMetrics *map[string]*metrics + ctx context.Context + mutex sync.RWMutex + pool *pool.Pool + host string + credProfile string + biosVersion string + chassisSerialNumber string + deviceMetrics *map[string]*metrics } // NewExporter returns an initialized Exporter for HPE DL360 device. @@ -138,6 +145,36 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return &exp, nil } + // chassis system endpoint to use for memory, processor, bios version scrapes + sysEndpoint, err := getChassisEndpoint(fqdn.String()+uri+"/Managers/1/", target, retryClient) + if err != nil { + log.Error("error when getting chassis endpoint from "+DL360, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: DL360, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + exp.chassisSerialNumber = path.Base(sysEndpoint) + + // chassis BIOS version + biosVer, err := getBIOSVersion(fqdn.String()+sysEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting BIOS version from "+DL360, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + exp.biosVersion = biosVer + // vars for drive parsing var ( initialURL = "/Systems/1/SmartStorage/ArrayControllers/" @@ -262,11 +299,27 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, NVME, target, profile, retryClient))) } + // DIMM endpoints array + dimms, err := getDIMMEndpoints(fqdn.String()+sysEndpoint+"/Memory", target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints from "+DL360, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Managers/1", FIRMWARE, target, profile, retryClient))) + // Additional tasks for pool to perform tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Thermal/", THERMAL, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Power/", POWER, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient))) + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY_SUMMARY, target, profile, retryClient))) + + // DIMMs + for _, dimm := range dimms.Members { + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+dimm.URL, MEMORY, target, profile, retryClient))) + } exp.pool = pool.NewPool(tasks, 1) @@ -351,6 +404,8 @@ func (e *Exporter) scrape() { } switch task.MetricType { + case FIRMWARE: + err = e.exportFirmwareMetrics(task.Body) case THERMAL: err = e.exportThermalMetrics(task.Body) case POWER: @@ -363,6 +418,8 @@ func (e *Exporter) scrape() { err = e.exportLogicalDriveMetrics(task.Body) case MEMORY: err = e.exportMemoryMetrics(task.Body) + case MEMORY_SUMMARY: + err = e.exportMemorySummaryMetrics(task.Body) } if err != nil { @@ -384,11 +441,175 @@ func (e *Exporter) scrape() { } -// exportPhysicalDriveMetrics collects the DL360's physical drive metrics in json format and sets the prometheus gauges +// exportFirmwareMetrics collects the device metrics in json format and sets the prometheus gauges +func (e *Exporter) exportFirmwareMetrics(body []byte) error { + var chas oem.Chassis + var dm = (*e.deviceMetrics)["deviceInfo"] + err := json.Unmarshal(body, &chas) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL360 FirmwareMetrics - " + err.Error()) + } + + (*dm)["deviceInfo"].WithLabelValues(chas.Description, e.chassisSerialNumber, chas.FirmwareVersion, e.biosVersion, DL360).Set(1.0) + + return nil +} + +// exportPowerMetrics collects the power metrics in json format and sets the prometheus gauges +func (e *Exporter) exportPowerMetrics(body []byte) error { + + var state float64 + var pm oem.PowerMetrics + var pow = (*e.deviceMetrics)["powerMetrics"] + err := json.Unmarshal(body, &pm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL360 PowerMetrics - " + err.Error()) + } + + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: + // use the AverageConsumedWatts if PowerConsumedWatts is not present + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } + } + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) + } + + for _, pv := range pm.Voltages { + if pv.Status.State == "Enabled" { + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) + if pv.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) + } + + for _, ps := range pm.PowerSupplies { + if ps.Status.State == "Enabled" { + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) + if ps.Status.Health == "OK" { + state = OK + } else if ps.Status.Health == "" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) + } + + return nil +} + +// exportThermalMetrics collects the thermal and fan metrics in json format and sets the prometheus gauges +func (e *Exporter) exportThermalMetrics(body []byte) error { + + var state float64 + var tm oem.ThermalMetrics + var therm = (*e.deviceMetrics)["thermalMetrics"] + err := json.Unmarshal(body, &tm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL360 ThermalMetrics - " + err.Error()) + } + + // Iterate through fans + for _, fan := range tm.Fans { + // Check fan status and convert string to numeric values + if fan.Status.State == "Enabled" { + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) + } + if fan.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } + } + } + + // Iterate through sensors + for _, sensor := range tm.Temperatures { + // Check sensor status and convert string to numeric values + if sensor.Status.State == "Enabled" { + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) + if sensor.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + (*therm)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(state) + } + } + + return nil +} + +// exportPhysicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { var state float64 - var dlphysical DiskDriveMetrics + var dlphysical oem.DiskDriveMetrics var dlphysicaldrive = (*e.deviceMetrics)["diskDriveMetrics"] err := json.Unmarshal(body, &dlphysical) if err != nil { @@ -407,14 +628,14 @@ func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { // Physical drives need to have a unique identifier like location so as to not overwrite data // physical drives can have the same ID, but belong to a different ArrayController, therefore need more than just the ID as a unique identifier. - (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) + (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, e.chassisSerialNumber, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) return nil } -// exportLogicalDriveMetrics collects the DL360's physical drive metrics in json format and sets the prometheus gauges +// exportLogicalDriveMetrics collects the logical drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { var state float64 - var dllogical LogicalDriveMetrics + var dllogical oem.LogicalDriveMetrics var dllogicaldrive = (*e.deviceMetrics)["logicalDriveMetrics"] err := json.Unmarshal(body, &dllogical) if err != nil { @@ -431,14 +652,14 @@ func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { state = DISABLED } - (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) + (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, e.chassisSerialNumber, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) return nil } // exportNVMeDriveMetrics collects the DL360 NVME drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { var state float64 - var dlnvme NVMeDriveMetrics + var dlnvme oem.NVMeDriveMetrics var dlnvmedrive = (*e.deviceMetrics)["nvmeMetrics"] err := json.Unmarshal(body, &dlnvme) if err != nil { @@ -456,143 +677,203 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { state = DISABLED } - (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) + (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(e.chassisSerialNumber, dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) return nil } -// exportPowerMetrics collects the DL360's power metrics in json format and sets the prometheus gauges -func (e *Exporter) exportPowerMetrics(body []byte) error { +// exportMemorySummaryMetrics collects the memory summary metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { var state float64 - var pm oem.PowerMetrics - var dlPower = (*e.deviceMetrics)["powerMetrics"] - err := json.Unmarshal(body, &pm) + var dlm oem.MemorySummaryMetrics + var dlMemory = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &dlm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL360 PowerMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL360 MemorySummaryMetrics - " + err.Error()) } - - for idx, pc := range pm.PowerControl { - if pc.MemberID != "" { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerCapacityWatts)) - } else { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerCapacityWatts)) - } + // Check memory status and convert string to numeric values + if dlm.MemorySummary.Status.HealthRollup == "OK" { + state = OK + } else { + state = BAD } - for _, ps := range pm.PowerSupplies { - if ps.Status.State == "Enabled" { - if ps.MemberID != "" { - (*dlPower)["supplyOutput"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) - } else if fmt.Sprint(ps.Oem.Hp.BayNumber) == "null" { - (*dlPower)["supplyOutput"].WithLabelValues(strconv.Itoa(ps.Oem.Hpe.BayNumber), ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) - } else { - (*dlPower)["supplyOutput"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) - } - if ps.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - if ps.MemberID != "" { - (*dlPower)["supplyStatus"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(state) - } else if fmt.Sprint(ps.Oem.Hp.BayNumber) == "null" { - (*dlPower)["supplyStatus"].WithLabelValues(strconv.Itoa(ps.Oem.Hpe.BayNumber), ps.SparePartNumber).Set(state) - } else { - (*dlPower)["supplyStatus"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(state) - } - } - } + (*dlMemory)["memoryStatus"].WithLabelValues(e.chassisSerialNumber, strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) return nil } -// exportThermalMetrics collects the DL360's thermal and fan metrics in json format and sets the prometheus gauges -func (e *Exporter) exportThermalMetrics(body []byte) error { +// exportMemoryMetrics collects the memory dimm metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemoryMetrics(body []byte) error { var state float64 - var tm oem.ThermalMetrics - var dlThermal = (*e.deviceMetrics)["thermalMetrics"] - err := json.Unmarshal(body, &tm) + var mm oem.MemoryMetrics + var mem = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &mm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL360 ThermalMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL360 MemoryMetrics - " + err.Error()) } - // Iterate through fans - for _, fan := range tm.Fans { - // Check fan status and convert string to numeric values - if fan.Status.State == "Enabled" { - var fanSpeed float64 - switch fan.Reading.(type) { - case string: - fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) - case float64: - fanSpeed = fan.Reading.(float64) - } + if mm.Status != "" { + var memCap string + var status string - if fan.FanName != "" { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.FanName).Set(float64(fan.CurrentReading)) - } else { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(fanSpeed) - } - if fan.Status.Health == "OK" { + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) + } + + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { state = OK } else { state = BAD } - if fan.FanName != "" { - (*dlThermal)["fanStatus"].WithLabelValues(fan.FanName).Set(state) - } else { - (*dlThermal)["fanStatus"].WithLabelValues(fan.Name).Set(state) + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } } } + (*mem)["memoryDimmStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) } - // Iterate through sensors - for _, sensor := range tm.Temperatures { - // Check sensor status and convert string to numeric values - if sensor.Status.State == "Enabled" { - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(float64(sensor.ReadingCelsius)) - if sensor.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*dlThermal)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(state) + return nil +} + +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { + var chas oem.Chassis + var urlFinal string + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusUnauthorized { + return "", common.ErrInvalidCredential + } else { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) } } - return nil + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &chas) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL360 Chassis struct - " + err.Error()) + } + + if len(chas.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + urlFinal = chas.Links.ManagerForServers.ServerManagerURLSlice[0] + } + + return urlFinal, nil } -// exportMemoryMetrics collects the DL360 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportMemoryMetrics(body []byte) error { +func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { + var biosVer oem.ServerManager + req := common.BuildRequest(url, host) - var state float64 - var dlm MemoryMetrics - var dlMemory = (*e.deviceMetrics)["memoryMetrics"] - err := json.Unmarshal(body, &dlm) + resp, err := client.Do(req) if err != nil { - return fmt.Errorf("Error Unmarshalling DL360 MemoryMetrics - " + err.Error()) + return "", err } - // Check memory status and convert string to numeric values - if dlm.MemorySummary.Status.HealthRollup == "OK" { - state = OK - } else { - state = BAD + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) } - (*dlMemory)["memoryStatus"].WithLabelValues(strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } - return nil + err = json.Unmarshal(body, &biosVer) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL360 ServerManager struct - " + err.Error()) + } + + return biosVer.BiosVersion, nil +} + +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms 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 dimms, 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 dimms, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return dimms, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &dimms) + if err != nil { + return dimms, fmt.Errorf("Error Unmarshalling DL360 Memory Collection struct - " + err.Error()) + } + + return dimms, nil } // The getDriveEndpoint function is used in a recursive fashion to get the body response // of any type of drive, NVMe, Physical DiskDrives, or Logical Drives, using the GenericDrive struct // This is used to find the final drive endpoints to append to the task pool for final scraping. -func getDriveEndpoint(url, host string, client *retryablehttp.Client) (GenericDrive, error) { - var drive GenericDrive +func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.GenericDrive, error) { + var drive oem.GenericDrive var resp *http.Response var err error retryCount := 0 diff --git a/hpe/dl360/exporter_test.go b/hpe/dl360/exporter_test.go index 4e69e46..f56cf1c 100644 --- a/hpe/dl360/exporter_test.go +++ b/hpe/dl360/exporter_test.go @@ -30,11 +30,26 @@ import ( ) const ( - up2Response = ` + up2Expected = ` # HELP up was the last scrape of fishymetrics successful. # TYPE up gauge up 2 ` + GoodLogicalDriveExpected = ` + # HELP dl360_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl360_logical_drive_status gauge + dl360_logical_drive_status{chassisSerialNumber="SN98765",logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + ` + GoodDiskDriveExpected = ` + # HELP dl360_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl360_disk_drive_status gauge + dl360_disk_drive_status{chassisSerialNumber="SN98765",id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + ` + GoodNvmeDriveExpected = ` + # HELP dl360_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl360_nvme_drive_status gauge + dl360_nvme_drive_status{chassisSerialNumber="SN98765",id="DA000000",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + ` ) type TestErrorResponse struct { @@ -61,7 +76,7 @@ func MustMarshal(v interface{}) []byte { func Test_DL360_Exporter(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/badcred/Systems/1/SmartStorage/ArrayControllers/" { + if r.URL.Path == "/redfish/v1/badcred/Managers/1/" { w.WriteHeader(http.StatusUnauthorized) w.Write(MustMarshal(TestErrorResponse{ Error: TestError{ @@ -99,7 +114,7 @@ func Test_DL360_Exporter(t *testing.T) { metricName: "up", metricRef1: "up", metricRef2: "up", - expected: up2Response, + expected: up2Expected, }, } @@ -123,3 +138,350 @@ func Test_DL360_Exporter(t *testing.T) { }) } } + +// Test_DL360_Upper_Lower_Links tests the uppercase and lowercase Links/links struct because of +// the different firmware versions of the redfish API +func Test_DL360_Upper_Lower_Links(t *testing.T) { + // server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // MembersCount int `json:"Members@odata.count"` + // Members []struct { + // URL string `json:"@odata.id"` + // } `json:"Members"` + // }{ + // MembersCount: 2, + // Members: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", + // }, + // { + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // LinksUpper struct { + // LogicalDrives struct { + // URL string `json:"@odata.id"` + // } `json:"LogicalDrives"` + // PhysicalDrives struct { + // URL string `json:"@odata.id"` + // } `json:"PhysicalDrives"` + // } `json:"Links"` + // }{ + // LinksUpper: struct { + // LogicalDrives struct { + // URL string `json:"@odata.id"` + // } `json:"LogicalDrives"` + // PhysicalDrives struct { + // URL string `json:"@odata.id"` + // } `json:"PhysicalDrives"` + // }{ + // LogicalDrives: struct { + // URL string `json:"@odata.id"` + // }{ + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", + // }, + // PhysicalDrives: struct { + // URL string `json:"@odata.id"` + // }{ + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // LinksLower struct { + // LogicalDrives struct { + // URL string `json:"href"` + // } `json:"LogicalDrives"` + // PhysicalDrives struct { + // URL string `json:"href"` + // } `json:"PhysicalDrives"` + // } `json:"links"` + // }{ + // LinksLower: struct { + // LogicalDrives struct { + // URL string `json:"href"` + // } `json:"LogicalDrives"` + // PhysicalDrives struct { + // URL string `json:"href"` + // } `json:"PhysicalDrives"` + // }{ + // LogicalDrives: struct { + // URL string `json:"href"` + // }{ + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", + // }, + // PhysicalDrives: struct { + // URL string `json:"href"` + // }{ + // URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // MembersCount int `json:"Members@odata.count"` + // Members []struct { + // URL string `json:"@odata.id"` + // } `json:"Members"` + // }{ + // MembersCount: 1, + // Members: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // MembersCount int `json:"Members@odata.count"` + // Members []struct { + // URL string `json:"@odata.id"` + // } `json:"Members"` + // }{ + // MembersCount: 1, + // Members: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // MembersCount int `json:"Members@odata.count"` + // Members []struct { + // URL string `json:"@odata.id"` + // } `json:"Members"` + // }{ + // MembersCount: 1, + // Members: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // MembersCount int `json:"Members@odata.count"` + // Members []struct { + // URL string `json:"@odata.id"` + // } `json:"Members"` + // }{ + // MembersCount: 1, + // Members: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", + // }, + // }, + // })) + // return + // } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { + // w.WriteHeader(http.StatusOK) + // w.Write(MustMarshal(struct { + // LinksUpper struct { + // Drives []struct { + // URL string `json:"@odata.id"` + // } `json:"Drives"` + // } `json:"Links"` + // }{ + // LinksUpper: struct { + // Drives []struct { + // URL string `json:"@odata.id"` + // } `json:"Drives"` + // }{ + // Drives: []struct { + // URL string `json:"@odata.id"` + // }{ + // { + // URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", + // }, + // }, + // }, + // })) + // return + // } + // w.WriteHeader(http.StatusInternalServerError) + // w.Write([]byte("Unknown path - please create test case(s) for it")) + // })) + // defer server.Close() +} + +func Test_DL360_Metrics_Handling(t *testing.T) { + + var GoodLogicalDriveResponse = []byte(`{ + "Id": "1", + "CapacityMiB": 915683, + "Description": "HPE Smart Storage Logical Drive View", + "InterfaceType": "SATA", + "LogicalDriveName": "TESTDRIVE NAME 1", + "LogicalDriveNumber": 1, + "Name": "HpeSmartStorageLogicalDrive", + "Raid": "1", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "StripeSizeBytes": 262144, + "VolumeUniqueIdentifier": "ABCDEF12345" + }`) + var GoodDiskDriveResponse = []byte(`{ + "Id": "0", + "CapacityMiB": 915715, + "Description": "HPE Smart Storage Disk Drive View", + "InterfaceType": "SATA", + "Location": "1I:1:1", + "Model": "MK000960GWXFH", + "Name": "HpeSmartStorageDiskDrive", + "SerialNumber": "ABC123", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }`) + var GoodNvmeDriveResponse = []byte(`{ + "Id": "DA000000", + "CapacityBytes": 1600321314816, + "FailurePredicted": false, + "MediaType": "SSD", + "Model": "MO001600KXPTR", + "Name": "Secondary Storage Device", + "Oem": { + "Hpe": { + "CurrentTemperatureCelsius": 33, + "DriveStatus": { + "Health": "OK", + "State": "Enabled" + }, + "NVMeId": "1c5c_MO001600KXPTR_KJC3N4902I2603P04" + } + }, + "PhysicalLocation": { + "PartLocation": { + "ServiceLabel": "Box 3:Bay 7" + } + }, + "Protocol": "NVMe" + }`) + + var exporter prometheus.Collector + + assert := assert.New(t) + + metrx := NewDeviceMetrics() + + exporter = &Exporter{ + ctx: context.Background(), + host: "fishymetrics.com", + biosVersion: "U99 v0.00 (xx/xx/xxxx)", + chassisSerialNumber: "SN98765", + deviceMetrics: metrx, + } + + prometheus.MustRegister(exporter) + + logicalDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportLogicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + physDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportPhysicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + nvmeDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportNVMeDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + tests := []struct { + name string + metricName string + metricRef1 string + metricRef2 string + handleFunc func(*Exporter, []byte) error + response []byte + expected string + }{ + { + name: "Good Logical Drive", + metricName: "dl360_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + handleFunc: logicalDevMetrics, + response: GoodLogicalDriveResponse, + expected: GoodLogicalDriveExpected, + }, + { + name: "Good Disk Drive", + metricName: "dl360_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + handleFunc: physDevMetrics, + response: GoodDiskDriveResponse, + expected: GoodDiskDriveExpected, + }, + { + name: "Good Nvme Drive", + metricName: "dl360_nvme_drive_status", + metricRef1: "nvmeMetrics", + metricRef2: "nvmeDriveStatus", + handleFunc: nvmeDevMetrics, + response: GoodNvmeDriveResponse, + expected: GoodNvmeDriveExpected, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.handleFunc(exporter.(*Exporter), test.response) + if err != nil { + t.Error(err) + } + + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + + assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) + + m.Reset() + }) + } + prometheus.Unregister(exporter) +} diff --git a/hpe/dl360/memory.go b/hpe/dl360/memory.go deleted file mode 100644 index edae25c..0000000 --- a/hpe/dl360/memory.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -// /redfish/v1/systems/1/ - -// MemoryMetrics is the top level json object for DL360 Memory metadata -type MemoryMetrics struct { - ID string `json:"Id"` - MemorySummary MemorySummary `json:"MemorySummary"` -} - -// MemorySummary is the json object for DL360 MemorySummary metadata -type MemorySummary struct { - Status StatusMemory `json:"Status"` - TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` - TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` -} - -// StatusMemory is the variable to determine if the memory is OK or not -type StatusMemory struct { - HealthRollup string `json:"HealthRollup"` -} diff --git a/hpe/dl360/metrics.go b/hpe/dl360/metrics.go index 86d3813..24d5d01 100644 --- a/hpe/dl360/metrics.go +++ b/hpe/dl360/metrics.go @@ -40,37 +40,43 @@ func NewDeviceMetrics() *map[string]*metrics { } ThermalMetrics = &metrics{ - "fanSpeed": newServerMetric("dl360_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name"}), - "fanStatus": newServerMetric("dl360_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name"}), - "sensorTemperature": newServerMetric("dl360_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name"}), - "sensorStatus": newServerMetric("dl360_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name"}), + "fanSpeed": newServerMetric("dl360_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name", "chassisSerialNumber"}), + "fanStatus": newServerMetric("dl360_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "sensorTemperature": newServerMetric("dl360_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), + "sensorStatus": newServerMetric("dl360_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), } PowerMetrics = &metrics{ - "supplyOutput": newServerMetric("dl360_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl360_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "sparePartNumber"}), - "supplyTotalConsumed": newServerMetric("dl360_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId"}), - "supplyTotalCapacity": newServerMetric("dl360_power_supply_total_capacity", "Total output capacity of all the power supplies", nil, []string{"memberId"}), + "voltageOutput": newServerMetric("dl360_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), + "voltageStatus": newServerMetric("dl360_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "supplyOutput": newServerMetric("dl360_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyStatus": newServerMetric("dl360_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyTotalConsumed": newServerMetric("dl360_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } // Splitting out the three different types of drives to gather metrics on each (NVMe, Disk Drive, and Logical Drive) // NVMe Drive Metrics NVMeDriveMetrics = &metrics{ - "nvmeDriveStatus": newServerMetric("dl360_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"protocol", "id", "serviceLabel"}), + "nvmeDriveStatus": newServerMetric("dl360_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"chassisSerialNumber", "protocol", "id", "serviceLabel"}), } // Phyiscal Storage Disk Drive Metrics DiskDriveMetrics = &metrics{ - "driveStatus": newServerMetric("dl360_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "id", "location", "serialnumber"}), // DiskDriveStatus values + "driveStatus": newServerMetric("dl360_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "id", "location", "serialnumber"}), // DiskDriveStatus values } // Logical Disk Drive Metrics LogicalDriveMetrics = &metrics{ - "raidStatus": newServerMetric("dl360_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value + "raidStatus": newServerMetric("dl360_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value } MemoryMetrics = &metrics{ - "memoryStatus": newServerMetric("dl360_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"totalSystemMemoryGiB"}), + "memoryStatus": newServerMetric("dl360_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"chassisSerialNumber", "totalSystemMemoryGiB"}), + "memoryDimmStatus": newServerMetric("dl360_memory_dimm_status", "Current dimm status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "capacityMiB", "manufacturer", "partNumber", "serialNumber"}), + } + + DeviceMetrics = &metrics{ + "deviceInfo": newServerMetric("device_info", "Current snapshot of device firmware information", nil, []string{"name", "chassisSerialNumber", "firmwareVersion", "biosVersion", "model"}), } Metrics = &map[string]*metrics{ @@ -81,6 +87,7 @@ func NewDeviceMetrics() *map[string]*metrics { "diskDriveMetrics": DiskDriveMetrics, "logicalDriveMetrics": LogicalDriveMetrics, "memoryMetrics": MemoryMetrics, + "deviceInfo": DeviceMetrics, } ) diff --git a/hpe/dl360/network_adapter.go b/hpe/dl360/network_adapter.go deleted file mode 100644 index 47bbfe4..0000000 --- a/hpe/dl360/network_adapter.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -// /redfish/v1/Systems/1/BaseNetworkAdapters - -// NetworkAdapter is the top level json object for DL360 Network Adapter metadata -type NetworkAdapter struct { - ID string `json:"Id"` - Firmware Firmware `json:"Firmware"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` - SerialNumber string `json:"SerialNumber"` - StructuredName string `json:"StructuredName"` - Status Status `json:"Status"` - UEFIDevicePath string `json:"UEFIDevicePath"` -} - -// Firmware is the top level json object for DL360 Network Adapter metadata -type Firmware struct { - Current FirmwareCurrent `json:"Current"` -} - -// FirmwareCurrent contains the version in string format -type FirmwareCurrent struct { - Version string `json:"VersionString"` -} - -// PhysicalPorts contains the metadata for the Chassis NICs -type PhysicalPorts struct { - FullDuplex bool `json:"FullDuplex"` - IPv4Addresses []Addr `json:"IPv4Addresses"` - IPv6Addresses []Addr `json:"IPv6Addresses"` - LinkStatus string `json:"LinkStatus"` - MacAddress string `json:"MacAddress"` - Name string `json:"Name"` - SpeedMbps int `json:"SpeedMbps"` - Status Status `json:"Status"` -} - -// Addr contains the IPv4 or IPv6 Address in string format -type Addr struct { - Address string `json:"Address"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/hpe/dl360/power.go b/hpe/dl360/power.go deleted file mode 100644 index 69ba953..0000000 --- a/hpe/dl360/power.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hpe Hpe `json:"Hpe,omitempty"` - Hp Hpe `json:"Hp,omitempty"` -} - -// Hpe contains metadata on power supply product info -type Hpe struct { - AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` - Mismatched bool `json:"Mismatched"` - PowerSupplyStatus Status `json:"PowerSupplyStatus"` - IPDUCapable bool `json:"iPDUCapable"` -} diff --git a/hpe/dl360/thermal.go b/hpe/dl360/thermal.go deleted file mode 100644 index d63fbd6..0000000 --- a/hpe/dl360/thermal.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl360 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for DL360 Thermal metadata -type ThermalMetrics struct { - ID string `json:"Id"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` -} - -// Fan is the json object for a DL360 fan module -type Fan struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - FanName string `json:"FanName"` - Reading int `json:"Reading"` - CurrentReading int `json:"CurrentReading"` - ReadingUnits string `json:"ReadingUnits"` - Status StatusThermal `json:"Status"` -} - -// StatusThermal is the variable to determine if a fan or temperature sensor module is OK or not -type StatusThermal struct { - Health string `json:"Health"` - State string `json:"State"` -} - -// Temperature is the json object for a DL360 temperature sensor module -type Temperature struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius int `json:"ReadingCelsius"` - SensorNumber int `json:"SensorNumber"` - Status StatusThermal `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` - UpperThresholdFatal int `json:"UpperThresholdFatal"` -} diff --git a/hpe/dl380/drive.go b/hpe/dl380/drive.go deleted file mode 100644 index 0fdc270..0000000 --- a/hpe/dl380/drive.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -// NVME's -// /redfish/v1/chassis/1/ -// NVMeMetrics is the top level json object for DL380 NVMe Metrics Metadata -type NVMeDriveMetrics struct { - ID string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem Oem `json:"Oem"` - PhysicalLocation PhysicalLocation `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - Status DriveStatus `json:"Status"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` -} - -// Logical Drives -// // /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/LogicalDrives/X/ -type LogicalDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status DriveStatus `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` -} - -// Disk Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/DiskDrives/X/ -type DiskDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status DriveStatus `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` -} - -// NVME, Logical, and Physical Disk Drive Status -type DriveStatus struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} - -// GenericDrive is used to iterate over differing drive endpoints -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ for Logical and Physical Drives -// /redfish/v1/Chassis/1/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"` -} - -type Members struct { - URL string `json:"@odata.id"` -} - -type LinksUpper struct { - Drives []URL `json:"Drives,omitempty"` - LogicalDrives URL `json:"LogicalDrives,omitempty"` - PhysicalDrives URL `json:"PhysicalDrives,omitempty"` -} - -type LinksLower struct { - Drives []HRef `json:"Drives,omitempty"` - LogicalDrives HRef `json:"LogicalDrives,omitempty"` - PhysicalDrives HRef `json:"PhysicalDrives,omitempty"` -} - -type HRef struct { - URL string `json:"href"` -} - -type URL struct { - URL string `json:"@odata.id"` -} - -// PhysicalLocation -type PhysicalLocation struct { - PartLocation PartLocation `json:"PartLocation"` -} - -// PartLocation is a variable that determines the Box and the Bay location of the NVMe drive -type PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` -} - -// Contents of Oem -type Oem struct { - Hpe HpeCont `json:"Hpe"` -} - -// Contents of Hpe -type HpeCont struct { - CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` - DriveStatus DriveStatus `json:"DriveStatus"` - NVMeID string `json:"NVMeId"` -} diff --git a/hpe/dl380/drive_test.go b/hpe/dl380/drive_test.go deleted file mode 100644 index a92a989..0000000 --- a/hpe/dl380/drive_test.go +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - GoodLogicalDriveUpperResponse = ` - # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_logical_drive_status gauge - dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 - ` - GoodDiskDriveUpperResponse = ` - # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_disk_drive_status gauge - dl380_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 - ` - GoodLogicalDriveLowerResponse = ` - # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_logical_drive_status gauge - dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_disk_drive_status gauge - dl380_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` - # HELP dl380_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_nvme_drive_status gauge - dl380_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 - ` -) - -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - -func Test_DL380_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() - - ctx := context.Background() - assert := assert.New(t) - - logicalDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportLogicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - physDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportPhysicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - nvmeDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportNVMeDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - tests := []struct { - name string - uri string - metricName string - metricRef1 string - metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte - expected string - }{ - { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl380_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, - }, - { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl380_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl380_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl380_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, - }, - { - name: "Good Nvme Drive", - uri: "/redfish/v1/good", - metricName: "dl380_nvme_drive_status", - metricRef1: "nvmeMetrics", - metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) - - err = test.exportFunc(exporter.(*Exporter), test.payload) - if err != nil { - t.Error(err) - } - - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - - }) - } -} diff --git a/hpe/dl380/exporter.go b/hpe/dl380/exporter.go index b6f4ae8..5ea9ed9 100644 --- a/hpe/dl380/exporter.go +++ b/hpe/dl380/exporter.go @@ -26,6 +26,7 @@ import ( "net" "net/http" "net/url" + "path" "strconv" "strings" "sync" @@ -56,6 +57,10 @@ const ( LOGICALDRIVE = "LogicalDriveMetrics" // MEMORY represents the memory metric endpoints MEMORY = "MemoryMetrics" + // MEMORY_SUMMARY represents the memory metric endpoints + MEMORY_SUMMARY = "MemorySummaryMetrics" + // FIRMWARE represents the firmware metric endpoints + FIRMWARE = "FirmwareMetrics" // OK is a string representation of the float 1.0 for device status OK = 1.0 // BAD is a string representation of the float 0.0 for device status @@ -71,12 +76,14 @@ var ( // Exporter collects chassis manager stats from the given URI and exports them using // the prometheus metrics package. type Exporter struct { - ctx context.Context - mutex sync.RWMutex - pool *pool.Pool - host string - credProfile string - deviceMetrics *map[string]*metrics + ctx context.Context + mutex sync.RWMutex + pool *pool.Pool + host string + credProfile string + biosVersion string + chassisSerialNumber string + deviceMetrics *map[string]*metrics } // NewExporter returns an initialized Exporter for HPE DL380 device. @@ -138,6 +145,36 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return &exp, nil } + // chassis system endpoint to use for memory, processor, bios version scrapes + sysEndpoint, err := getChassisEndpoint(fqdn.String()+uri+"/Managers/1/", target, retryClient) + if err != nil { + log.Error("error when getting chassis endpoint from "+DL380, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: DL380, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + exp.chassisSerialNumber = path.Base(sysEndpoint) + + // chassis BIOS version + biosVer, err := getBIOSVersion(fqdn.String()+sysEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting BIOS version from "+DL380, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + exp.biosVersion = biosVer + // vars for drive parsing var ( initialURL = "/Systems/1/SmartStorage/ArrayControllers/" @@ -260,11 +297,24 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, NVME, target, profile, retryClient))) } + // DIMM endpoints array + dimms, err := getDIMMEndpoints(fqdn.String()+sysEndpoint+"/Memory", target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints from "+DL380, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + // Additional tasks for pool to perform tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Thermal/", THERMAL, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Power/", POWER, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient))) + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY_SUMMARY, target, profile, retryClient))) + + // DIMMs + for _, dimm := range dimms.Members { + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+dimm.URL, MEMORY, target, profile, retryClient))) + } exp.pool = pool.NewPool(tasks, 1) @@ -349,6 +399,8 @@ func (e *Exporter) scrape() { } switch task.MetricType { + case FIRMWARE: + err = e.exportFirmwareMetrics(task.Body) case THERMAL: err = e.exportThermalMetrics(task.Body) case POWER: @@ -361,6 +413,8 @@ func (e *Exporter) scrape() { err = e.exportLogicalDriveMetrics(task.Body) case MEMORY: err = e.exportMemoryMetrics(task.Body) + case MEMORY_SUMMARY: + err = e.exportMemorySummaryMetrics(task.Body) } if err != nil { @@ -382,11 +436,175 @@ func (e *Exporter) scrape() { } -// exportPhysicalDriveMetrics collects the DL380's physical drive metrics in json format and sets the prometheus gauges +// exportFirmwareMetrics collects the device metrics in json format and sets the prometheus gauges +func (e *Exporter) exportFirmwareMetrics(body []byte) error { + var chas oem.Chassis + var dm = (*e.deviceMetrics)["deviceInfo"] + err := json.Unmarshal(body, &chas) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL380 FirmwareMetrics - " + err.Error()) + } + + (*dm)["deviceInfo"].WithLabelValues(chas.Description, e.chassisSerialNumber, chas.FirmwareVersion, e.biosVersion, DL380).Set(1.0) + + return nil +} + +// exportPowerMetrics collects the power metrics in json format and sets the prometheus gauges +func (e *Exporter) exportPowerMetrics(body []byte) error { + + var state float64 + var pm oem.PowerMetrics + var pow = (*e.deviceMetrics)["powerMetrics"] + err := json.Unmarshal(body, &pm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL380 PowerMetrics - " + err.Error()) + } + + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: + // use the AverageConsumedWatts if PowerConsumedWatts is not present + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } + } + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) + } + + for _, pv := range pm.Voltages { + if pv.Status.State == "Enabled" { + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) + if pv.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) + } + + for _, ps := range pm.PowerSupplies { + if ps.Status.State == "Enabled" { + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) + if ps.Status.Health == "OK" { + state = OK + } else if ps.Status.Health == "" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) + } + + return nil +} + +// exportThermalMetrics collects the thermal and fan metrics in json format and sets the prometheus gauges +func (e *Exporter) exportThermalMetrics(body []byte) error { + + var state float64 + var tm oem.ThermalMetrics + var therm = (*e.deviceMetrics)["thermalMetrics"] + err := json.Unmarshal(body, &tm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL380 ThermalMetrics - " + err.Error()) + } + + // Iterate through fans + for _, fan := range tm.Fans { + // Check fan status and convert string to numeric values + if fan.Status.State == "Enabled" { + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) + } + if fan.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } + } + } + + // Iterate through sensors + for _, sensor := range tm.Temperatures { + // Check sensor status and convert string to numeric values + if sensor.Status.State == "Enabled" { + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) + if sensor.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + (*therm)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(state) + } + } + + return nil +} + +// exportPhysicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { var state float64 - var dlphysical DiskDriveMetrics + var dlphysical oem.DiskDriveMetrics var dlphysicaldrive = (*e.deviceMetrics)["diskDriveMetrics"] err := json.Unmarshal(body, &dlphysical) if err != nil { @@ -405,14 +623,14 @@ func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { // Physical drives need to have a unique identifier like location so as to not overwrite data // physical drives can have the same ID, but belong to a different ArrayController, therefore need more than just the ID as a unique identifier. - (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) + (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, e.chassisSerialNumber, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) return nil } -// exportLogicalDriveMetrics collects the DL380's physical drive metrics in json format and sets the prometheus gauges +// exportLogicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { var state float64 - var dllogical LogicalDriveMetrics + var dllogical oem.LogicalDriveMetrics var dllogicaldrive = (*e.deviceMetrics)["logicalDriveMetrics"] err := json.Unmarshal(body, &dllogical) if err != nil { @@ -429,14 +647,14 @@ func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { state = DISABLED } - (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) + (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, e.chassisSerialNumber, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) return nil } // exportNVMeDriveMetrics collects the DL380 NVME drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { var state float64 - var dlnvme NVMeDriveMetrics + var dlnvme oem.NVMeDriveMetrics var dlnvmedrive = (*e.deviceMetrics)["nvmeMetrics"] err := json.Unmarshal(body, &dlnvme) if err != nil { @@ -454,117 +672,203 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { state = DISABLED } - (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) + (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(e.chassisSerialNumber, dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) return nil } -// exportPowerMetrics collects the DL380's power metrics in json format and sets the prometheus gauges -func (e *Exporter) exportPowerMetrics(body []byte) error { +// exportMemorySummaryMetrics collects the memory summary metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { var state float64 - var pm oem.PowerMetrics - var dlPower = (*e.deviceMetrics)["powerMetrics"] - err := json.Unmarshal(body, &pm) + var dlm oem.MemorySummaryMetrics + var dlMemory = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &dlm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL380 PowerMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL380 MemorySummaryMetrics - " + err.Error()) } - - for _, pc := range pm.PowerControl { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerCapacityWatts)) + // Check memory status and convert string to numeric values + if dlm.MemorySummary.Status.HealthRollup == "OK" { + state = OK + } else { + state = BAD } - for _, ps := range pm.PowerSupplies { - if ps.Status.State == "Enabled" { - (*dlPower)["supplyOutput"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) - if ps.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*dlPower)["supplyStatus"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(state) - } - } + (*dlMemory)["memoryStatus"].WithLabelValues(e.chassisSerialNumber, strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) return nil } -// exportThermalMetrics collects the DL380's thermal and fan metrics in json format and sets the prometheus gauges -func (e *Exporter) exportThermalMetrics(body []byte) error { +// exportMemoryMetrics collects the memory dimm metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemoryMetrics(body []byte) error { var state float64 - var tm oem.ThermalMetrics - var dlThermal = (*e.deviceMetrics)["thermalMetrics"] - err := json.Unmarshal(body, &tm) + var mm oem.MemoryMetrics + var mem = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &mm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL380 ThermalMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL380 MemoryMetrics - " + err.Error()) } - // Iterate through fans - for _, fan := range tm.Fans { - // Check fan status and convert string to numeric values - if fan.Status.State == "Enabled" { - var fanSpeed float64 - switch fan.Reading.(type) { - case string: - fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) - case float64: - fanSpeed = fan.Reading.(float64) - } - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(float64(fan.Reading)) - if fan.Status.Health == "OK" { - state = OK - } else { - state = BAD - } - (*dlThermal)["fanStatus"].WithLabelValues(fan.Name).Set(state) + if mm.Status != "" { + var memCap string + var status string + + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) } - } - // Iterate through sensors - for _, sensor := range tm.Temperatures { - // Check sensor status and convert string to numeric values - if sensor.Status.State == "Enabled" { - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(float64(sensor.ReadingCelsius)) - if sensor.Status.Health == "OK" { + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { state = OK } else { state = BAD } - (*dlThermal)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(state) + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } + } } + (*mem)["memoryDimmStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) } return nil } -// exportMemoryMetrics collects the DL380 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportMemoryMetrics(body []byte) error { +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { + var chas oem.Chassis + var urlFinal string + req := common.BuildRequest(url, host) - var state float64 - var dlm MemoryMetrics - var dlMemory = (*e.deviceMetrics)["memoryMetrics"] - err := json.Unmarshal(body, &dlm) + resp, err := client.Do(req) if err != nil { - return fmt.Errorf("Error Unmarshalling DL380 MemoryMetrics - " + err.Error()) + return "", err } - // Check memory status and convert string to numeric values - if dlm.MemorySummary.Status.HealthRollup == "OK" { - state = OK - } else { - state = BAD + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusUnauthorized { + return "", common.ErrInvalidCredential + } else { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } } - (*dlMemory)["memoryStatus"].WithLabelValues(strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } - return nil + err = json.Unmarshal(body, &chas) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL380 Chassis struct - " + err.Error()) + } + + if len(chas.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + urlFinal = chas.Links.ManagerForServers.ServerManagerURLSlice[0] + } + + return urlFinal, nil +} + +func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { + var biosVer oem.ServerManager + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &biosVer) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL380 ServerManager struct - " + err.Error()) + } + + return biosVer.BiosVersion, nil +} + +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms 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 dimms, 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 dimms, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return dimms, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &dimms) + if err != nil { + return dimms, fmt.Errorf("Error Unmarshalling DL380 Memory Collection struct - " + err.Error()) + } + + return dimms, nil } // The getDriveEndpoint function is used in a recursive fashion to get the body response // of any type of drive, NVMe, Physical DiskDrives, or Logical Drives, using the GenericDrive struct // This is used to find the final drive endpoints to append to the task pool for final scraping. -func getDriveEndpoint(url, host string, client *retryablehttp.Client) (GenericDrive, error) { - var drive GenericDrive +func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.GenericDrive, error) { + var drive oem.GenericDrive var resp *http.Response var err error retryCount := 0 diff --git a/hpe/dl380/exporter_test.go b/hpe/dl380/exporter_test.go index 84a331b..4808caf 100644 --- a/hpe/dl380/exporter_test.go +++ b/hpe/dl380/exporter_test.go @@ -35,6 +35,230 @@ const ( # TYPE up gauge up 2 ` + GoodLogicalDriveUpperResponse = ` + # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl380_logical_drive_status gauge + dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + ` + GoodDiskDriveUpperResponse = ` + # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl380_disk_drive_status gauge + dl380_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + ` + GoodLogicalDriveLowerResponse = ` + # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl380_logical_drive_status gauge + dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 + ` + GoodDiskDriveLowerResponse = ` + # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl380_disk_drive_status gauge + dl380_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 + ` + GoodNvmeDriveResponse = ` + # HELP dl380_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl380_nvme_drive_status gauge + dl380_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + ` +) + +var ( + GoodDiskDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "0", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:1", + SerialNumber: "ABC123", + }) + + GoodDiskDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "1", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:2", + SerialNumber: "DEF456", + }) + + GoodLogicalDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 1", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "ABCDEF12345", + }) + + GoodLogicalDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 2", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "FEDCBA12345", + }) + + GoodNvmeDrive = MustMarshal(struct { + Id string `json:"Id"` + Model string `json:"Model"` + Name string `json:"Name"` + MediaType string `json:"MediaType"` + Oem struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + } `json:"Oem"` + PhysicalLocation struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + } `json:"PhysicalLocation"` + Protocol string `json:"Protocol"` + FailurePredicted bool `json:"FailurePredicted"` + CapacityBytes int `json:"CapacityBytes"` + }{ + Id: "0", + Model: "TESTMODEL", + Name: "TESTNAME", + MediaType: "SSD", + Oem: struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + }{ + Hpe: struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + }{ + DriveStatus: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + }, + }, + PhysicalLocation: struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + }{ + PartLocation: struct { + ServiceLabel string `json:"ServiceLabel"` + }{ + ServiceLabel: "Box 3:Bay 7", + }, + }, + Protocol: "NVMe", + FailurePredicted: false, + CapacityBytes: 1600321314816, + }) ) type TestErrorResponse struct { @@ -123,3 +347,309 @@ func Test_DL380_Exporter(t *testing.T) { }) } } + +func Test_DL380_Drives(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 2, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", + }, + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + } `json:"Links"` + }{ + LinksUpper: struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksLower struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + } `json:"links"` + }{ + LinksLower: struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + } `json:"Links"` + }{ + LinksUpper: struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + }{ + Drives: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", + }, + }, + }, + })) + return + } + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Unknown path - please create test case(s) for it")) + })) + defer server.Close() + + ctx := context.Background() + assert := assert.New(t) + + logicalDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportLogicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + physDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportPhysicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + nvmeDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportNVMeDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + tests := []struct { + name string + uri string + metricName string + metricRef1 string + metricRef2 string + exportFunc func(*Exporter, []byte) error + payload []byte + expected string + }{ + { + name: "Good Logical Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "dl380_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveUpper, + expected: GoodLogicalDriveUpperResponse, + }, + { + name: "Good Logical Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "dl380_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveLower, + expected: GoodLogicalDriveLowerResponse, + }, + { + name: "Good Disk Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "dl380_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveUpper, + expected: GoodDiskDriveUpperResponse, + }, + { + name: "Good Disk Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "dl380_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveLower, + expected: GoodDiskDriveLowerResponse, + }, + { + name: "Good Nvme Drive", + uri: "/redfish/v1/good", + metricName: "dl380_nvme_drive_status", + metricRef1: "nvmeMetrics", + metricRef2: "nvmeDriveStatus", + exportFunc: nvmeDevMetrics, + payload: GoodNvmeDrive, + expected: GoodNvmeDriveResponse, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var exporter prometheus.Collector + var err error + exporter, err = NewExporter(ctx, server.URL, test.uri, "") + assert.Nil(err) + assert.NotNil(exporter) + + prometheus.MustRegister(exporter) + + err = test.exportFunc(exporter.(*Exporter), test.payload) + if err != nil { + t.Error(err) + } + + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + + assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) + + prometheus.Unregister(exporter) + + }) + } +} diff --git a/hpe/dl380/memory.go b/hpe/dl380/memory.go deleted file mode 100644 index ce443cf..0000000 --- a/hpe/dl380/memory.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -// /redfish/v1/systems/1/ - -// MemoryMetrics is the top level json object for DL380 Memory metadata -type MemoryMetrics struct { - ID string `json:"Id"` - MemorySummary MemorySummary `json:"MemorySummary"` -} - -// MemorySummary is the json object for DL380 MemorySummary metadata -type MemorySummary struct { - Status StatusMemory `json:"Status"` - TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` - TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` -} - -// StatusMemory is the variable to determine if the memory is OK or not -type StatusMemory struct { - HealthRollup string `json:"HealthRollup"` -} diff --git a/hpe/dl380/metrics.go b/hpe/dl380/metrics.go index 603f644..46011bc 100644 --- a/hpe/dl380/metrics.go +++ b/hpe/dl380/metrics.go @@ -40,37 +40,43 @@ func NewDeviceMetrics() *map[string]*metrics { } ThermalMetrics = &metrics{ - "fanSpeed": newServerMetric("dl380_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name"}), - "fanStatus": newServerMetric("dl380_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name"}), - "sensorTemperature": newServerMetric("dl380_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name"}), - "sensorStatus": newServerMetric("dl380_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name"}), + "fanSpeed": newServerMetric("dl380_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name", "chassisSerialNumber"}), + "fanStatus": newServerMetric("dl380_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "sensorTemperature": newServerMetric("dl380_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), + "sensorStatus": newServerMetric("dl380_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), } PowerMetrics = &metrics{ - "supplyOutput": newServerMetric("dl380_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl380_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "sparePartNumber"}), - "supplyTotalConsumed": newServerMetric("dl380_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId"}), - "supplyTotalCapacity": newServerMetric("dl380_power_supply_total_capacity", "Total output capacity of all the power supplies", nil, []string{"memberId"}), + "voltageOutput": newServerMetric("dl380_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), + "voltageStatus": newServerMetric("dl380_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "supplyOutput": newServerMetric("dl380_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyStatus": newServerMetric("dl380_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyTotalConsumed": newServerMetric("dl380_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } // Splitting out the three different types of drives to gather metrics on each (NVMe, Disk Drive, and Logical Drive) // NVMe Drive Metrics NVMeDriveMetrics = &metrics{ - "nvmeDriveStatus": newServerMetric("dl380_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"protocol", "id", "serviceLabel"}), + "nvmeDriveStatus": newServerMetric("dl380_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"chassisSerialNumber", "protocol", "id", "serviceLabel"}), } // Phyiscal Storage Disk Drive Metrics DiskDriveMetrics = &metrics{ - "driveStatus": newServerMetric("dl380_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "id", "location", "serialnumber"}), // DiskDriveStatus values + "driveStatus": newServerMetric("dl380_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "id", "location", "serialnumber"}), // DiskDriveStatus values } // Logical Disk Drive Metrics LogicalDriveMetrics = &metrics{ - "raidStatus": newServerMetric("dl380_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value + "raidStatus": newServerMetric("dl380_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value } MemoryMetrics = &metrics{ - "memoryStatus": newServerMetric("dl380_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"totalSystemMemoryGiB"}), + "memoryStatus": newServerMetric("dl380_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"chassisSerialNumber", "totalSystemMemoryGiB"}), + "memoryDimmStatus": newServerMetric("dl380_memory_dimm_status", "Current dimm status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "capacityMiB", "manufacturer", "partNumber", "serialNumber"}), + } + + DeviceMetrics = &metrics{ + "deviceInfo": newServerMetric("device_info", "Current snapshot of device firmware information", nil, []string{"name", "chassisSerialNumber", "firmwareVersion", "biosVersion", "model"}), } Metrics = &map[string]*metrics{ @@ -81,6 +87,7 @@ func NewDeviceMetrics() *map[string]*metrics { "diskDriveMetrics": DiskDriveMetrics, "logicalDriveMetrics": LogicalDriveMetrics, "memoryMetrics": MemoryMetrics, + "deviceInfo": DeviceMetrics, } ) return Metrics diff --git a/hpe/dl380/network_adapter.go b/hpe/dl380/network_adapter.go deleted file mode 100644 index ea778ac..0000000 --- a/hpe/dl380/network_adapter.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -// /redfish/v1/Systems/1/BaseNetworkAdapters - -// NetworkAdapter is the top level json object for DL380 Network Adapter metadata -type NetworkAdapter struct { - ID string `json:"Id"` - Firmware Firmware `json:"Firmware"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` - SerialNumber string `json:"SerialNumber"` - StructuredName string `json:"StructuredName"` - Status Status `json:"Status"` - UEFIDevicePath string `json:"UEFIDevicePath"` -} - -// Firmware is the top level json object for DL380 Network Adapter metadata -type Firmware struct { - Current FirmwareCurrent `json:"Current"` -} - -// FirmwareCurrent contains the version in string format -type FirmwareCurrent struct { - Version string `json:"VersionString"` -} - -// PhysicalPorts contains the metadata for the Chassis NICs -type PhysicalPorts struct { - FullDuplex bool `json:"FullDuplex"` - IPv4Addresses []Addr `json:"IPv4Addresses"` - IPv6Addresses []Addr `json:"IPv6Addresses"` - LinkStatus string `json:"LinkStatus"` - MacAddress string `json:"MacAddress"` - Name string `json:"Name"` - SpeedMbps int `json:"SpeedMbps"` - Status Status `json:"Status"` -} - -// Addr contains the IPv4 or IPv6 Address in string format -type Addr struct { - Address string `json:"Address"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/hpe/dl380/power.go b/hpe/dl380/power.go deleted file mode 100644 index 3d6b343..0000000 --- a/hpe/dl380/power.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hpe Hpe `json:"Hpe"` -} - -// Hpe contains metadata on power supply product info -type Hpe struct { - AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` - Mismatched bool `json:"Mismatched"` - PowerSupplyStatus Status `json:"PowerSupplyStatus"` - IPDUCapable bool `json:"iPDUCapable"` -} diff --git a/hpe/dl380/thermal.go b/hpe/dl380/thermal.go deleted file mode 100644 index ff1eb86..0000000 --- a/hpe/dl380/thermal.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl380 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for DL380 Thermal metadata -type ThermalMetrics struct { - ID string `json:"Id"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` -} - -// Fan is the json object for a DL380 fan module -type Fan struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - Reading int `json:"Reading"` - ReadingUnits string `json:"ReadingUnits"` - Status StatusThermal `json:"Status"` -} - -// StatusThermal is the variable to determine if a fan or temperature sensor module is OK or not -type StatusThermal struct { - Health string `json:"Health"` - State string `json:"State"` -} - -// Temperature is the json object for a DL380 temperature sensor module -type Temperature struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius int `json:"ReadingCelsius"` - SensorNumber int `json:"SensorNumber"` - Status StatusThermal `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` - UpperThresholdFatal int `json:"UpperThresholdFatal"` -} diff --git a/hpe/dl560/drive.go b/hpe/dl560/drive.go deleted file mode 100644 index 6302479..0000000 --- a/hpe/dl560/drive.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ - -// NVME's -// /redfish/v1/chassis/1/ -// NVMeMetrics is the top level json object for DL380 NVMe Metrics Metadata -type NVMeDriveMetrics struct { - ID string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem Oem `json:"Oem"` - PhysicalLocation PhysicalLocation `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - Status Status `json:"Status"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` -} - -// Logical Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/LogicalDrives/X/ -type LogicalDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status Status `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` -} - -// Disk Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/DiskDrives/X/ -type DiskDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status Status `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` -} - -// GenericDrive is used to iterate over differing drive endpoints -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ for Logical and Physical Drives -// /redfish/v1/Chassis/1/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"` -} - -type Members struct { - URL string `json:"@odata.id"` -} - -type LinksUpper struct { - Drives []URL `json:"Drives,omitempty"` - LogicalDrives URL `json:"LogicalDrives,omitempty"` - PhysicalDrives URL `json:"PhysicalDrives,omitempty"` -} - -type LinksLower struct { - Drives []HRef `json:"Drives,omitempty"` - LogicalDrives HRef `json:"LogicalDrives,omitempty"` - PhysicalDrives HRef `json:"PhysicalDrives,omitempty"` -} - -type HRef struct { - URL string `json:"href"` -} - -type URL struct { - URL string `json:"@odata.id"` -} - -// PhysicalLocation -type PhysicalLocation struct { - PartLocation PartLocation `json:"PartLocation"` -} - -// PartLocation is a variable that determines the Box and the Bay location of the NVMe drive -type PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` -} - -// Contents of Oem -type Oem struct { - Hpe HpeCont `json:"Hpe"` -} - -// Contents of Hpe -type HpeCont struct { - CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` - DriveStatus Status `json:"DriveStatus"` - NVMeID string `json:"NVMeId"` -} diff --git a/hpe/dl560/drive_test.go b/hpe/dl560/drive_test.go deleted file mode 100644 index 75d8e08..0000000 --- a/hpe/dl560/drive_test.go +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - GoodLogicalDriveUpperResponse = ` - # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_logical_drive_status gauge - dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 - ` - GoodDiskDriveUpperResponse = ` - # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_disk_drive_status gauge - dl560_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 - ` - GoodLogicalDriveLowerResponse = ` - # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_logical_drive_status gauge - dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_disk_drive_status gauge - dl560_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` - # HELP dl560_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_nvme_drive_status gauge - dl560_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 - ` -) - -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - -func Test_DL560_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() - - ctx := context.Background() - assert := assert.New(t) - - logicalDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportLogicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - physDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportPhysicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - nvmeDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportNVMeDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - tests := []struct { - name string - uri string - metricName string - metricRef1 string - metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte - expected string - }{ - { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl560_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, - }, - { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl560_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl560_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl560_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, - }, - { - name: "Good Nvme Drive", - uri: "/redfish/v1/good", - metricName: "dl560_nvme_drive_status", - metricRef1: "nvmeMetrics", - metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) - - err = test.exportFunc(exporter.(*Exporter), test.payload) - if err != nil { - t.Error(err) - } - - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - - }) - } -} diff --git a/hpe/dl560/exporter.go b/hpe/dl560/exporter.go index 9302f4f..791d35c 100644 --- a/hpe/dl560/exporter.go +++ b/hpe/dl560/exporter.go @@ -26,6 +26,7 @@ import ( "net" "net/http" "net/url" + "path" "strconv" "strings" "sync" @@ -56,6 +57,10 @@ const ( LOGICALDRIVE = "LogicalDriveMetrics" // MEMORY represents the memory metric endpoints MEMORY = "MemoryMetrics" + // MEMORY_SUMMARY represents the memory metric endpoints + MEMORY_SUMMARY = "MemorySummaryMetrics" + // FIRMWARE represents the firmware metric endpoints + FIRMWARE = "FirmwareMetrics" // OK is a string representation of the float 1.0 for device status OK = 1.0 // BAD is a string representation of the float 0.0 for device status @@ -71,12 +76,14 @@ var ( // Exporter collects chassis manager stats from the given URI and exports them using // the prometheus metrics package. type Exporter struct { - ctx context.Context - mutex sync.RWMutex - pool *pool.Pool - host string - credProfile string - deviceMetrics *map[string]*metrics + ctx context.Context + mutex sync.RWMutex + pool *pool.Pool + host string + credProfile string + biosVersion string + chassisSerialNumber string + deviceMetrics *map[string]*metrics } // NewExporter returns an initialized Exporter for HPE DL560 device. @@ -138,6 +145,36 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return &exp, nil } + // chassis system endpoint to use for memory, processor, bios version scrapes + sysEndpoint, err := getChassisEndpoint(fqdn.String()+uri+"/Managers/1/", target, retryClient) + if err != nil { + log.Error("error when getting chassis endpoint from "+DL560, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: DL560, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + exp.chassisSerialNumber = path.Base(sysEndpoint) + + // chassis BIOS version + biosVer, err := getBIOSVersion(fqdn.String()+sysEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting BIOS version from "+DL560, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + exp.biosVersion = biosVer + // vars for drive parsing var ( initialURL = "/Systems/1/SmartStorage/ArrayControllers/" @@ -253,12 +290,25 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, NVME, target, profile, retryClient))) } + // DIMM endpoints array + dimms, err := getDIMMEndpoints(fqdn.String()+sysEndpoint+"/Memory", target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints from "+DL560, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Thermal/", THERMAL, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Power/", POWER, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient)), + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY_SUMMARY, target, profile, retryClient)), ) + // DIMMs + for _, dimm := range dimms.Members { + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+dimm.URL, MEMORY, target, profile, retryClient))) + } + exp.pool = pool.NewPool(tasks, 1) return &exp, nil @@ -342,6 +392,8 @@ func (e *Exporter) scrape() { } switch task.MetricType { + case FIRMWARE: + err = e.exportFirmwareMetrics(task.Body) case THERMAL: err = e.exportThermalMetrics(task.Body) case POWER: @@ -354,6 +406,8 @@ func (e *Exporter) scrape() { err = e.exportLogicalDriveMetrics(task.Body) case MEMORY: err = e.exportMemoryMetrics(task.Body) + case MEMORY_SUMMARY: + err = e.exportMemorySummaryMetrics(task.Body) } if err != nil { @@ -375,43 +429,109 @@ func (e *Exporter) scrape() { } -// exportPowerMetrics collects the DL560's power metrics in json format and sets the prometheus gauges +// exportFirmwareMetrics collects the device metrics in json format and sets the prometheus gauges +func (e *Exporter) exportFirmwareMetrics(body []byte) error { + var chas oem.Chassis + var dm = (*e.deviceMetrics)["deviceInfo"] + err := json.Unmarshal(body, &chas) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL560 FirmwareMetrics - " + err.Error()) + } + + (*dm)["deviceInfo"].WithLabelValues(chas.Description, e.chassisSerialNumber, chas.FirmwareVersion, e.biosVersion, DL560).Set(1.0) + + return nil +} + +// exportPowerMetrics collects the power metrics in json format and sets the prometheus gauges func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 var pm oem.PowerMetrics - var dlPower = (*e.deviceMetrics)["powerMetrics"] + var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { return fmt.Errorf("Error Unmarshalling DL560 PowerMetrics - " + err.Error()) } - for idx, pc := range pm.PowerControl { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerCapacityWatts)) + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: + // use the AverageConsumedWatts if PowerConsumedWatts is not present + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } + } + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) + } + + for _, pv := range pm.Voltages { + if pv.Status.State == "Enabled" { + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) + if pv.Status.Health == "OK" { + state = OK + } else { + state = BAD + } + } else { + state = BAD + } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) } for _, ps := range pm.PowerSupplies { if ps.Status.State == "Enabled" { - (*dlPower)["supplyOutput"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) + } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) if ps.Status.Health == "OK" { state = OK + } else if ps.Status.Health == "" { + state = OK } else { state = BAD } - (*dlPower)["supplyStatus"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(state) + } else { + state = BAD } + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) } return nil } -// exportThermalMetrics collects the DL560's thermal and fan metrics in json format and sets the prometheus gauges +// exportThermalMetrics collects the thermal and fan metrics in json format and sets the prometheus gauges func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 var tm oem.ThermalMetrics - var dlThermal = (*e.deviceMetrics)["thermalMetrics"] + var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling DL560 ThermalMetrics - " + err.Error()) @@ -421,13 +541,29 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.FanName).Set(float64(fan.CurrentReading)) + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + + if fan.FanName != "" { + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) + } else { + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) + } if fan.Status.Health == "OK" { state = OK } else { state = BAD } - (*dlThermal)["fanStatus"].WithLabelValues(fan.FanName).Set(state) + if fan.FanName != "" { + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) + } else { + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) + } } } @@ -435,32 +571,41 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(float64(sensor.ReadingCelsius)) + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { state = BAD } - (*dlThermal)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(state) + (*therm)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(state) } } return nil } -// exportLogicalDriveMetrics collects the DL560 logical drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { +// exportPhysicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { var state float64 - var dld LogicalDriveMetrics - var dlDrive = (*e.deviceMetrics)["logicalDriveMetrics"] - err := json.Unmarshal(body, &dld) + var dlphysical oem.DiskDriveMetrics + var dlphysicaldrive = (*e.deviceMetrics)["diskDriveMetrics"] + err := json.Unmarshal(body, &dlphysical) if err != nil { - return fmt.Errorf("Error Unmarshalling DL560 LogicalDriveMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL560 DiskDriveMetrics - " + err.Error()) } - // Check logical drive is enabled then check status and convert string to numeric values - if dld.Status.State == "Enabled" { - if dld.Status.Health == "OK" { + // Check physical drive is enabled then check status and convert string to numeric values + if dlphysical.Status.State == "Enabled" { + if dlphysical.Status.Health == "OK" { state = OK } else { state = BAD @@ -469,24 +614,24 @@ func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { state = DISABLED } - (*dlDrive)["raidStatus"].WithLabelValues(dld.Name, dld.LogicalDriveName, dld.VolumeUniqueIdentifier, dld.Raid).Set(state) - + // Physical drives need to have a unique identifier like location so as to not overwrite data + // physical drives can have the same ID, but belong to a different ArrayController, therefore need more than just the ID as a unique identifier. + (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, e.chassisSerialNumber, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) return nil } -// exportPhysicalDriveMetrics collects the DL560 physical drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { - +// exportLogicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { var state float64 - var dpd DiskDriveMetrics - var dpDrive = (*e.deviceMetrics)["diskDriveMetrics"] - err := json.Unmarshal(body, &dpd) + var dllogical oem.LogicalDriveMetrics + var dllogicaldrive = (*e.deviceMetrics)["logicalDriveMetrics"] + err := json.Unmarshal(body, &dllogical) if err != nil { - return fmt.Errorf("Error Unmarshalling DL560 PhysicalDriveMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL560 LogicalDriveMetrics - " + err.Error()) } - // Check physical drive is enabled then check status - if dpd.Status.State == "Enabled" { - if dpd.Status.Health == "OK" { + // Check physical drive is enabled then check status and convert string to numeric values + if dllogical.Status.State == "Enabled" { + if dllogical.Status.Health == "OK" { state = OK } else { state = BAD @@ -495,15 +640,14 @@ func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { state = DISABLED } - (*dpDrive)["driveStatus"].WithLabelValues(dpd.Name, dpd.Id, dpd.Location, dpd.SerialNumber).Set(state) - + (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, e.chassisSerialNumber, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) return nil } -// exportNVMeDriveMetrics collects the DL380 NVME drive metrics in json format and sets the prometheus gauges +// exportNVMeDriveMetrics collects the NVME drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { var state float64 - var dlnvme NVMeDriveMetrics + var dlnvme oem.NVMeDriveMetrics var dlnvmedrive = (*e.deviceMetrics)["nvmeMetrics"] err := json.Unmarshal(body, &dlnvme) if err != nil { @@ -521,19 +665,19 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { state = DISABLED } - (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) + (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(e.chassisSerialNumber, dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) return nil } -// exportMemoryMetrics collects the DL560 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportMemoryMetrics(body []byte) error { +// exportMemorySummaryMetrics collects the memory summary metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { var state float64 - var dlm MemoryMetrics + var dlm oem.MemorySummaryMetrics var dlMemory = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &dlm) if err != nil { - return fmt.Errorf("Error Unmarshalling DL560 MemoryMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling DL560 MemorySummaryMetrics - " + err.Error()) } // Check memory status and convert string to numeric values if dlm.MemorySummary.Status.HealthRollup == "OK" { @@ -542,16 +686,182 @@ func (e *Exporter) exportMemoryMetrics(body []byte) error { state = BAD } - (*dlMemory)["memoryStatus"].WithLabelValues(strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + (*dlMemory)["memoryStatus"].WithLabelValues(e.chassisSerialNumber, strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + + return nil +} + +// exportMemoryMetrics collects the memory dimm metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemoryMetrics(body []byte) error { + + var state float64 + var mm oem.MemoryMetrics + var mem = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &mm) + if err != nil { + return fmt.Errorf("Error Unmarshalling DL560 MemoryMetrics - " + err.Error()) + } + + if mm.Status != "" { + var memCap string + var status string + + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) + } + + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { + state = OK + } else { + state = BAD + } + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } + } + } + (*mem)["memoryDimmStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) + } return nil } +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { + var chas oem.Chassis + var urlFinal string + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusUnauthorized { + return "", common.ErrInvalidCredential + } else { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &chas) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL560 Chassis struct - " + err.Error()) + } + + if len(chas.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + urlFinal = chas.Links.ManagerForServers.ServerManagerURLSlice[0] + } + + return urlFinal, nil +} + +func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { + var biosVer oem.ServerManager + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &biosVer) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling DL560 ServerManager struct - " + err.Error()) + } + + return biosVer.BiosVersion, nil +} + +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms 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 dimms, 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 dimms, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return dimms, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &dimms) + if err != nil { + return dimms, fmt.Errorf("Error Unmarshalling DL560 Memory Collection struct - " + err.Error()) + } + + return dimms, nil +} + // The getDriveEndpoint function is used in a recursive fashion to get the body response // of any type of drive, NVMe, Physical DiskDrives, or Logical Drives, using the GenericDrive struct // This is used to find the final drive endpoints to append to the task pool for final scraping. -func getDriveEndpoint(url, host string, client *retryablehttp.Client) (GenericDrive, error) { - var drive GenericDrive +func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.GenericDrive, error) { + var drive oem.GenericDrive var resp *http.Response var err error retryCount := 0 diff --git a/hpe/dl560/exporter_test.go b/hpe/dl560/exporter_test.go index 8dd2298..c74bd47 100644 --- a/hpe/dl560/exporter_test.go +++ b/hpe/dl560/exporter_test.go @@ -35,6 +35,230 @@ const ( # TYPE up gauge up 2 ` + GoodLogicalDriveUpperResponse = ` + # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl560_logical_drive_status gauge + dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + ` + GoodDiskDriveUpperResponse = ` + # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl560_disk_drive_status gauge + dl560_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + ` + GoodLogicalDriveLowerResponse = ` + # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl560_logical_drive_status gauge + dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 + ` + GoodDiskDriveLowerResponse = ` + # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl560_disk_drive_status gauge + dl560_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 + ` + GoodNvmeDriveResponse = ` + # HELP dl560_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE dl560_nvme_drive_status gauge + dl560_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + ` +) + +var ( + GoodDiskDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "0", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:1", + SerialNumber: "ABC123", + }) + + GoodDiskDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "1", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:2", + SerialNumber: "DEF456", + }) + + GoodLogicalDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 1", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "ABCDEF12345", + }) + + GoodLogicalDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 2", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "FEDCBA12345", + }) + + GoodNvmeDrive = MustMarshal(struct { + Id string `json:"Id"` + Model string `json:"Model"` + Name string `json:"Name"` + MediaType string `json:"MediaType"` + Oem struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + } `json:"Oem"` + PhysicalLocation struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + } `json:"PhysicalLocation"` + Protocol string `json:"Protocol"` + FailurePredicted bool `json:"FailurePredicted"` + CapacityBytes int `json:"CapacityBytes"` + }{ + Id: "0", + Model: "TESTMODEL", + Name: "TESTNAME", + MediaType: "SSD", + Oem: struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + }{ + Hpe: struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + }{ + DriveStatus: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + }, + }, + PhysicalLocation: struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + }{ + PartLocation: struct { + ServiceLabel string `json:"ServiceLabel"` + }{ + ServiceLabel: "Box 3:Bay 7", + }, + }, + Protocol: "NVMe", + FailurePredicted: false, + CapacityBytes: 1600321314816, + }) ) type TestErrorResponse struct { @@ -123,3 +347,309 @@ func Test_DL560_Exporter(t *testing.T) { }) } } + +func Test_DL560_Drives(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 2, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", + }, + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + } `json:"Links"` + }{ + LinksUpper: struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksLower struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + } `json:"links"` + }{ + LinksLower: struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + } `json:"Links"` + }{ + LinksUpper: struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + }{ + Drives: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", + }, + }, + }, + })) + return + } + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Unknown path - please create test case(s) for it")) + })) + defer server.Close() + + ctx := context.Background() + assert := assert.New(t) + + logicalDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportLogicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + physDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportPhysicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + nvmeDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportNVMeDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + tests := []struct { + name string + uri string + metricName string + metricRef1 string + metricRef2 string + exportFunc func(*Exporter, []byte) error + payload []byte + expected string + }{ + { + name: "Good Logical Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "dl560_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveUpper, + expected: GoodLogicalDriveUpperResponse, + }, + { + name: "Good Logical Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "dl560_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveLower, + expected: GoodLogicalDriveLowerResponse, + }, + { + name: "Good Disk Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "dl560_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveUpper, + expected: GoodDiskDriveUpperResponse, + }, + { + name: "Good Disk Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "dl560_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveLower, + expected: GoodDiskDriveLowerResponse, + }, + { + name: "Good Nvme Drive", + uri: "/redfish/v1/good", + metricName: "dl560_nvme_drive_status", + metricRef1: "nvmeMetrics", + metricRef2: "nvmeDriveStatus", + exportFunc: nvmeDevMetrics, + payload: GoodNvmeDrive, + expected: GoodNvmeDriveResponse, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var exporter prometheus.Collector + var err error + exporter, err = NewExporter(ctx, server.URL, test.uri, "") + assert.Nil(err) + assert.NotNil(exporter) + + prometheus.MustRegister(exporter) + + err = test.exportFunc(exporter.(*Exporter), test.payload) + if err != nil { + t.Error(err) + } + + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + + assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) + + prometheus.Unregister(exporter) + + }) + } +} diff --git a/hpe/dl560/memory.go b/hpe/dl560/memory.go deleted file mode 100644 index 0d884ce..0000000 --- a/hpe/dl560/memory.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -// /redfish/v1/systems/1/ - -// MemoryMetrics is the top level json object for DL560 Memory metadata -type MemoryMetrics struct { - ID string `json:"Id"` - MemorySummary MemorySummary `json:"MemorySummary"` -} - -// MemorySummary is the json object for DL560 MemorySummary metadata -type MemorySummary struct { - Status StatusMemory `json:"Status"` - TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` - TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` -} - -// StatusMemory is the variable to determine if the memory is OK or not -type StatusMemory struct { - HealthRollup string `json:"HealthRollup"` -} diff --git a/hpe/dl560/metrics.go b/hpe/dl560/metrics.go index 2931f92..956b0f1 100644 --- a/hpe/dl560/metrics.go +++ b/hpe/dl560/metrics.go @@ -40,37 +40,43 @@ func NewDeviceMetrics() *map[string]*metrics { } ThermalMetrics = &metrics{ - "fanSpeed": newServerMetric("dl560_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name"}), - "fanStatus": newServerMetric("dl560_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name"}), - "sensorTemperature": newServerMetric("dl560_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name"}), - "sensorStatus": newServerMetric("dl560_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name"}), + "fanSpeed": newServerMetric("dl560_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name", "chassisSerialNumber"}), + "fanStatus": newServerMetric("dl560_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "sensorTemperature": newServerMetric("dl560_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), + "sensorStatus": newServerMetric("dl560_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), } PowerMetrics = &metrics{ - "supplyOutput": newServerMetric("dl560_power_supply_output", "Power supply output in watts", nil, []string{"bayNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl560_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"bayNumber", "sparePartNumber"}), - "supplyTotalConsumed": newServerMetric("dl560_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId"}), - "supplyTotalCapacity": newServerMetric("dl560_power_supply_total_capacity", "Total output capacity of all the power supplies", nil, []string{"memberId"}), + "voltageOutput": newServerMetric("dl560_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), + "voltageStatus": newServerMetric("dl560_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "supplyOutput": newServerMetric("dl560_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyStatus": newServerMetric("dl560_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyTotalConsumed": newServerMetric("dl560_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } // Splitting out the three different types of drives to gather metrics on each (NVMe, Disk Drive, and Logical Drive) // NVMe Drive Metrics NVMeDriveMetrics = &metrics{ - "nvmeDriveStatus": newServerMetric("dl560_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"protocol", "id", "serviceLabel"}), + "nvmeDriveStatus": newServerMetric("dl560_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"chassisSerialNumber", "protocol", "id", "serviceLabel"}), } // Phyiscal Storage Disk Drive Metrics DiskDriveMetrics = &metrics{ - "driveStatus": newServerMetric("dl560_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "id", "location", "serialnumber"}), + "driveStatus": newServerMetric("dl560_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "id", "location", "serialnumber"}), } // Logical Disk Drive Metrics LogicalDriveMetrics = &metrics{ - "raidStatus": newServerMetric("dl560_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "logicaldrivename", "volumeuniqueidentifier", "raid"}), + "raidStatus": newServerMetric("dl560_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "logicaldrivename", "volumeuniqueidentifier", "raid"}), } MemoryMetrics = &metrics{ - "memoryStatus": newServerMetric("dl560_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"totalSystemMemoryGiB"}), + "memoryStatus": newServerMetric("dl560_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"chassisSerialNumber", "totalSystemMemoryGiB"}), + "memoryDimmStatus": newServerMetric("dl560_memory_dimm_status", "Current dimm status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "capacityMiB", "manufacturer", "partNumber", "serialNumber"}), + } + + DeviceMetrics = &metrics{ + "deviceInfo": newServerMetric("device_info", "Current snapshot of device firmware information", nil, []string{"name", "chassisSerialNumber", "firmwareVersion", "biosVersion", "model"}), } Metrics = &map[string]*metrics{ @@ -81,6 +87,7 @@ func NewDeviceMetrics() *map[string]*metrics { "diskDriveMetrics": DiskDriveMetrics, "logicalDriveMetrics": LogicalDriveMetrics, "memoryMetrics": MemoryMetrics, + "deviceInfo": DeviceMetrics, } ) diff --git a/hpe/dl560/network_adapter.go b/hpe/dl560/network_adapter.go deleted file mode 100644 index a6b8fc4..0000000 --- a/hpe/dl560/network_adapter.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -// /redfish/v1/Systems/1/BaseNetworkAdapters - -// NetworkAdapter is the top level json object for DL560 Network Adapter metadata -type NetworkAdapter struct { - ID string `json:"Id"` - Firmware Firmware `json:"Firmware"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` - SerialNumber string `json:"SerialNumber"` - StructuredName string `json:"StructuredName"` - Status Status `json:"Status"` - UEFIDevicePath string `json:"UEFIDevicePath"` -} - -// Firmware is the top level json object for DL560 Network Adapter metadata -type Firmware struct { - Current FirmwareCurrent `json:"Current"` -} - -// FirmwareCurrent contains the version in string format -type FirmwareCurrent struct { - Version string `json:"VersionString"` -} - -// PhysicalPorts contains the metadata for the Chassis NICs -type PhysicalPorts struct { - FullDuplex bool `json:"FullDuplex"` - IPv4Addresses []Addr `json:"IPv4Addresses"` - IPv6Addresses []Addr `json:"IPv6Addresses"` - LinkStatus string `json:"LinkStatus"` - MacAddress string `json:"MacAddress"` - Name string `json:"Name"` - SpeedMbps int `json:"SpeedMbps"` - Status Status `json:"Status"` -} - -// Addr contains the IPv4 or IPv6 Address in string format -type Addr struct { - Address string `json:"Address"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/hpe/dl560/power.go b/hpe/dl560/power.go deleted file mode 100644 index 6a238e7..0000000 --- a/hpe/dl560/power.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hp Hp `json:"Hp"` -} - -// Hpe contains metadata on power supply product info -type Hp struct { - AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` - Mismatched bool `json:"Mismatched"` - PowerSupplyStatus Status `json:"PowerSupplyStatus"` - IPDUCapable bool `json:"iPDUCapable"` -} diff --git a/hpe/dl560/thermal.go b/hpe/dl560/thermal.go deleted file mode 100644 index a39ab8f..0000000 --- a/hpe/dl560/thermal.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dl560 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for DL560 Thermal metadata -type ThermalMetrics struct { - ID string `json:"Id"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` -} - -// Fan is the json object for a DL560 fan module -type Fan struct { - MemberID string `json:"MemberId"` - FanName string `json:"FanName"` - CurrentReading int `json:"CurrentReading"` - ReadingUnits string `json:"ReadingUnits"` - Status StatusThermal `json:"Status"` -} - -// StatusThermal is the variable to determine if a fan or temperature sensor module is OK or not -type StatusThermal struct { - Health string `json:"Health"` - State string `json:"State"` -} - -// Temperature is the json object for a DL560 temperature sensor module -type Temperature struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius int `json:"ReadingCelsius"` - SensorNumber int `json:"SensorNumber"` - Status StatusThermal `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` - UpperThresholdFatal int `json:"UpperThresholdFatal"` -} diff --git a/hpe/moonshot/exporter.go b/hpe/moonshot/exporter.go index 66b616f..8c30555 100644 --- a/hpe/moonshot/exporter.go +++ b/hpe/moonshot/exporter.go @@ -406,7 +406,7 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { // exportSwitchMetrics collects the switches metrics in json format and sets the prometheus gauges func (e *Exporter) exportSwitchMetrics(body []byte) error { var state float64 - var sm Sw + var sm moonshot.Sw var msSw = (*e.deviceMetrics)["swMetrics"] err := json.Unmarshal(body, &sm) if err != nil { @@ -427,7 +427,7 @@ func (e *Exporter) exportSwitchMetrics(body []byte) error { func (e *Exporter) exportSwitchThermalMetrics(namePrefix string, body []byte) error { var state float64 - var tm ThermalMetrics + var tm moonshot.ThermalMetrics var msSwThermal = (*e.deviceMetrics)["swThermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { @@ -452,7 +452,7 @@ func (e *Exporter) exportSwitchThermalMetrics(namePrefix string, body []byte) er // exportSwitchPowerMetrics collects the switches power metrics in json format and sets the prometheus gauges func (e *Exporter) exportSwitchPowerMetrics(namePrefix string, body []byte) error { - var spm SwPowerMetrics + var spm moonshot.SwPowerMetrics var msSwPower = (*e.deviceMetrics)["swPowerMetrics"] err := json.Unmarshal(body, &spm) if err != nil { diff --git a/hpe/moonshot/power.go b/hpe/moonshot/power.go deleted file mode 100644 index 0565e8d..0000000 --- a/hpe/moonshot/power.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package moonshot - -// /rest/v1/Chassis/1/PowerMetrics - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerSupplies []PowerSupplies `json:"PowerSupplies"` - Type string `json:"Type"` - Links Links `json:"links"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hp HpPower `json:"Hp"` -} - -// HpPower is the top level json object for the power supplies metadata -type HpPower struct { - PowercapDescription string `json:"PowercapDescription"` - PowercapMode int `json:"PowercapMode"` - InstantWattage int `json:"InstantWattage,omitempty"` - Type string `json:"Type"` - WattageHistoryLevel []WattageHistoryLevel `json:"WattageHistoryLevel"` -} - -// WattageHistoryLevel is the top level json object for all historical Samples metadata -type WattageHistoryLevel struct { - Counter int `json:"Counter"` - Cumulator int `json:"Cumulator"` - SampleType string `json:"SampleType"` - Samples []SamplesPower `json:"Samples"` -} - -// SamplesPower holds the historical data for power wattage -type SamplesPower struct { - Wattage string `json:"Wattage"` -} - -// StatusPower is the variable to determine if a power supply is OK or not -type StatusPower struct { - State string `json:"State"` -} - -// PowerSupplies is the top level json object for metadata on power supply product info -type PowerSupplies struct { - ACInputStatus string `json:"ACInputStatus"` - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem Oem `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status StatusPower `json:"Status"` -} - -// Oem namespace layer for Hp json object -type Oem struct { - Hp Hp `json:"Hp"` -} - -// Hp contains metadata on power supply product info -type Hp struct { - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - Type string `json:"Type"` -} - -// Links is a reference to the current REST API URL call -type Links struct { - Self struct { - Href string `json:"href"` - } `json:"self"` -} diff --git a/hpe/moonshot/sw.go b/hpe/moonshot/sw.go deleted file mode 100644 index 9a86367..0000000 --- a/hpe/moonshot/sw.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package moonshot - -// /rest/v1/chassis/1/switches/sa - -// Sw is the top level json object for Switch Information metadata -type Sw struct { - Name string `json:"Name"` - Power string `json:"Power,omitempty"` - SerialNumber string `json:"SerialNumber"` - Status SwStatus `json:"Status"` - SwitchInfo SwInfo `json:"SwitchInfo"` - Type string `json:"Type"` -} - -// SwStatus is the top level json object for switch status -type SwStatus struct { - State string `json:"State,omitempty"` -} - -// SwInfo is the top level json object for switch info -type SwInfo struct { - HealthStatus string `json:"HealthStatus,omitempty"` -} diff --git a/hpe/moonshot/sw_power.go b/hpe/moonshot/sw_power.go deleted file mode 100644 index 0e1e530..0000000 --- a/hpe/moonshot/sw_power.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package moonshot - -// /rest/v1/chassis/1/switches/sa/PowerMetrics - -// SwPowerMetrics is the top level json object for Power metadata -type SwPowerMetrics struct { - Name string `json:"Name"` - Oem SwOemPower `json:"Oem"` - PowerSupplies []PowerSupplies `json:"PowerSupplies"` - Type string `json:"Type"` - Links Links `json:"links"` -} - -// SwOemPower is the top level json object for historical data for wattage -type SwOemPower struct { - Hp SwHpPower `json:"Hp"` -} - -// SwHpPower is the top level json object for the power supplies metadata -type SwHpPower struct { - InstantWattage int `json:"InstantWattage"` - MaximumWattage int `json:"MaximumWattage"` - Type string `json:"Type"` - WattageHistoryLevel []SwWattageHistoryLevel `json:"WattageHistoryLevel"` -} - -// SwWattageHistoryLevel is the top level json object for all historical Samples metadata -type SwWattageHistoryLevel struct { - Counter int `json:"Counter"` - Cumulator int `json:"Cumulator"` - SampleType string `json:"SampleType"` - Samples []SwSamplesPower `json:"Samples"` -} - -// SwSamplesPower holds the historical data for power wattage -type SwSamplesPower struct { - Wattage string `json:"Wattage"` -} diff --git a/hpe/moonshot/systems_summary.go b/hpe/moonshot/systems_summary.go deleted file mode 100644 index 9bed3d5..0000000 --- a/hpe/moonshot/systems_summary.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package moonshot - -// /rest/v1/SystemsSummary - -// SystemsSummary is the top level json object for Moonshot summary metadata -type SystemsSummary struct { - Name string `json:"Name"` - Systems []Systems `json:"Systems,omitempty"` - SystemsInChassis int `json:"SystemsInChassis,omitempty"` - Type string `json:"Type"` -} - -// Systems contains metadata on each cartridge that is present in the Moonshot Chassis -type Systems struct { - AssetTag string `json:"AssetTag,omitempty"` - Health string `json:"Health,omitempty"` - HostMACAddress []string `json:"HostMACAddress,omitempty"` - Memory string `json:"Memory,omitempty"` - Model string `json:"Model,omitempty"` - Name string `json:"Name,omitempty"` - Power string `json:"Power,omitempty"` - ProcessorFamily string `json:"ProcessorFamily,omitempty"` - ProcessorManufacturer string `json:"ProcessorManufacturer,omitempty"` - SKU string `json:"SKU,omitempty"` - SerialNumber string `json:"SerialNumber,omitempty"` - UUID string `json:"UUID,omitempty"` -} diff --git a/hpe/moonshot/thermal.go b/hpe/moonshot/thermal.go deleted file mode 100644 index 07dc9e4..0000000 --- a/hpe/moonshot/thermal.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package moonshot - -// /rest/v1/Chassis/1/ThermalMetrics - -// HpThermal is hold the metadata for product information -type HpThermal struct { - Location string `json:"Location"` - Type string `json:"Type"` -} - -// OemThermal is the top level json object for Hp product information -type OemThermal struct { - Hp HpThermal `json:"Hp"` -} - -// StatusThermal is the variable to determine if a Fan is OK or not -type StatusThermal struct { - State string `json:"State"` -} - -// SamplesThermal saves historical data on specific intervals -type SamplesThermal struct { - Temperature int `json:"Temperature"` -} - -// Fans gives metadata on each fan -type Fans struct { - CurrentReading int `json:"CurrentReading"` - FanName string `json:"FanName"` - Oem OemThermal `json:"Oem"` - ProductName string `json:"ProductName"` - Status StatusThermal `json:"Status"` - Units string `json:"Units"` -} - -// ThermalMetrics is the top level json object for Fan and Temperatures metadata -type ThermalMetrics struct { - Fans []Fans `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperatures `json:"Temperatures"` - Type string `json:"Type"` -} - -// Temperatures is the top level json object for temperature metadata -type Temperatures struct { - CurrentReading int `json:"CurrentReading"` - Name string `json:"Name"` - Status StatusThermal `json:"Status"` - TemperatureHistoryLevel []TemperatureHistoryLevel `json:"TemperatureHistoryLevel"` - Units string `json:"Units"` - UpperThresholdCritical int `json:"UpperThresholdCritical,omitempty"` - UpperThresholdNonCritical int `json:"UpperThresholdNonCritical,omitempty"` -} - -// TemperatureHistoryLevel is the top level json object for all historical Samples metadata -type TemperatureHistoryLevel struct { - Counter int `json:"Counter"` - Cumulator int `json:"Cumulator"` - SampleType string `json:"SampleType"` - Samples []SamplesThermal `json:"Samples"` -} diff --git a/hpe/xl420/drive.go b/hpe/xl420/drive.go deleted file mode 100644 index 8b2f882..0000000 --- a/hpe/xl420/drive.go +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ - -// NVME's -// /redfish/v1/chassis/1/ -type NVMeDriveMetrics struct { - ID string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem Oem `json:"Oem"` - PhysicalLocation PhysicalLocation `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - Status Status `json:"Status"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` -} - -// Logical Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/LogicalDrives/X/ -type LogicalDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status Status `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` -} - -// Disk Drives -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/X/DiskDrives/X/ -type DiskDriveMetrics struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status Status `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` -} - -// GenericDrive is used to iterate over differing drive endpoints -// /redfish/v1/Systems/1/SmartStorage/ArrayControllers/ for Logical and Physical Drives -// /redfish/v1/Chassis/1/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"` -} - -type Members struct { - URL string `json:"@odata.id"` -} - -type LinksUpper struct { - Drives []URL `json:"Drives,omitempty"` - LogicalDrives URL `json:"LogicalDrives,omitempty"` - PhysicalDrives URL `json:"PhysicalDrives,omitempty"` -} - -type LinksLower struct { - Drives []HRef `json:"Drives,omitempty"` - LogicalDrives HRef `json:"LogicalDrives,omitempty"` - PhysicalDrives HRef `json:"PhysicalDrives,omitempty"` -} - -type HRef struct { - URL string `json:"href"` -} - -type URL struct { - URL string `json:"@odata.id"` -} - -// PhysicalLocation -type PhysicalLocation struct { - PartLocation PartLocation `json:"PartLocation"` -} - -// PartLocation is a variable that determines the Box and the Bay location of the NVMe drive -type PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` -} - -// Contents of Oem -type Oem struct { - Hpe HpeCont `json:"Hpe"` -} - -// Contents of Hpe -type HpeCont struct { - CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` - DriveStatus Status `json:"DriveStatus"` - NVMeID string `json:"NVMeId"` -} diff --git a/hpe/xl420/drive_test.go b/hpe/xl420/drive_test.go deleted file mode 100644 index e328e5f..0000000 --- a/hpe/xl420/drive_test.go +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/stretchr/testify/assert" -) - -const ( - GoodLogicalDriveUpperResponse = ` - # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_logical_drive_status gauge - xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 - ` - GoodDiskDriveUpperResponse = ` - # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_disk_drive_status gauge - xl420_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 - ` - GoodLogicalDriveLowerResponse = ` - # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_logical_drive_status gauge - xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_disk_drive_status gauge - xl420_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` - # HELP xl420_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_nvme_drive_status gauge - xl420_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 - ` -) - -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - -func Test_XL420_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() - - ctx := context.Background() - assert := assert.New(t) - - logicalDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportLogicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - physDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportPhysicalDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - nvmeDevMetrics := func(exp *Exporter, payload []byte) error { - err := exp.exportNVMeDriveMetrics(payload) - if err != nil { - return err - } - return nil - } - - tests := []struct { - name string - uri string - metricName string - metricRef1 string - metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte - expected string - }{ - { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "xl420_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, - }, - { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "xl420_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "xl420_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "xl420_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, - }, - { - name: "Good Nvme Drive", - uri: "/redfish/v1/good", - metricName: "xl420_nvme_drive_status", - metricRef1: "nvmeMetrics", - metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) - - err = test.exportFunc(exporter.(*Exporter), test.payload) - if err != nil { - t.Error(err) - } - - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - - }) - } -} diff --git a/hpe/xl420/exporter.go b/hpe/xl420/exporter.go index 6ad06d4..dabb663 100644 --- a/hpe/xl420/exporter.go +++ b/hpe/xl420/exporter.go @@ -26,6 +26,7 @@ import ( "net" "net/http" "net/url" + "path" "strconv" "strings" "sync" @@ -56,6 +57,10 @@ const ( LOGICALDRIVE = "LogicalDriveMetrics" // MEMORY represents the memory metric endpoints MEMORY = "MemoryMetrics" + // MEMORY_SUMMARY represents the memory metric endpoints + MEMORY_SUMMARY = "MemorySummaryMetrics" + // FIRMWARE represents the firmware metric endpoints + FIRMWARE = "FirmwareMetrics" // OK is a string representation of the float 1.0 for device status OK = 1.0 // BAD is a string representation of the float 0.0 for device status @@ -71,12 +76,14 @@ var ( // Exporter collects chassis manager stats from the given URI and exports them using // the prometheus metrics package. type Exporter struct { - ctx context.Context - mutex sync.RWMutex - pool *pool.Pool - host string - credProfile string - deviceMetrics *map[string]*metrics + ctx context.Context + mutex sync.RWMutex + pool *pool.Pool + host string + credProfile string + biosVersion string + chassisSerialNumber string + deviceMetrics *map[string]*metrics } // NewExporter returns an initialized Exporter for HPE XL420 device. @@ -138,6 +145,36 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e return &exp, nil } + // chassis system endpoint to use for memory, processor, bios version scrapes + sysEndpoint, err := getChassisEndpoint(fqdn.String()+uri+"/Managers/1/", target, retryClient) + if err != nil { + log.Error("error when getting chassis endpoint from "+XL420, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + if errors.Is(err, common.ErrInvalidCredential) { + common.IgnoredDevices[exp.host] = common.IgnoredDevice{ + Name: exp.host, + Endpoint: "https://" + exp.host + "/redfish/v1/Chassis", + Module: XL420, + CredentialProfile: exp.credProfile, + } + log.Info("added host "+exp.host+" to ignored list", zap.Any("trace_id", exp.ctx.Value("traceID"))) + var upMetric = (*exp.deviceMetrics)["up"] + (*upMetric)["up"].WithLabelValues().Set(float64(2)) + + return &exp, nil + } + return nil, err + } + + exp.chassisSerialNumber = path.Base(sysEndpoint) + + // chassis BIOS version + biosVer, err := getBIOSVersion(fqdn.String()+sysEndpoint, target, retryClient) + if err != nil { + log.Error("error when getting BIOS version from "+XL420, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + exp.biosVersion = biosVer + // vars for drive parsing var ( initialURL = "/Systems/1/SmartStorage/ArrayControllers/" @@ -262,10 +299,23 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+url, NVME, target, profile, retryClient))) } + // DIMM endpoints array + dimms, err := getDIMMEndpoints(fqdn.String()+sysEndpoint+"/Memory", target, retryClient) + if err != nil { + log.Error("error when getting DIMM endpoints from "+XL420, zap.Error(err), zap.Any("trace_id", ctx.Value("traceID"))) + return nil, err + } + tasks = append(tasks, pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Thermal/", THERMAL, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Chassis/1/Power/", POWER, target, profile, retryClient)), - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient))) + pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY_SUMMARY, target, profile, retryClient))) + + // DIMMs + for _, dimm := range dimms.Members { + tasks = append(tasks, + pool.NewTask(common.Fetch(fqdn.String()+dimm.URL, MEMORY, target, profile, retryClient))) + } exp.pool = pool.NewPool(tasks, 1) @@ -350,6 +400,8 @@ func (e *Exporter) scrape() { } switch task.MetricType { + case FIRMWARE: + err = e.exportFirmwareMetrics(task.Body) case THERMAL: err = e.exportThermalMetrics(task.Body) case POWER: @@ -362,6 +414,8 @@ func (e *Exporter) scrape() { err = e.exportLogicalDriveMetrics(task.Body) case MEMORY: err = e.exportMemoryMetrics(task.Body) + case MEMORY_SUMMARY: + err = e.exportMemorySummaryMetrics(task.Body) } if err != nil { @@ -383,56 +437,109 @@ func (e *Exporter) scrape() { } -// exportPowerMetrics collects the XL420's power metrics in json format and sets the prometheus gauges +// exportFirmwareMetrics collects the device metrics in json format and sets the prometheus gauges +func (e *Exporter) exportFirmwareMetrics(body []byte) error { + var chas oem.Chassis + var dm = (*e.deviceMetrics)["deviceInfo"] + err := json.Unmarshal(body, &chas) + if err != nil { + return fmt.Errorf("Error Unmarshalling XL420 FirmwareMetrics - " + err.Error()) + } + + (*dm)["deviceInfo"].WithLabelValues(chas.Description, e.chassisSerialNumber, chas.FirmwareVersion, e.biosVersion, XL420).Set(1.0) + + return nil +} + +// exportPowerMetrics collects the power metrics in json format and sets the prometheus gauges func (e *Exporter) exportPowerMetrics(body []byte) error { var state float64 var pm oem.PowerMetrics - var dlPower = (*e.deviceMetrics)["powerMetrics"] + var pow = (*e.deviceMetrics)["powerMetrics"] err := json.Unmarshal(body, &pm) if err != nil { return fmt.Errorf("Error Unmarshalling XL420 PowerMetrics - " + err.Error()) } - for idx, pc := range pm.PowerControl { - if pc.MemberID != "" { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(pc.MemberID).Set(float64(pc.PowerCapacityWatts)) + for _, pc := range pm.PowerControl.PowerControl { + var watts float64 + switch pc.PowerConsumedWatts.(type) { + case float64: + if pc.PowerConsumedWatts.(float64) > 0 { + watts = pc.PowerConsumedWatts.(float64) + } + case string: + if pc.PowerConsumedWatts.(string) != "" { + watts, _ = strconv.ParseFloat(pc.PowerConsumedWatts.(string), 32) + } + default: + // use the AverageConsumedWatts if PowerConsumedWatts is not present + switch pc.PowerMetrics.AverageConsumedWatts.(type) { + case float64: + watts = pc.PowerMetrics.AverageConsumedWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(pc.PowerMetrics.AverageConsumedWatts.(string), 32) + } + } + (*pow)["supplyTotalConsumed"].WithLabelValues(pc.MemberID, e.chassisSerialNumber).Set(watts) + } + + for _, pv := range pm.Voltages { + if pv.Status.State == "Enabled" { + var volts float64 + switch pv.ReadingVolts.(type) { + case float64: + volts = pv.ReadingVolts.(float64) + case string: + volts, _ = strconv.ParseFloat(pv.ReadingVolts.(string), 32) + } + (*pow)["voltageOutput"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(volts) + if pv.Status.Health == "OK" { + state = OK + } else { + state = BAD + } } else { - (*dlPower)["supplyTotalConsumed"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerConsumedWatts)) - (*dlPower)["supplyTotalCapacity"].WithLabelValues(strconv.Itoa(idx)).Set(float64(pc.PowerCapacityWatts)) + state = BAD } + + (*pow)["voltageStatus"].WithLabelValues(pv.Name, e.chassisSerialNumber).Set(state) } for _, ps := range pm.PowerSupplies { if ps.Status.State == "Enabled" { - if ps.MemberID != "" { - (*dlPower)["supplyOutput"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) - } else { - (*dlPower)["supplyOutput"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(float64(ps.LastPowerOutputWatts)) + var watts float64 + switch ps.LastPowerOutputWatts.(type) { + case float64: + watts = ps.LastPowerOutputWatts.(float64) + case string: + watts, _ = strconv.ParseFloat(ps.LastPowerOutputWatts.(string), 32) } + (*pow)["supplyOutput"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(watts) if ps.Status.Health == "OK" { state = OK + } else if ps.Status.Health == "" { + state = OK } else { state = BAD } - if ps.MemberID != "" { - (*dlPower)["supplyStatus"].WithLabelValues(ps.MemberID, ps.SparePartNumber).Set(state) - } else { - (*dlPower)["supplyStatus"].WithLabelValues(strconv.Itoa(ps.Oem.Hp.BayNumber), ps.SparePartNumber).Set(state) - } + } else { + state = BAD } + + (*pow)["supplyStatus"].WithLabelValues(ps.Name, e.chassisSerialNumber, ps.Manufacturer, ps.SparePartNumber, ps.SerialNumber, ps.PowerSupplyType, ps.Model).Set(state) } return nil } -// exportThermalMetrics collects the XL420's thermal and fan metrics in json format and sets the prometheus gauges +// exportThermalMetrics collects the thermal and fan metrics in json format and sets the prometheus gauges func (e *Exporter) exportThermalMetrics(body []byte) error { var state float64 var tm oem.ThermalMetrics - var dlThermal = (*e.deviceMetrics)["thermalMetrics"] + var therm = (*e.deviceMetrics)["thermalMetrics"] err := json.Unmarshal(body, &tm) if err != nil { return fmt.Errorf("Error Unmarshalling XL420 ThermalMetrics - " + err.Error()) @@ -442,10 +549,18 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, fan := range tm.Fans { // Check fan status and convert string to numeric values if fan.Status.State == "Enabled" { + var fanSpeed float64 + switch fan.Reading.(type) { + case string: + fanSpeed, _ = strconv.ParseFloat(fan.Reading.(string), 32) + case float64: + fanSpeed = fan.Reading.(float64) + } + if fan.FanName != "" { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.FanName).Set(float64(fan.CurrentReading)) + (*therm)["fanSpeed"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(float64(fan.CurrentReading)) } else { - (*dlThermal)["fanSpeed"].WithLabelValues(fan.Name).Set(float64(fan.Reading)) + (*therm)["fanSpeed"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(fanSpeed) } if fan.Status.Health == "OK" { state = OK @@ -453,9 +568,9 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { state = BAD } if fan.FanName != "" { - (*dlThermal)["fanStatus"].WithLabelValues(fan.FanName).Set(state) + (*therm)["fanStatus"].WithLabelValues(fan.FanName, e.chassisSerialNumber).Set(state) } else { - (*dlThermal)["fanStatus"].WithLabelValues(fan.Name).Set(state) + (*therm)["fanStatus"].WithLabelValues(fan.Name, e.chassisSerialNumber).Set(state) } } } @@ -464,32 +579,41 @@ func (e *Exporter) exportThermalMetrics(body []byte) error { for _, sensor := range tm.Temperatures { // Check sensor status and convert string to numeric values if sensor.Status.State == "Enabled" { - (*dlThermal)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(float64(sensor.ReadingCelsius)) + var celsTemp float64 + switch sensor.ReadingCelsius.(type) { + case string: + celsTemp, _ = strconv.ParseFloat(sensor.ReadingCelsius.(string), 32) + case int: + celsTemp = float64(sensor.ReadingCelsius.(int)) + case float64: + celsTemp = sensor.ReadingCelsius.(float64) + } + (*therm)["sensorTemperature"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(celsTemp) if sensor.Status.Health == "OK" { state = OK } else { state = BAD } - (*dlThermal)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " ")).Set(state) + (*therm)["sensorStatus"].WithLabelValues(strings.TrimRight(sensor.Name, " "), e.chassisSerialNumber).Set(state) } } return nil } -// exportLogicalDriveMetrics collects the DL560 logical drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { +// exportPhysicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { var state float64 - var dld LogicalDriveMetrics - var dlDrive = (*e.deviceMetrics)["logicalDriveMetrics"] - err := json.Unmarshal(body, &dld) + var dlphysical oem.DiskDriveMetrics + var dlphysicaldrive = (*e.deviceMetrics)["diskDriveMetrics"] + err := json.Unmarshal(body, &dlphysical) if err != nil { - return fmt.Errorf("Error Unmarshalling XL420 LogicalDriveMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling XL420 DiskDriveMetrics - " + err.Error()) } - // Check logical drive is enabled then check status and convert string to numeric values - if dld.Status.State == "Enabled" { - if dld.Status.Health == "OK" { + // Check physical drive is enabled then check status and convert string to numeric values + if dlphysical.Status.State == "Enabled" { + if dlphysical.Status.Health == "OK" { state = OK } else { state = BAD @@ -498,24 +622,24 @@ func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { state = DISABLED } - (*dlDrive)["raidStatus"].WithLabelValues(dld.Name, dld.LogicalDriveName, dld.VolumeUniqueIdentifier, dld.Raid).Set(state) - + // Physical drives need to have a unique identifier like location so as to not overwrite data + // physical drives can have the same ID, but belong to a different ArrayController, therefore need more than just the ID as a unique identifier. + (*dlphysicaldrive)["driveStatus"].WithLabelValues(dlphysical.Name, e.chassisSerialNumber, dlphysical.Id, dlphysical.Location, dlphysical.SerialNumber).Set(state) return nil } -// exportPhysicalDriveMetrics collects the XL420 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { - +// exportLogicalDriveMetrics collects the physical drive metrics in json format and sets the prometheus gauges +func (e *Exporter) exportLogicalDriveMetrics(body []byte) error { var state float64 - var dpd DiskDriveMetrics - var dpDrive = (*e.deviceMetrics)["diskDriveMetrics"] - err := json.Unmarshal(body, &dpd) + var dllogical oem.LogicalDriveMetrics + var dllogicaldrive = (*e.deviceMetrics)["logicalDriveMetrics"] + err := json.Unmarshal(body, &dllogical) if err != nil { - return fmt.Errorf("Error Unmarshalling XL420 DriveMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling XL420 LogicalDriveMetrics - " + err.Error()) } // Check physical drive is enabled then check status and convert string to numeric values - if dpd.Status.State == "Enabled" { - if dpd.Status.Health == "OK" { + if dllogical.Status.State == "Enabled" { + if dllogical.Status.Health == "OK" { state = OK } else { state = BAD @@ -524,15 +648,14 @@ func (e *Exporter) exportPhysicalDriveMetrics(body []byte) error { state = DISABLED } - (*dpDrive)["driveStatus"].WithLabelValues(dpd.Name, dpd.Id, dpd.Location, dpd.SerialNumber).Set(state) - + (*dllogicaldrive)["raidStatus"].WithLabelValues(dllogical.Name, e.chassisSerialNumber, dllogical.LogicalDriveName, dllogical.VolumeUniqueIdentifier, dllogical.Raid).Set(state) return nil } -// exportNVMeDriveMetrics collects the DL360 NVME drive metrics in json format and sets the prometheus gauges +// exportNVMeDriveMetrics collects the NVME drive metrics in json format and sets the prometheus gauges func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { var state float64 - var dlnvme NVMeDriveMetrics + var dlnvme oem.NVMeDriveMetrics var dlnvmedrive = (*e.deviceMetrics)["nvmeMetrics"] err := json.Unmarshal(body, &dlnvme) if err != nil { @@ -550,19 +673,19 @@ func (e *Exporter) exportNVMeDriveMetrics(body []byte) error { state = DISABLED } - (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) + (*dlnvmedrive)["nvmeDriveStatus"].WithLabelValues(e.chassisSerialNumber, dlnvme.Protocol, dlnvme.ID, dlnvme.PhysicalLocation.PartLocation.ServiceLabel).Set(state) return nil } -// exportMemoryMetrics collects the XL420 drive metrics in json format and sets the prometheus gauges -func (e *Exporter) exportMemoryMetrics(body []byte) error { +// exportMemorySummaryMetrics collects the memory summary metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemorySummaryMetrics(body []byte) error { var state float64 - var dlm MemoryMetrics + var dlm oem.MemorySummaryMetrics var dlMemory = (*e.deviceMetrics)["memoryMetrics"] err := json.Unmarshal(body, &dlm) if err != nil { - return fmt.Errorf("Error Unmarshalling XL420 MemoryMetrics - " + err.Error()) + return fmt.Errorf("Error Unmarshalling XL420 MemorySummaryMetrics - " + err.Error()) } // Check memory status and convert string to numeric values if dlm.MemorySummary.Status.HealthRollup == "OK" { @@ -571,13 +694,179 @@ func (e *Exporter) exportMemoryMetrics(body []byte) error { state = BAD } - (*dlMemory)["memoryStatus"].WithLabelValues(strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) + (*dlMemory)["memoryStatus"].WithLabelValues(e.chassisSerialNumber, strconv.Itoa(dlm.MemorySummary.TotalSystemMemoryGiB)).Set(state) return nil } -func getDriveEndpoint(url, host string, client *retryablehttp.Client) (GenericDrive, error) { - var drive GenericDrive +// exportMemoryMetrics collects the memory dimm metrics in json format and sets the prometheus gauges +func (e *Exporter) exportMemoryMetrics(body []byte) error { + + var state float64 + var mm oem.MemoryMetrics + var mem = (*e.deviceMetrics)["memoryMetrics"] + err := json.Unmarshal(body, &mm) + if err != nil { + return fmt.Errorf("Error Unmarshalling XL420 MemoryMetrics - " + err.Error()) + } + + if mm.Status != "" { + var memCap string + var status string + + switch mm.CapacityMiB.(type) { + case string: + memCap = mm.CapacityMiB.(string) + case int: + memCap = strconv.Itoa(mm.CapacityMiB.(int)) + case float64: + memCap = strconv.Itoa(int(mm.CapacityMiB.(float64))) + } + + switch mm.Status.(type) { + case string: + status = mm.Status.(string) + if status == "Operable" { + state = OK + } else { + state = BAD + } + default: + if s, ok := mm.Status.(map[string]interface{}); ok { + switch s["State"].(type) { + case string: + if s["State"].(string) == "Enabled" { + switch s["Health"].(type) { + case string: + if s["Health"].(string) == "OK" { + state = OK + } else if s["Health"].(string) == "" { + state = OK + } else { + state = BAD + } + case nil: + state = OK + } + } else if s["State"].(string) == "Absent" { + return nil + } else { + state = BAD + } + } + } + } + (*mem)["memoryDimmStatus"].WithLabelValues(mm.Name, e.chassisSerialNumber, memCap, mm.Manufacturer, strings.TrimRight(mm.PartNumber, " "), mm.SerialNumber).Set(state) + } + + return nil +} + +func getChassisEndpoint(url, host string, client *retryablehttp.Client) (string, error) { + var chas oem.Chassis + var urlFinal string + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + if resp.StatusCode == http.StatusUnauthorized { + return "", common.ErrInvalidCredential + } else { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &chas) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling XL420 Chassis struct - " + err.Error()) + } + + if len(chas.Links.ManagerForServers.ServerManagerURLSlice) > 0 { + urlFinal = chas.Links.ManagerForServers.ServerManagerURLSlice[0] + } + + return urlFinal, nil +} + +func getBIOSVersion(url, host string, client *retryablehttp.Client) (string, error) { + var biosVer oem.ServerManager + req := common.BuildRequest(url, host) + + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return "", fmt.Errorf("HTTP status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &biosVer) + if err != nil { + return "", fmt.Errorf("Error Unmarshalling XL420 ServerManager struct - " + err.Error()) + } + + return biosVer.BiosVersion, nil +} + +func getDIMMEndpoints(url, host string, client *retryablehttp.Client) (oem.Collection, error) { + var dimms 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 dimms, 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 dimms, err + } else if !(resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices) { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } else { + return dimms, fmt.Errorf("HTTP status %d", resp.StatusCode) + } + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return dimms, fmt.Errorf("Error reading Response Body - " + err.Error()) + } + + err = json.Unmarshal(body, &dimms) + if err != nil { + return dimms, fmt.Errorf("Error Unmarshalling XL420 Memory Collection struct - " + err.Error()) + } + + return dimms, nil +} + +func getDriveEndpoint(url, host string, client *retryablehttp.Client) (oem.GenericDrive, error) { + var drive oem.GenericDrive var resp *http.Response var err error retryCount := 0 diff --git a/hpe/xl420/exporter_test.go b/hpe/xl420/exporter_test.go index 00abd0e..33e9673 100644 --- a/hpe/xl420/exporter_test.go +++ b/hpe/xl420/exporter_test.go @@ -35,6 +35,230 @@ const ( # TYPE up gauge up 2 ` + GoodLogicalDriveUpperResponse = ` + # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE xl420_logical_drive_status gauge + xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + ` + GoodDiskDriveUpperResponse = ` + # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE xl420_disk_drive_status gauge + xl420_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + ` + GoodLogicalDriveLowerResponse = ` + # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE xl420_logical_drive_status gauge + xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 + ` + GoodDiskDriveLowerResponse = ` + # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE xl420_disk_drive_status gauge + xl420_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 + ` + GoodNvmeDriveResponse = ` + # HELP xl420_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED + # TYPE xl420_nvme_drive_status gauge + xl420_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + ` +) + +var ( + GoodDiskDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "0", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:1", + SerialNumber: "ABC123", + }) + + GoodDiskDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + Model string `json:"Model"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + Location string `json:"Location"` + SerialNumber string `json:"SerialNumber"` + }{ + Id: "1", + CapacityMiB: 572325, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + Name: "HpeSmartStorageDiskDrive", + Model: "TESTMODEL", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + Location: "1I:1:2", + SerialNumber: "DEF456", + }) + + GoodLogicalDriveUpper = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 1", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "ABCDEF12345", + }) + + GoodLogicalDriveLower = MustMarshal(struct { + Id string `json:"Id"` + CapacityMiB int `json:"CapacityMiB"` + Description string `json:"Description"` + InterfaceType string `json:"InterfaceType"` + LogicalDriveName string `json:"LogicalDriveName"` + LogicalDriveNumber int `json:"LogicalDriveNumber"` + Name string `json:"Name"` + Raid string `json:"Raid"` + Status struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"Status"` + StripeSizebytes int `json:"StripeSizebytes"` + VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` + }{ + Id: "1", + CapacityMiB: 572293, + Description: "HPE Smart Storage Disk Drive View", + InterfaceType: "SAS", + LogicalDriveName: "TESTDRIVE NAME 2", + LogicalDriveNumber: 1, + Name: "HpeSmartStorageLogicalDrive", + Raid: "1", + Status: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + StripeSizebytes: 262144, + VolumeUniqueIdentifier: "FEDCBA12345", + }) + + GoodNvmeDrive = MustMarshal(struct { + Id string `json:"Id"` + Model string `json:"Model"` + Name string `json:"Name"` + MediaType string `json:"MediaType"` + Oem struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + } `json:"Oem"` + PhysicalLocation struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + } `json:"PhysicalLocation"` + Protocol string `json:"Protocol"` + FailurePredicted bool `json:"FailurePredicted"` + CapacityBytes int `json:"CapacityBytes"` + }{ + Id: "0", + Model: "TESTMODEL", + Name: "TESTNAME", + MediaType: "SSD", + Oem: struct { + Hpe struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + } `json:"Hpe"` + }{ + Hpe: struct { + DriveStatus struct { + Health string `json:"Health"` + State string `json:"State"` + } `json:"DriveStatus"` + }{ + DriveStatus: struct { + Health string `json:"Health"` + State string `json:"State"` + }{ + Health: "OK", + State: "Enabled", + }, + }, + }, + PhysicalLocation: struct { + PartLocation struct { + ServiceLabel string `json:"ServiceLabel"` + } `json:"PartLocation"` + }{ + PartLocation: struct { + ServiceLabel string `json:"ServiceLabel"` + }{ + ServiceLabel: "Box 3:Bay 7", + }, + }, + Protocol: "NVMe", + FailurePredicted: false, + CapacityBytes: 1600321314816, + }) ) type TestErrorResponse struct { @@ -123,3 +347,309 @@ func Test_XL420_Exporter(t *testing.T) { }) } } + +func Test_XL420_Drives(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 2, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", + }, + { + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + } `json:"Links"` + }{ + LinksUpper: struct { + LogicalDrives struct { + URL string `json:"@odata.id"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"@odata.id"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"@odata.id"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksLower struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + } `json:"links"` + }{ + LinksLower: struct { + LogicalDrives struct { + URL string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + URL string `json:"href"` + } `json:"PhysicalDrives"` + }{ + LogicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", + }, + PhysicalDrives: struct { + URL string `json:"href"` + }{ + URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + MembersCount int `json:"Members@odata.count"` + Members []struct { + URL string `json:"@odata.id"` + } `json:"Members"` + }{ + MembersCount: 1, + Members: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", + }, + }, + })) + return + } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { + w.WriteHeader(http.StatusOK) + w.Write(MustMarshal(struct { + LinksUpper struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + } `json:"Links"` + }{ + LinksUpper: struct { + Drives []struct { + URL string `json:"@odata.id"` + } `json:"Drives"` + }{ + Drives: []struct { + URL string `json:"@odata.id"` + }{ + { + URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", + }, + }, + }, + })) + return + } + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Unknown path - please create test case(s) for it")) + })) + defer server.Close() + + ctx := context.Background() + assert := assert.New(t) + + logicalDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportLogicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + physDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportPhysicalDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + nvmeDevMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportNVMeDriveMetrics(payload) + if err != nil { + return err + } + return nil + } + + tests := []struct { + name string + uri string + metricName string + metricRef1 string + metricRef2 string + exportFunc func(*Exporter, []byte) error + payload []byte + expected string + }{ + { + name: "Good Logical Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "xl420_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveUpper, + expected: GoodLogicalDriveUpperResponse, + }, + { + name: "Good Logical Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "xl420_logical_drive_status", + metricRef1: "logicalDriveMetrics", + metricRef2: "raidStatus", + exportFunc: logicalDevMetrics, + payload: GoodLogicalDriveLower, + expected: GoodLogicalDriveLowerResponse, + }, + { + name: "Good Disk Drive Links Uppercase", + uri: "/redfish/v1/good", + metricName: "xl420_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveUpper, + expected: GoodDiskDriveUpperResponse, + }, + { + name: "Good Disk Drive Links Lowercase", + uri: "/redfish/v1/good", + metricName: "xl420_disk_drive_status", + metricRef1: "diskDriveMetrics", + metricRef2: "driveStatus", + exportFunc: physDevMetrics, + payload: GoodDiskDriveLower, + expected: GoodDiskDriveLowerResponse, + }, + { + name: "Good Nvme Drive", + uri: "/redfish/v1/good", + metricName: "xl420_nvme_drive_status", + metricRef1: "nvmeMetrics", + metricRef2: "nvmeDriveStatus", + exportFunc: nvmeDevMetrics, + payload: GoodNvmeDrive, + expected: GoodNvmeDriveResponse, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var exporter prometheus.Collector + var err error + exporter, err = NewExporter(ctx, server.URL, test.uri, "") + assert.Nil(err) + assert.NotNil(exporter) + + prometheus.MustRegister(exporter) + + err = test.exportFunc(exporter.(*Exporter), test.payload) + if err != nil { + t.Error(err) + } + + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + + assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) + + prometheus.Unregister(exporter) + + }) + } +} diff --git a/hpe/xl420/memory.go b/hpe/xl420/memory.go deleted file mode 100644 index e9511f7..0000000 --- a/hpe/xl420/memory.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -// /redfish/v1/systems/1/ - -// MemoryMetrics is the top level json object for XL420 Memory metadata -type MemoryMetrics struct { - ID string `json:"Id"` - MemorySummary MemorySummary `json:"MemorySummary"` -} - -// MemorySummary is the json object for XL420 MemorySummary metadata -type MemorySummary struct { - Status StatusMemory `json:"Status"` - TotalSystemMemoryGiB int `json:"TotalSystemMemoryGiB"` - TotalSystemPersistentMemoryGiB int `json:"TotalSystemPersistentMemoryGiB"` -} - -// StatusMemory is the variable to determine if the memory is OK or not -type StatusMemory struct { - HealthRollup string `json:"HealthRollup"` -} diff --git a/hpe/xl420/metrics.go b/hpe/xl420/metrics.go index dc4ed6a..bb6eb03 100644 --- a/hpe/xl420/metrics.go +++ b/hpe/xl420/metrics.go @@ -40,37 +40,43 @@ func NewDeviceMetrics() *map[string]*metrics { } ThermalMetrics = &metrics{ - "fanSpeed": newServerMetric("xl420_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name"}), - "fanStatus": newServerMetric("xl420_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name"}), - "sensorTemperature": newServerMetric("xl420_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name"}), - "sensorStatus": newServerMetric("xl420_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name"}), + "fanSpeed": newServerMetric("xl420_thermal_fan_speed", "Current fan speed in the unit of percentage, possible values are 0 - 100", nil, []string{"name", "chassisSerialNumber"}), + "fanStatus": newServerMetric("xl420_thermal_fan_status", "Current fan status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "sensorTemperature": newServerMetric("xl420_thermal_sensor_temperature", "Current sensor temperature reading in Celsius", nil, []string{"name", "chassisSerialNumber"}), + "sensorStatus": newServerMetric("xl420_thermal_sensor_status", "Current sensor status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), } PowerMetrics = &metrics{ - "supplyOutput": newServerMetric("xl420_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "sparePartNumber"}), - "supplyStatus": newServerMetric("xl420_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "sparePartNumber"}), - "supplyTotalConsumed": newServerMetric("xl420_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId"}), - "supplyTotalCapacity": newServerMetric("xl420_power_supply_total_capacity", "Total output capacity of all the power supplies", nil, []string{"memberId"}), + "voltageOutput": newServerMetric("xl420_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), + "voltageStatus": newServerMetric("xl420_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), + "supplyOutput": newServerMetric("xl420_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyStatus": newServerMetric("xl420_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyTotalConsumed": newServerMetric("xl420_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } // Splitting out the three different types of drives to gather metrics on each (NVMe, Disk Drive, and Logical Drive) // NVMe Drive Metrics NVMeDriveMetrics = &metrics{ - "nvmeDriveStatus": newServerMetric("xl420_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"protocol", "id", "serviceLabel"}), + "nvmeDriveStatus": newServerMetric("xl420_nvme_drive_status", "Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"chassisSerialNumber", "protocol", "id", "serviceLabel"}), } // Phyiscal Storage Disk Drive Metrics DiskDriveMetrics = &metrics{ - "driveStatus": newServerMetric("xl420_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "id", "location", "serialnumber"}), // DiskDriveStatus values + "driveStatus": newServerMetric("xl420_disk_drive_status", "Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "id", "location", "serialnumber"}), // DiskDriveStatus values } // Logical Disk Drive Metrics LogicalDriveMetrics = &metrics{ - "raidStatus": newServerMetric("xl420_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value + "raidStatus": newServerMetric("xl420_logical_drive_status", "Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED", nil, []string{"name", "chassisSerialNumber", "logicaldrivename", "volumeuniqueidentifier", "raid"}), // Logical Drive Raid value } MemoryMetrics = &metrics{ - "memoryStatus": newServerMetric("xl420_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"totalSystemMemoryGiB"}), + "memoryStatus": newServerMetric("xl420_memory_status", "Current memory status 1 = OK, 0 = BAD", nil, []string{"chassisSerialNumber", "totalSystemMemoryGiB"}), + "memoryDimmStatus": newServerMetric("xl420_memory_dimm_status", "Current dimm status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "capacityMiB", "manufacturer", "partNumber", "serialNumber"}), + } + + DeviceMetrics = &metrics{ + "deviceInfo": newServerMetric("device_info", "Current snapshot of device firmware information", nil, []string{"name", "chassisSerialNumber", "firmwareVersion", "biosVersion", "model"}), } Metrics = &map[string]*metrics{ @@ -81,6 +87,7 @@ func NewDeviceMetrics() *map[string]*metrics { "diskDriveMetrics": DiskDriveMetrics, "logicalDriveMetrics": LogicalDriveMetrics, "memoryMetrics": MemoryMetrics, + "deviceInfo": DeviceMetrics, } ) diff --git a/hpe/xl420/network_adapter.go b/hpe/xl420/network_adapter.go deleted file mode 100644 index f326931..0000000 --- a/hpe/xl420/network_adapter.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -// /redfish/v1/Systems/1/BaseNetworkAdapters - -// NetworkAdapter is the top level json object for XL420 Network Adapter metadata -type NetworkAdapter struct { - ID string `json:"Id"` - Firmware Firmware `json:"Firmware"` - Name string `json:"Name"` - PartNumber string `json:"PartNumber"` - PhysicalPorts []PhysicalPorts `json:"PhysicalPorts"` - SerialNumber string `json:"SerialNumber"` - StructuredName string `json:"StructuredName"` - Status Status `json:"Status"` - UEFIDevicePath string `json:"UEFIDevicePath"` -} - -// Firmware is the top level json object for XL420 Network Adapter metadata -type Firmware struct { - Current FirmwareCurrent `json:"Current"` -} - -// FirmwareCurrent contains the version in string format -type FirmwareCurrent struct { - Version string `json:"VersionString"` -} - -// PhysicalPorts contains the metadata for the Chassis NICs -type PhysicalPorts struct { - FullDuplex bool `json:"FullDuplex"` - IPv4Addresses []Addr `json:"IPv4Addresses"` - IPv6Addresses []Addr `json:"IPv6Addresses"` - LinkStatus string `json:"LinkStatus"` - MacAddress string `json:"MacAddress"` - Name string `json:"Name"` - SpeedMbps int `json:"SpeedMbps"` - Status Status `json:"Status"` -} - -// Addr contains the IPv4 or IPv6 Address in string format -type Addr struct { - Address string `json:"Address"` -} - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/hpe/xl420/power.go b/hpe/xl420/power.go deleted file mode 100644 index ce76ae5..0000000 --- a/hpe/xl420/power.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -// /redfish/v1/Chassis/1/Power/ - -// PowerMetrics is the top level json object for Power metadata -type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` -} - -// PowerControl is the top level json object for metadata on power supply consumption -type PowerControl struct { - MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` - PowerMetrics PowerMetric `json:"PowerMetrics"` -} - -// PowerMetric contains avg/min/max power metadata -type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` -} - -// PowerSupply is the top level json object for metadata on power supply product info -type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` -} - -// OemPower is the top level json object for historical data for wattage -type OemPower struct { - Hpe Hpe `json:"Hpe"` - Hp Hpe `json:"Hp"` -} - -// Hpe contains metadata on power supply product info -type Hpe struct { - AveragePowerOutputWatts int `json:"AveragePowerOutputWatts"` - BayNumber int `json:"BayNumber"` - HotplugCapable bool `json:"HotplugCapable"` - MaxPowerOutputWatts int `json:"MaxPowerOutputWatts"` - Mismatched bool `json:"Mismatched"` - PowerSupplyStatus Status `json:"PowerSupplyStatus"` - IPDUCapable bool `json:"iPDUCapable"` -} diff --git a/hpe/xl420/thermal.go b/hpe/xl420/thermal.go deleted file mode 100644 index c3151aa..0000000 --- a/hpe/xl420/thermal.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package xl420 - -// /redfish/v1/Chassis/1/Thermal/ - -// ThermalMetrics is the top level json object for XL420 Thermal metadata -type ThermalMetrics struct { - ID string `json:"Id"` - Fans []Fan `json:"Fans"` - Name string `json:"Name"` - Temperatures []Temperature `json:"Temperatures"` -} - -// Fan is the json object for a XL420 fan module -type Fan struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - FanName string `json:"FanName"` - Reading int `json:"Reading"` - CurrentReading int `json:"CurrentReading"` - ReadingUnits string `json:"ReadingUnits"` - Status StatusThermal `json:"Status"` -} - -// StatusThermal is the variable to determine if a fan or temperature sensor module is OK or not -type StatusThermal struct { - Health string `json:"Health"` - State string `json:"State"` -} - -// Temperature is the json object for a XL420 temperature sensor module -type Temperature struct { - MemberID string `json:"MemberId"` - Name string `json:"Name"` - PhysicalContext string `json:"PhysicalContext"` - ReadingCelsius int `json:"ReadingCelsius"` - SensorNumber int `json:"SensorNumber"` - Status StatusThermal `json:"Status"` - UpperThresholdCritical int `json:"UpperThresholdCritical"` - UpperThresholdFatal int `json:"UpperThresholdFatal"` -} diff --git a/cisco/s3260m4/chassis.go b/oem/chassis.go similarity index 91% rename from cisco/s3260m4/chassis.go rename to oem/chassis.go index 3f772d5..df8c963 100644 --- a/cisco/s3260m4/chassis.go +++ b/oem/chassis.go @@ -1,5 +1,5 @@ /* - * Copyright 2023 Comcast Cable Communications Management, LLC + * Copyright 2024 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,14 @@ * limitations under the License. */ -package s3260m4 +package oem import ( "bytes" "encoding/json" ) -// /redfish/v1/Managers/BMC2 +// /redfish/v1/Managers/XX/ // Chassis contains the Model Number, Firmware, etc of the chassis type Chassis struct { @@ -42,7 +42,7 @@ type ServerManagerURLWrapper struct { } func (w *ServerManagerURLWrapper) UnmarshalJSON(data []byte) error { - // because of a change in output betwen s3260m4 firmware versions we need to account for this + // because of a change in output betwen c220 firmware versions we need to account for this if bytes.Compare([]byte("[{"), data[0:2]) == 0 { var serMgrTmp []struct { Url string `json:"@odata.id,omitempty"` diff --git a/oem/common.go b/oem/common.go deleted file mode 100644 index e0be1bc..0000000 --- a/oem/common.go +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024 Comcast Cable Communications Management, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package oem - -// Status contains metadata for the health of a particular component/module -type Status struct { - Health string `json:"Health"` - State string `json:"State,omitempty"` -} diff --git a/oem/memory.go b/oem/memory.go index e111aee..359e24e 100644 --- a/oem/memory.go +++ b/oem/memory.go @@ -16,22 +16,23 @@ package oem -// /redfish/v1/systems/1/ // /redfish/v1/Systems/XXXXX/Memory/DIMM_X1 -// MemoryMetrics is the top level json object for UCS S3260 M4 Memory metadata -// type MemoryMetrics struct { -// Name string `json:"Name"` -// CapacityMiB interface{} `json:"CapacityMiB"` -// Manufacturer string `json:"Manufacturer"` -// MemoryDeviceType string `json:"MemoryDeviceType"` -// PartNumber string `json:"PartNumber"` -// SerialNumber string `json:"SerialNumber"` -// Status interface{} `json:"Status"` -// } - -// MemoryMetrics is the top level json object for Memory metadata +// MemoryMetrics is the top level json object for a Memory DIMMs metadata type MemoryMetrics struct { + Name string `json:"Name"` + CapacityMiB interface{} `json:"CapacityMiB"` + Manufacturer string `json:"Manufacturer"` + MemoryDeviceType string `json:"MemoryDeviceType"` + PartNumber string `json:"PartNumber"` + SerialNumber string `json:"SerialNumber"` + Status interface{} `json:"Status"` +} + +// /redfish/v1/systems/1/ + +// MemorySummaryMetrics is the top level json object for all Memory DIMMs metadata +type MemorySummaryMetrics struct { ID string `json:"Id"` MemorySummary MemorySummary `json:"MemorySummary"` } diff --git a/oem/power.go b/oem/power.go index dfe1e86..d2fd1a6 100644 --- a/oem/power.go +++ b/oem/power.go @@ -16,48 +16,85 @@ package oem +import ( + "bytes" + "encoding/json" +) + // /redfish/v1/Chassis/X/Power/ // PowerMetrics is the top level json object for Power metadata type PowerMetrics struct { - ID string `json:"Id"` - Name string `json:"Name"` - PowerControl []PowerControl `json:"PowerControl"` - PowerSupplies []PowerSupply `json:"PowerSupplies"` + ID string `json:"Id"` + Name string `json:"Name"` + PowerControl PowerControlWrapper `json:"PowerControl"` + PowerSupplies []PowerSupply `json:"PowerSupplies"` + Voltages []Voltages `json:"Voltages,omitempty"` } // PowerControl is the top level json object for metadata on power supply consumption type PowerControl struct { MemberID string `json:"MemberId"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerConsumedWatts int `json:"PowerConsumedWatts"` + PowerCapacityWatts int `json:"PowerCapacityWatts,omitempty"` + PowerConsumedWatts interface{} `json:"PowerConsumedWatts"` PowerMetrics PowerMetric `json:"PowerMetrics"` } +type PowerControlSlice struct { + PowerControl []PowerControl +} + +type PowerControlWrapper struct { + PowerControlSlice +} + +func (w *PowerControlWrapper) UnmarshalJSON(data []byte) error { + // because of a change in output betwen firmware versions we need to account for this + if bytes.Compare([]byte("{"), data[0:1]) == 0 { + var powCtlSlice PowerControl + err := json.Unmarshal(data, &powCtlSlice) + if err != nil { + return err + } + s := make([]PowerControl, 0) + s = append(s, powCtlSlice) + w.PowerControl = s + return nil + } + return json.Unmarshal(data, &w.PowerControl) +} + // PowerMetric contains avg/min/max power metadata type PowerMetric struct { - AverageConsumedWatts int `json:"AverageConsumedWatts"` - IntervalInMin int `json:"IntervalInMin"` - MaxConsumedWatts int `json:"MaxConsumedWatts"` - MinConsumedWatts int `json:"MinConsumedWatts"` + AverageConsumedWatts interface{} `json:"AverageConsumedWatts"` + IntervalInMin interface{} `json:"IntervalInMin,omitempty"` + MaxConsumedWatts interface{} `json:"MaxConsumedWatts"` + MinConsumedWatts interface{} `json:"MinConsumedWatts"` } // PowerSupply is the top level json object for metadata on power supply product info type PowerSupply struct { - FirmwareVersion string `json:"FirmwareVersion"` - LastPowerOutputWatts int `json:"LastPowerOutputWatts"` - LineInputVoltage int `json:"LineInputVoltage"` - LineInputVoltageType string `json:"LineInputVoltageType"` - Manufacturer string `json:"Manufacturer"` - MemberID string `json:"MemberId"` - Model string `json:"Model"` - Name string `json:"Name"` - Oem OemPower `json:"Oem"` - PowerCapacityWatts int `json:"PowerCapacityWatts"` - PowerSupplyType string `json:"PowerSupplyType"` - SerialNumber string `json:"SerialNumber"` - SparePartNumber string `json:"SparePartNumber"` - Status Status `json:"Status"` + FirmwareVersion string `json:"FirmwareVersion"` + LastPowerOutputWatts interface{} `json:"LastPowerOutputWatts"` + LineInputVoltage interface{} `json:"LineInputVoltage"` + LineInputVoltageType string `json:"LineInputVoltageType,omitempty"` + InputRanges []InputRange `json:"InputRanges,omitempty"` + Manufacturer string `json:"Manufacturer"` + MemberID string `json:"MemberId"` + Model string `json:"Model"` + Name string `json:"Name"` + Oem OemPower `json:"Oem,omitempty"` + PowerCapacityWatts int `json:"PowerCapacityWatts,omitempty"` + PowerSupplyType string `json:"PowerSupplyType"` + SerialNumber string `json:"SerialNumber"` + SparePartNumber string `json:"SparePartNumber"` + Status Status `json:"Status"` +} + +// InputRange is the top level json object for input voltage metadata +type InputRange struct { + InputType string `json:"InputType,omitempty"` + OutputWattage int `json:"OutputWattage"` } // OemPower is the top level json object for historical data for wattage @@ -76,3 +113,11 @@ type Hpe struct { PowerSupplyStatus Status `json:"PowerSupplyStatus"` IPDUCapable bool `json:"iPDUCapable"` } + +// Voltages contains current/lower/upper voltage and power supply status metadata +type Voltages struct { + Name string `json:"Name"` + ReadingVolts interface{} `json:"ReadingVolts"` + Status Status `json:"Status"` + UpperThresholdCritical interface{} `json:"UpperThresholdCritical"` +} diff --git a/cisco/c220/processor.go b/oem/processor.go similarity index 92% rename from cisco/c220/processor.go rename to oem/processor.go index 4d69bac..2e3f6bf 100644 --- a/cisco/c220/processor.go +++ b/oem/processor.go @@ -14,11 +14,11 @@ * limitations under the License. */ -package c220 +package oem // /redfish/v1/Systems/XXXXX/Processors/CPUX -// ProcessorMetrics is the top level json object for UCS C220 Processor metadata +// ProcessorMetrics is the top level json object for Processor metadata type ProcessorMetrics struct { Name string `json:"Name"` Description string `json:"Description"` diff --git a/oem/storage_ctrl.go b/oem/storage_ctrl.go index ce93cae..b97ce5a 100644 --- a/oem/storage_ctrl.go +++ b/oem/storage_ctrl.go @@ -1,5 +1,5 @@ /* - * Copyright 2023 Comcast Cable Communications Management, LLC + * Copyright 2024 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ type StorageControllerMetrics struct { 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"` From 0fcd4b20629d2586d10bf6a2f68036a10943ebc1 Mon Sep 17 00:00:00 2001 From: Derrick DaCosta Date: Mon, 1 Apr 2024 15:28:37 -0400 Subject: [PATCH 3/4] fix metric labels for power supplies, add more test cases around metrics handling --- hpe/dl20/metrics.go | 4 +- hpe/dl360/exporter.go | 1 - hpe/dl360/exporter_test.go | 753 ++++++++++++++++++++++++++++++++++++- hpe/dl360/metrics.go | 4 +- hpe/dl380/exporter_test.go | 557 +++++---------------------- hpe/dl380/metrics.go | 4 +- hpe/dl560/exporter_test.go | 557 +++++---------------------- hpe/dl560/metrics.go | 4 +- hpe/xl420/exporter.go | 1 - hpe/xl420/exporter_test.go | 557 +++++---------------------- hpe/xl420/metrics.go | 4 +- 11 files changed, 1046 insertions(+), 1400 deletions(-) diff --git a/hpe/dl20/metrics.go b/hpe/dl20/metrics.go index a915abf..7c7598d 100644 --- a/hpe/dl20/metrics.go +++ b/hpe/dl20/metrics.go @@ -49,8 +49,8 @@ func NewDeviceMetrics() *map[string]*metrics { PowerMetrics = &metrics{ "voltageOutput": newServerMetric("dl20_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), "voltageStatus": newServerMetric("dl20_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "supplyOutput": newServerMetric("dl20_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl20_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyOutput": newServerMetric("dl20_power_supply_output", "Power supply output in watts", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), + "supplyStatus": newServerMetric("dl20_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), "supplyTotalConsumed": newServerMetric("dl20_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } diff --git a/hpe/dl360/exporter.go b/hpe/dl360/exporter.go index 201b9e5..eb3a07a 100644 --- a/hpe/dl360/exporter.go +++ b/hpe/dl360/exporter.go @@ -328,7 +328,6 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e } tasks = append(tasks, - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", STORAGEBATTERY, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Managers/1/", ILOSELFTEST, target, profile, retryClient))) diff --git a/hpe/dl360/exporter_test.go b/hpe/dl360/exporter_test.go index f56cf1c..8e75e43 100644 --- a/hpe/dl360/exporter_test.go +++ b/hpe/dl360/exporter_test.go @@ -35,6 +35,16 @@ const ( # TYPE up gauge up 2 ` + GoodDeviceInfoExpected = ` + # HELP device_info Current snapshot of device firmware information + # TYPE device_info gauge + device_info{biosVersion="U99 v0.00 (xx/xx/xxxx)",chassisSerialNumber="SN98765",firmwareVersion="iLO 5 v2.65",model="DL360",name="test description"} 1 + ` + GoodCPUStatusExpected = ` + # HELP dl360_cpu_status Current cpu status 1 = OK, 0 = BAD + # TYPE dl360_cpu_status gauge + dl360_cpu_status{chassisSerialNumber="SN98765",id="1",model="cpu model",socket="Proc 1",totalCores="99"} 1 + ` GoodLogicalDriveExpected = ` # HELP dl360_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl360_logical_drive_status gauge @@ -50,6 +60,71 @@ const ( # TYPE dl360_nvme_drive_status gauge dl360_nvme_drive_status{chassisSerialNumber="SN98765",id="DA000000",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 ` + GoodILOSelfTestExpected = ` + # HELP dl360_ilo_selftest_status Current ilo selftest status 1 = OK, 0 = BAD + # TYPE dl360_ilo_selftest_status gauge + dl360_ilo_selftest_status{chassisSerialNumber="SN98765",name="EEPROM"} 1 + ` + GoodStorageBatteryStatusExpected = ` + # HELP dl360_storage_battery_status Current storage battery status 1 = OK, 0 = BAD + # TYPE dl360_storage_battery_status gauge + dl360_storage_battery_status{chassisSerialNumber="SN98765",id="1",model="battery model",name="HPE Smart Storage Battery ",serialnumber="123456789"} 1 + ` + GoodMemoryDimmExpected = ` + # HELP dl360_memory_dimm_status Current dimm status 1 = OK, 0 = BAD + # TYPE dl360_memory_dimm_status gauge + dl360_memory_dimm_status{capacityMiB="32768",chassisSerialNumber="SN98765",manufacturer="HPE",name="proc1dimm1",partNumber="part number",serialNumber="123456789"} 1 + ` + GoodMemorySummaryExpected = ` + # HELP dl360_memory_status Current memory status 1 = OK, 0 = BAD + # TYPE dl360_memory_status gauge + dl360_memory_status{chassisSerialNumber="SN98765",totalSystemMemoryGiB="384"} 1 + ` + GoodThermalFanSpeedExpected = ` + # HELP dl360_thermal_fan_speed Current fan speed in the unit of percentage, possible values are 0 - 100 + # TYPE dl360_thermal_fan_speed gauge + dl360_thermal_fan_speed{chassisSerialNumber="SN98765",name="Fan 1"} 16 + ` + GoodThermalFanStatusExpected = ` + # HELP dl360_thermal_fan_status Current fan status 1 = OK, 0 = BAD + # TYPE dl360_thermal_fan_status gauge + dl360_thermal_fan_status{chassisSerialNumber="SN98765",name="Fan 1"} 1 + ` + GoodThermalSensorStatusExpected = ` + # HELP dl360_thermal_sensor_status Current sensor status 1 = OK, 0 = BAD + # TYPE dl360_thermal_sensor_status gauge + dl360_thermal_sensor_status{chassisSerialNumber="SN98765",name="01-Inlet Ambient"} 1 + ` + GoodThermalSensorTempExpected = ` + # HELP dl360_thermal_sensor_temperature Current sensor temperature reading in Celsius + # TYPE dl360_thermal_sensor_temperature gauge + dl360_thermal_sensor_temperature{chassisSerialNumber="SN98765",name="01-Inlet Ambient"} 22 + ` + GoodPowerVoltageOutputExpected = ` + # HELP dl360_power_voltage_output Power voltage output in watts + # TYPE dl360_power_voltage_output gauge + dl360_power_voltage_output{chassisSerialNumber="SN98765",name="PSU1_VOUT"} 12.2 + ` + GoodPowerVoltageStatusExpected = ` + # HELP dl360_power_voltage_status Current power voltage status 1 = OK, 0 = BAD + # TYPE dl360_power_voltage_status gauge + dl360_power_voltage_status{chassisSerialNumber="SN98765",name="PSU1_VOUT"} 1 + ` + GoodPowerSupplyOutputExpected = ` + # HELP dl360_power_supply_output Power supply output in watts + # TYPE dl360_power_supply_output gauge + dl360_power_supply_output{chassisSerialNumber="SN98765",manufacturer="DELTA",model="psmodel",name="HpeServerPowerSupply",partNumber="part number",powerSupplyType="AC",serialNumber="123456789"} 91 + ` + GoodPowerSupplyStatusExpected = ` + # HELP dl360_power_supply_status Current power supply status 1 = OK, 0 = BAD + # TYPE dl360_power_supply_status gauge + dl360_power_supply_status{chassisSerialNumber="SN98765",manufacturer="DELTA",model="psmodel",name="HpeServerPowerSupply",partNumber="part number",powerSupplyType="AC",serialNumber="123456789"} 1 + ` + GoodPowerSupplyTotalConsumedExpected = ` + # HELP dl360_power_supply_total_consumed Total output of all power supplies in watts + # TYPE dl360_power_supply_total_consumed gauge + dl360_power_supply_total_consumed{chassisSerialNumber="SN98765",memberId="0"} 206 + ` ) type TestErrorResponse struct { @@ -335,6 +410,24 @@ func Test_DL360_Upper_Lower_Links(t *testing.T) { func Test_DL360_Metrics_Handling(t *testing.T) { + var GoodDeviceInfoResponse = []byte(`{ + "Description": "test description", + "FirmwareVersion": "iLO 5 v2.65", + "Model": "iLO 5" + }`) + var GoodCPUStatusResponse = []byte(`{ + "Id": "1", + "Model": "cpu model", + "Name": "Processors", + "ProcessorArchitecture": "x86", + "Socket": "Proc 1", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "TotalCores": 99, + "TotalThreads": 99 + }`) var GoodLogicalDriveResponse = []byte(`{ "Id": "1", "CapacityMiB": 915683, @@ -357,7 +450,7 @@ func Test_DL360_Metrics_Handling(t *testing.T) { "Description": "HPE Smart Storage Disk Drive View", "InterfaceType": "SATA", "Location": "1I:1:1", - "Model": "MK000960GWXFH", + "Model": "model name", "Name": "HpeSmartStorageDiskDrive", "SerialNumber": "ABC123", "Status": { @@ -370,7 +463,7 @@ func Test_DL360_Metrics_Handling(t *testing.T) { "CapacityBytes": 1600321314816, "FailurePredicted": false, "MediaType": "SSD", - "Model": "MO001600KXPTR", + "Model": "model name", "Name": "Secondary Storage Device", "Oem": { "Hpe": { @@ -379,7 +472,7 @@ func Test_DL360_Metrics_Handling(t *testing.T) { "Health": "OK", "State": "Enabled" }, - "NVMeId": "1c5c_MO001600KXPTR_KJC3N4902I2603P04" + "NVMeId": "drive id" } }, "PhysicalLocation": { @@ -389,6 +482,451 @@ func Test_DL360_Metrics_Handling(t *testing.T) { }, "Protocol": "NVMe" }`) + var GoodILOSelfTestResponse = []byte(`{ + "Oem": { + "Hpe": { + "iLOSelfTestResults": [ + { + "Notes": "", + "SelfTestName": "EEPROM", + "Status": "OK" + } + ] + } + } + }`) + var GoodStorageBatteryStatusResponse = []byte(`{ + "Oem": { + "Hp": { + "Battery": [ + { + "Condition": "Ok", + "ErrorCode": 0, + "FirmwareVersion": "1.1", + "Index": 1, + "MaxCapWatts": 96, + "Model": "battery model", + "Present": "Yes", + "ProductName": "HPE Smart Storage Battery ", + "SerialNumber": "123456789", + "Spare": "815983-001" + } + ] + } + } + }`) + var GoodMemoryDimmResponse = []byte(`{ + "CapacityMiB": 32768, + "Manufacturer": "HPE", + "MemoryDeviceType": "DDR4", + "Name": "proc1dimm1", + "PartNumber": "part number ", + "SerialNumber": "123456789", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }`) + var GoodMemorySummaryResponse = []byte(`{ + "Id": "1", + "MemorySummary": { + "Status": { + "HealthRollup": "OK" + }, + "TotalSystemMemoryGiB": 384, + "TotalSystemPersistentMemoryGiB": 0 + } + }`) + var GoodThermalFanSpeedResponse = []byte(`{ + "@odata.id": "/redfish/v1/Chassis/1/Thermal/", + "Id": "Thermal", + "Fans": [ + { + "MemberId": "0", + "Name": "Fan 1", + "Reading": 16, + "ReadingUnits": "Percent", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "Name": "Thermal", + "Temperatures": [ + { + "MemberId": "0", + "Name": "01-Inlet Ambient", + "PhysicalContext": "Intake", + "ReadingCelsius": 22, + "SensorNumber": 1, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 42, + "UpperThresholdFatal": 47 + } + ] + }`) + var GoodThermalFanStatusResponse = []byte(`{ + "@odata.id": "/redfish/v1/Chassis/1/Thermal/", + "Id": "Thermal", + "Fans": [ + { + "MemberId": "0", + "Name": "Fan 1", + "Reading": 18, + "ReadingUnits": "Percent", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "Name": "Thermal", + "Temperatures": [ + { + "MemberId": "0", + "Name": "01-Inlet Ambient", + "PhysicalContext": "Intake", + "ReadingCelsius": 22, + "SensorNumber": 1, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 42, + "UpperThresholdFatal": 47 + } + ] + }`) + var GoodThermalSensorStatusResponse = []byte(`{ + "@odata.id": "/redfish/v1/Chassis/1/Thermal/", + "Id": "Thermal", + "Fans": [ + { + "MemberId": "0", + "Name": "Fan 1", + "Reading": 18, + "ReadingUnits": "Percent", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "Name": "Thermal", + "Temperatures": [ + { + "MemberId": "0", + "Name": "01-Inlet Ambient", + "PhysicalContext": "Intake", + "ReadingCelsius": 22, + "SensorNumber": 1, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 42, + "UpperThresholdFatal": 47 + } + ] + }`) + var GoodThermalSensorTempResponse = []byte(`{ + "@odata.id": "/redfish/v1/Chassis/1/Thermal/", + "Id": "Thermal", + "Fans": [ + { + "MemberId": "0", + "Name": "Fan 1", + "Reading": 18, + "ReadingUnits": "Percent", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "Name": "Thermal", + "Temperatures": [ + { + "MemberId": "0", + "Name": "01-Inlet Ambient", + "PhysicalContext": "Intake", + "ReadingCelsius": 22, + "SensorNumber": 1, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 42, + "UpperThresholdFatal": 47 + } + ] + }`) + var GoodPowerVoltageOutputResponse = []byte(`{ + "PowerControl": [ + { + "PhysicalContext": "PowerSupply", + "PowerMetrics": { + "MinConsumedWatts": 266, + "AverageConsumedWatts": 358, + "MaxConsumedWatts": 614 + }, + "MemberId": "1", + "PowerConsumedWatts": 378 + } + ], + "Voltages": [ + { + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "UpperThresholdCritical": 14, + "Name": "PSU1_VOUT", + "ReadingVolts": 12.2 + } + ], + "Id": "Power", + "PowerSupplies": [ + { + "SerialNumber": "123456789", + "InputRanges": [ + { + "InputType": "AC", + "OutputWattage": 1050 + } + ], + "FirmwareVersion": "123", + "PowerOutputWatts": 160, + "LineInputVoltage": 202, + "Name": "PSU1", + "Status": { + "State": "Enabled" + }, + "PowerInputWatts": 180, + "Manufacturer": "DELTA", + "LastPowerOutputWatts": 160, + "MemberId": "1", + "PartNumber": "part number", + "PowerSupplyType": "AC", + "Model": "psmodel", + "SparePartNumber": "part number" + } + ], + "Name": "Power" + }`) + var GoodPowerVoltageStatusResponse = []byte(`{ + "PowerControl": [ + { + "PhysicalContext": "PowerSupply", + "PowerMetrics": { + "MinConsumedWatts": 266, + "AverageConsumedWatts": 358, + "MaxConsumedWatts": 614 + }, + "MemberId": "1", + "PowerConsumedWatts": 378 + } + ], + "Voltages": [ + { + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "UpperThresholdCritical": 14, + "Name": "PSU1_VOUT", + "ReadingVolts": 12.2 + } + ], + "Id": "Power", + "PowerSupplies": [ + { + "SerialNumber": "123456789", + "InputRanges": [ + { + "InputType": "AC", + "OutputWattage": 1050 + } + ], + "FirmwareVersion": "123", + "PowerOutputWatts": 160, + "LineInputVoltage": 202, + "Name": "PSU1", + "Status": { + "State": "Enabled" + }, + "PowerInputWatts": 180, + "Manufacturer": "DELTA", + "LastPowerOutputWatts": 160, + "MemberId": "1", + "PartNumber": "part number", + "PowerSupplyType": "AC", + "Model": "psmodel", + "SparePartNumber": "part number" + } + ], + "Name": "Power" + }`) + var GoodPowerSupplyOutputResponse = []byte(`{ + "Id": "Power", + "Name": "PowerMetrics", + "PowerControl": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerControl/0", + "MemberId": "0", + "PowerCapacityWatts": 1600, + "PowerConsumedWatts": 206, + "PowerMetrics": { + "AverageConsumedWatts": 207, + "IntervalInMin": 20, + "MaxConsumedWatts": 282, + "MinConsumedWatts": 205 + } + } + ], + "PowerSupplies": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerSupplies/0", + "FirmwareVersion": "2.04", + "LastPowerOutputWatts": 91, + "LineInputVoltage": 206, + "LineInputVoltageType": "ACHighLine", + "Manufacturer": "DELTA", + "MemberId": "0", + "Model": "psmodel", + "Name": "HpeServerPowerSupply", + "Oem": { + "Hpe": { + "AveragePowerOutputWatts": 91, + "BayNumber": 1, + "HotplugCapable": true, + "MaxPowerOutputWatts": 93, + "Mismatched": false, + "PowerSupplyStatus": { + "State": "Ok" + }, + "iPDUCapable": false + } + }, + "PowerCapacityWatts": 800, + "PowerSupplyType": "AC", + "SerialNumber": "123456789", + "SparePartNumber": "part number", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ] + }`) + var GoodPowerSupplyStatusResponse = []byte(`{ + "Id": "Power", + "Name": "PowerMetrics", + "PowerControl": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerControl/0", + "MemberId": "0", + "PowerCapacityWatts": 1600, + "PowerConsumedWatts": 206, + "PowerMetrics": { + "AverageConsumedWatts": 207, + "IntervalInMin": 20, + "MaxConsumedWatts": 282, + "MinConsumedWatts": 205 + } + } + ], + "PowerSupplies": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerSupplies/0", + "FirmwareVersion": "2.04", + "LastPowerOutputWatts": 91, + "LineInputVoltage": 206, + "LineInputVoltageType": "ACHighLine", + "Manufacturer": "DELTA", + "MemberId": "0", + "Model": "psmodel", + "Name": "HpeServerPowerSupply", + "Oem": { + "Hpe": { + "AveragePowerOutputWatts": 91, + "BayNumber": 1, + "HotplugCapable": true, + "MaxPowerOutputWatts": 93, + "Mismatched": false, + "PowerSupplyStatus": { + "State": "Ok" + }, + "iPDUCapable": false + } + }, + "PowerCapacityWatts": 800, + "PowerSupplyType": "AC", + "SerialNumber": "123456789", + "SparePartNumber": "part number", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ] + }`) + var GoodPowerSupplyTotalConsumedResponse = []byte(`{ + "Id": "Power", + "Name": "PowerMetrics", + "PowerControl": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerControl/0", + "MemberId": "0", + "PowerCapacityWatts": 1600, + "PowerConsumedWatts": 206, + "PowerMetrics": { + "AverageConsumedWatts": 207, + "IntervalInMin": 20, + "MaxConsumedWatts": 282, + "MinConsumedWatts": 205 + } + } + ], + "PowerSupplies": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Power/#PowerSupplies/0", + "FirmwareVersion": "2.04", + "LastPowerOutputWatts": 91, + "LineInputVoltage": 206, + "LineInputVoltageType": "ACHighLine", + "Manufacturer": "DELTA", + "MemberId": "0", + "Model": "psmodel", + "Name": "HpeServerPowerSupply", + "Oem": { + "Hpe": { + "AveragePowerOutputWatts": 91, + "BayNumber": 1, + "HotplugCapable": true, + "MaxPowerOutputWatts": 93, + "Mismatched": false, + "PowerSupplyStatus": { + "State": "Ok" + }, + "iPDUCapable": false + } + }, + "PowerCapacityWatts": 800, + "PowerSupplyType": "AC", + "SerialNumber": "123456789", + "SparePartNumber": "part number", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ] + }`) var exporter prometheus.Collector @@ -406,6 +944,22 @@ func Test_DL360_Metrics_Handling(t *testing.T) { prometheus.MustRegister(exporter) + deviceInfoMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportFirmwareMetrics(payload) + if err != nil { + return err + } + return nil + } + + processorMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportProcessorMetrics(payload) + if err != nil { + return err + } + return nil + } + logicalDevMetrics := func(exp *Exporter, payload []byte) error { err := exp.exportLogicalDriveMetrics(payload) if err != nil { @@ -430,6 +984,54 @@ func Test_DL360_Metrics_Handling(t *testing.T) { return nil } + iloSelfTestMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportIloSelfTest(payload) + if err != nil { + return err + } + return nil + } + + storBatterytMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportStorageBattery(payload) + if err != nil { + return err + } + return nil + } + + memDimmMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportMemoryMetrics(payload) + if err != nil { + return err + } + return nil + } + + memSummaryMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportMemorySummaryMetrics(payload) + if err != nil { + return err + } + return nil + } + + thermMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportThermalMetrics(payload) + if err != nil { + return err + } + return nil + } + + powMetrics := func(exp *Exporter, payload []byte) error { + err := exp.exportPowerMetrics(payload) + if err != nil { + return err + } + return nil + } + tests := []struct { name string metricName string @@ -439,6 +1041,24 @@ func Test_DL360_Metrics_Handling(t *testing.T) { response []byte expected string }{ + { + name: "Good Device Info", + metricName: "device_info", + metricRef1: "deviceInfo", + metricRef2: "deviceInfo", + handleFunc: deviceInfoMetrics, + response: GoodDeviceInfoResponse, + expected: GoodDeviceInfoExpected, + }, + { + name: "Good CPU Status", + metricName: "dl360_cpu_status", + metricRef1: "processorMetrics", + metricRef2: "processorStatus", + handleFunc: processorMetrics, + response: GoodCPUStatusResponse, + expected: GoodCPUStatusExpected, + }, { name: "Good Logical Drive", metricName: "dl360_logical_drive_status", @@ -466,21 +1086,138 @@ func Test_DL360_Metrics_Handling(t *testing.T) { response: GoodNvmeDriveResponse, expected: GoodNvmeDriveExpected, }, + { + name: "Good iLO Self Test", + metricName: "dl360_ilo_selftest_status", + metricRef1: "iloSelfTestMetrics", + metricRef2: "iloSelfTestStatus", + handleFunc: iloSelfTestMetrics, + response: GoodILOSelfTestResponse, + expected: GoodILOSelfTestExpected, + }, + { + name: "Good Storage Battery Status", + metricName: "dl360_storage_battery_status", + metricRef1: "storBatteryMetrics", + metricRef2: "storageBatteryStatus", + handleFunc: storBatterytMetrics, + response: GoodStorageBatteryStatusResponse, + expected: GoodStorageBatteryStatusExpected, + }, + { + name: "Good Memory DIMM Status", + metricName: "dl360_memory_dimm_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryDimmStatus", + handleFunc: memDimmMetrics, + response: GoodMemoryDimmResponse, + expected: GoodMemoryDimmExpected, + }, + { + name: "Good Memory Summary Status", + metricName: "dl360_memory_status", + metricRef1: "memoryMetrics", + metricRef2: "memoryStatus", + handleFunc: memSummaryMetrics, + response: GoodMemorySummaryResponse, + expected: GoodMemorySummaryExpected, + }, + { + name: "Good Thermal Fan Speed", + metricName: "dl360_thermal_fan_speed", + metricRef1: "thermalMetrics", + metricRef2: "fanSpeed", + handleFunc: thermMetrics, + response: GoodThermalFanSpeedResponse, + expected: GoodThermalFanSpeedExpected, + }, + { + name: "Good Thermal Fan Status", + metricName: "dl360_thermal_fan_status", + metricRef1: "thermalMetrics", + metricRef2: "fanStatus", + handleFunc: thermMetrics, + response: GoodThermalFanStatusResponse, + expected: GoodThermalFanStatusExpected, + }, + { + name: "Good Thermal Sensor Status", + metricName: "dl360_thermal_sensor_status", + metricRef1: "thermalMetrics", + metricRef2: "sensorStatus", + handleFunc: thermMetrics, + response: GoodThermalSensorStatusResponse, + expected: GoodThermalSensorStatusExpected, + }, + { + name: "Good Thermal Sensor Temperature", + metricName: "dl360_thermal_sensor_temperature", + metricRef1: "thermalMetrics", + metricRef2: "sensorTemperature", + handleFunc: thermMetrics, + response: GoodThermalSensorTempResponse, + expected: GoodThermalSensorTempExpected, + }, + { + name: "Good Power Voltage Output", + metricName: "dl360_power_voltage_output", + metricRef1: "powerMetrics", + metricRef2: "voltageOutput", + handleFunc: powMetrics, + response: GoodPowerVoltageOutputResponse, + expected: GoodPowerVoltageOutputExpected, + }, + { + name: "Good Power Voltage Status", + metricName: "dl360_power_voltage_status", + metricRef1: "powerMetrics", + metricRef2: "voltageStatus", + handleFunc: powMetrics, + response: GoodPowerVoltageStatusResponse, + expected: GoodPowerVoltageStatusExpected, + }, + { + name: "Good Power Supply Output", + metricName: "dl360_power_supply_output", + metricRef1: "powerMetrics", + metricRef2: "supplyOutput", + handleFunc: powMetrics, + response: GoodPowerSupplyOutputResponse, + expected: GoodPowerSupplyOutputExpected, + }, + { + name: "Good Power Supply Status", + metricName: "dl360_power_supply_status", + metricRef1: "powerMetrics", + metricRef2: "supplyStatus", + handleFunc: powMetrics, + response: GoodPowerSupplyStatusResponse, + expected: GoodPowerSupplyStatusExpected, + }, + { + name: "Good Power Supply Total Consumed", + metricName: "dl360_power_supply_total_consumed", + metricRef1: "powerMetrics", + metricRef2: "supplyTotalConsumed", + handleFunc: powMetrics, + response: GoodPowerSupplyTotalConsumedResponse, + expected: GoodPowerSupplyTotalConsumedExpected, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + // clear metric before each test + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + m.Reset() + err := test.handleFunc(exporter.(*Exporter), test.response) if err != nil { t.Error(err) } - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - m.Reset() }) } prometheus.Unregister(exporter) diff --git a/hpe/dl360/metrics.go b/hpe/dl360/metrics.go index fffb731..920c52b 100644 --- a/hpe/dl360/metrics.go +++ b/hpe/dl360/metrics.go @@ -49,8 +49,8 @@ func NewDeviceMetrics() *map[string]*metrics { PowerMetrics = &metrics{ "voltageOutput": newServerMetric("dl360_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), "voltageStatus": newServerMetric("dl360_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "supplyOutput": newServerMetric("dl360_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl360_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyOutput": newServerMetric("dl360_power_supply_output", "Power supply output in watts", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), + "supplyStatus": newServerMetric("dl360_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), "supplyTotalConsumed": newServerMetric("dl360_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } diff --git a/hpe/dl380/exporter_test.go b/hpe/dl380/exporter_test.go index 4808caf..554875a 100644 --- a/hpe/dl380/exporter_test.go +++ b/hpe/dl380/exporter_test.go @@ -30,237 +30,28 @@ import ( ) const ( - up2Response = ` + up2Expected = ` # HELP up was the last scrape of fishymetrics successful. # TYPE up gauge up 2 ` - GoodLogicalDriveUpperResponse = ` + GoodLogicalDriveExpected = ` # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl380_logical_drive_status gauge - dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + dl380_logical_drive_status{chassisSerialNumber="SN98765",logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 ` - GoodDiskDriveUpperResponse = ` + GoodDiskDriveExpected = ` # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl380_disk_drive_status gauge - dl380_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + dl380_disk_drive_status{chassisSerialNumber="SN98765",id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 ` - GoodLogicalDriveLowerResponse = ` - # HELP dl380_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_logical_drive_status gauge - dl380_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP dl380_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl380_disk_drive_status gauge - dl380_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` + GoodNvmeDriveExpected = ` # HELP dl380_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl380_nvme_drive_status gauge - dl380_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + dl380_nvme_drive_status{chassisSerialNumber="SN98765",id="DA000000",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 ` ) -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - type TestErrorResponse struct { Error TestError `json:"error"` } @@ -285,7 +76,7 @@ func MustMarshal(v interface{}) []byte { func Test_DL380_Exporter(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/badcred/Systems/1/SmartStorage/ArrayControllers/" { + if r.URL.Path == "/redfish/v1/badcred/Managers/1/" { w.WriteHeader(http.StatusUnauthorized) w.Write(MustMarshal(TestErrorResponse{ Error: TestError{ @@ -323,7 +114,7 @@ func Test_DL380_Exporter(t *testing.T) { metricName: "up", metricRef1: "up", metricRef2: "up", - expected: up2Response, + expected: up2Expected, }, } @@ -348,200 +139,79 @@ func Test_DL380_Exporter(t *testing.T) { } } -func Test_DL380_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() +func Test_DL380_Metrics_Handling(t *testing.T) { + + var GoodLogicalDriveResponse = []byte(`{ + "Id": "1", + "CapacityMiB": 915683, + "Description": "HPE Smart Storage Logical Drive View", + "InterfaceType": "SATA", + "LogicalDriveName": "TESTDRIVE NAME 1", + "LogicalDriveNumber": 1, + "Name": "HpeSmartStorageLogicalDrive", + "Raid": "1", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "StripeSizeBytes": 262144, + "VolumeUniqueIdentifier": "ABCDEF12345" + }`) + var GoodDiskDriveResponse = []byte(`{ + "Id": "0", + "CapacityMiB": 915715, + "Description": "HPE Smart Storage Disk Drive View", + "InterfaceType": "SATA", + "Location": "1I:1:1", + "Model": "model name", + "Name": "HpeSmartStorageDiskDrive", + "SerialNumber": "ABC123", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }`) + var GoodNvmeDriveResponse = []byte(`{ + "Id": "DA000000", + "CapacityBytes": 1600321314816, + "FailurePredicted": false, + "MediaType": "SSD", + "Model": "model name", + "Name": "Secondary Storage Device", + "Oem": { + "Hpe": { + "CurrentTemperatureCelsius": 33, + "DriveStatus": { + "Health": "OK", + "State": "Enabled" + }, + "NVMeId": "drive id" + } + }, + "PhysicalLocation": { + "PartLocation": { + "ServiceLabel": "Box 3:Bay 7" + } + }, + "Protocol": "NVMe" + }`) + + var exporter prometheus.Collector - ctx := context.Background() assert := assert.New(t) + metrx := NewDeviceMetrics() + + exporter = &Exporter{ + ctx: context.Background(), + host: "fishymetrics.com", + biosVersion: "U99 v0.00 (xx/xx/xxxx)", + chassisSerialNumber: "SN98765", + deviceMetrics: metrx, + } + + prometheus.MustRegister(exporter) + logicalDevMetrics := func(exp *Exporter, payload []byte) error { err := exp.exportLogicalDriveMetrics(payload) if err != nil { @@ -568,88 +238,55 @@ func Test_DL380_Drives(t *testing.T) { tests := []struct { name string - uri string metricName string metricRef1 string metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte + handleFunc func(*Exporter, []byte) error + response []byte expected string }{ { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", + name: "Good Logical Drive", metricName: "dl380_logical_drive_status", metricRef1: "logicalDriveMetrics", metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, + handleFunc: logicalDevMetrics, + response: GoodLogicalDriveResponse, + expected: GoodLogicalDriveExpected, }, { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl380_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl380_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", + name: "Good Disk Drive", metricName: "dl380_disk_drive_status", metricRef1: "diskDriveMetrics", metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, + handleFunc: physDevMetrics, + response: GoodDiskDriveResponse, + expected: GoodDiskDriveExpected, }, { name: "Good Nvme Drive", - uri: "/redfish/v1/good", metricName: "dl380_nvme_drive_status", metricRef1: "nvmeMetrics", metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, + handleFunc: nvmeDevMetrics, + response: GoodNvmeDriveResponse, + expected: GoodNvmeDriveExpected, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + m.Reset() - err = test.exportFunc(exporter.(*Exporter), test.payload) + err := test.handleFunc(exporter.(*Exporter), test.response) if err != nil { t.Error(err) } - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - }) } + prometheus.Unregister(exporter) } diff --git a/hpe/dl380/metrics.go b/hpe/dl380/metrics.go index 46011bc..f2f9c53 100644 --- a/hpe/dl380/metrics.go +++ b/hpe/dl380/metrics.go @@ -49,8 +49,8 @@ func NewDeviceMetrics() *map[string]*metrics { PowerMetrics = &metrics{ "voltageOutput": newServerMetric("dl380_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), "voltageStatus": newServerMetric("dl380_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "supplyOutput": newServerMetric("dl380_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl380_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyOutput": newServerMetric("dl380_power_supply_output", "Power supply output in watts", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), + "supplyStatus": newServerMetric("dl380_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), "supplyTotalConsumed": newServerMetric("dl380_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } diff --git a/hpe/dl560/exporter_test.go b/hpe/dl560/exporter_test.go index c74bd47..424c2af 100644 --- a/hpe/dl560/exporter_test.go +++ b/hpe/dl560/exporter_test.go @@ -30,237 +30,28 @@ import ( ) const ( - up2Response = ` + up2Expected = ` # HELP up was the last scrape of fishymetrics successful. # TYPE up gauge up 2 ` - GoodLogicalDriveUpperResponse = ` + GoodLogicalDriveExpected = ` # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl560_logical_drive_status gauge - dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + dl560_logical_drive_status{chassisSerialNumber="SN98765",logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 ` - GoodDiskDriveUpperResponse = ` + GoodDiskDriveExpected = ` # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl560_disk_drive_status gauge - dl560_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + dl560_disk_drive_status{chassisSerialNumber="SN98765",id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 ` - GoodLogicalDriveLowerResponse = ` - # HELP dl560_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_logical_drive_status gauge - dl560_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP dl560_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE dl560_disk_drive_status gauge - dl560_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` + GoodNvmeDriveExpected = ` # HELP dl560_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE dl560_nvme_drive_status gauge - dl560_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + dl560_nvme_drive_status{chassisSerialNumber="SN98765",id="DA000000",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 ` ) -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - type TestErrorResponse struct { Error TestError `json:"error"` } @@ -285,7 +76,7 @@ func MustMarshal(v interface{}) []byte { func Test_DL560_Exporter(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/badcred/Systems/1/SmartStorage/ArrayControllers/" { + if r.URL.Path == "/redfish/v1/badcred/Managers/1/" { w.WriteHeader(http.StatusUnauthorized) w.Write(MustMarshal(TestErrorResponse{ Error: TestError{ @@ -323,7 +114,7 @@ func Test_DL560_Exporter(t *testing.T) { metricName: "up", metricRef1: "up", metricRef2: "up", - expected: up2Response, + expected: up2Expected, }, } @@ -348,200 +139,79 @@ func Test_DL560_Exporter(t *testing.T) { } } -func Test_DL560_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() +func Test_DL560_Metrics_Handling(t *testing.T) { + + var GoodLogicalDriveResponse = []byte(`{ + "Id": "1", + "CapacityMiB": 915683, + "Description": "HPE Smart Storage Logical Drive View", + "InterfaceType": "SATA", + "LogicalDriveName": "TESTDRIVE NAME 1", + "LogicalDriveNumber": 1, + "Name": "HpeSmartStorageLogicalDrive", + "Raid": "1", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "StripeSizeBytes": 262144, + "VolumeUniqueIdentifier": "ABCDEF12345" + }`) + var GoodDiskDriveResponse = []byte(`{ + "Id": "0", + "CapacityMiB": 915715, + "Description": "HPE Smart Storage Disk Drive View", + "InterfaceType": "SATA", + "Location": "1I:1:1", + "Model": "model name", + "Name": "HpeSmartStorageDiskDrive", + "SerialNumber": "ABC123", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }`) + var GoodNvmeDriveResponse = []byte(`{ + "Id": "DA000000", + "CapacityBytes": 1600321314816, + "FailurePredicted": false, + "MediaType": "SSD", + "Model": "model name", + "Name": "Secondary Storage Device", + "Oem": { + "Hpe": { + "CurrentTemperatureCelsius": 33, + "DriveStatus": { + "Health": "OK", + "State": "Enabled" + }, + "NVMeId": "drive id" + } + }, + "PhysicalLocation": { + "PartLocation": { + "ServiceLabel": "Box 3:Bay 7" + } + }, + "Protocol": "NVMe" + }`) + + var exporter prometheus.Collector - ctx := context.Background() assert := assert.New(t) + metrx := NewDeviceMetrics() + + exporter = &Exporter{ + ctx: context.Background(), + host: "fishymetrics.com", + biosVersion: "U99 v0.00 (xx/xx/xxxx)", + chassisSerialNumber: "SN98765", + deviceMetrics: metrx, + } + + prometheus.MustRegister(exporter) + logicalDevMetrics := func(exp *Exporter, payload []byte) error { err := exp.exportLogicalDriveMetrics(payload) if err != nil { @@ -568,88 +238,55 @@ func Test_DL560_Drives(t *testing.T) { tests := []struct { name string - uri string metricName string metricRef1 string metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte + handleFunc func(*Exporter, []byte) error + response []byte expected string }{ { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", + name: "Good Logical Drive", metricName: "dl560_logical_drive_status", metricRef1: "logicalDriveMetrics", metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, + handleFunc: logicalDevMetrics, + response: GoodLogicalDriveResponse, + expected: GoodLogicalDriveExpected, }, { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "dl560_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "dl560_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", + name: "Good Disk Drive", metricName: "dl560_disk_drive_status", metricRef1: "diskDriveMetrics", metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, + handleFunc: physDevMetrics, + response: GoodDiskDriveResponse, + expected: GoodDiskDriveExpected, }, { name: "Good Nvme Drive", - uri: "/redfish/v1/good", metricName: "dl560_nvme_drive_status", metricRef1: "nvmeMetrics", metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, + handleFunc: nvmeDevMetrics, + response: GoodNvmeDriveResponse, + expected: GoodNvmeDriveExpected, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + m.Reset() - err = test.exportFunc(exporter.(*Exporter), test.payload) + err := test.handleFunc(exporter.(*Exporter), test.response) if err != nil { t.Error(err) } - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - }) } + prometheus.Unregister(exporter) } diff --git a/hpe/dl560/metrics.go b/hpe/dl560/metrics.go index 956b0f1..3d0fc9a 100644 --- a/hpe/dl560/metrics.go +++ b/hpe/dl560/metrics.go @@ -49,8 +49,8 @@ func NewDeviceMetrics() *map[string]*metrics { PowerMetrics = &metrics{ "voltageOutput": newServerMetric("dl560_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), "voltageStatus": newServerMetric("dl560_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "supplyOutput": newServerMetric("dl560_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("dl560_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyOutput": newServerMetric("dl560_power_supply_output", "Power supply output in watts", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), + "supplyStatus": newServerMetric("dl560_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), "supplyTotalConsumed": newServerMetric("dl560_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } diff --git a/hpe/xl420/exporter.go b/hpe/xl420/exporter.go index e00d102..41203b2 100644 --- a/hpe/xl420/exporter.go +++ b/hpe/xl420/exporter.go @@ -324,7 +324,6 @@ func NewExporter(ctx context.Context, target, uri, profile string) (*Exporter, e } tasks = append(tasks, - pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", MEMORY, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Systems/1/", STORAGEBATTERY, target, profile, retryClient)), pool.NewTask(common.Fetch(fqdn.String()+uri+"/Managers/1/", ILOSELFTEST, target, profile, retryClient))) diff --git a/hpe/xl420/exporter_test.go b/hpe/xl420/exporter_test.go index 33e9673..82b1995 100644 --- a/hpe/xl420/exporter_test.go +++ b/hpe/xl420/exporter_test.go @@ -30,237 +30,28 @@ import ( ) const ( - up2Response = ` + up2Expected = ` # HELP up was the last scrape of fishymetrics successful. # TYPE up gauge up 2 ` - GoodLogicalDriveUpperResponse = ` + GoodLogicalDriveExpected = ` # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED # TYPE xl420_logical_drive_status gauge - xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 + xl420_logical_drive_status{chassisSerialNumber="SN98765",logicaldrivename="TESTDRIVE NAME 1",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="ABCDEF12345"} 1 ` - GoodDiskDriveUpperResponse = ` + GoodDiskDriveExpected = ` # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE xl420_disk_drive_status gauge - xl420_disk_drive_status{id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 + xl420_disk_drive_status{chassisSerialNumber="SN98765",id="0",location="1I:1:1",name="HpeSmartStorageDiskDrive",serialnumber="ABC123"} 1 ` - GoodLogicalDriveLowerResponse = ` - # HELP xl420_logical_drive_status Current Logical Drive Raid 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_logical_drive_status gauge - xl420_logical_drive_status{logicaldrivename="TESTDRIVE NAME 2",name="HpeSmartStorageLogicalDrive",raid="1",volumeuniqueidentifier="FEDCBA12345"} 1 - ` - GoodDiskDriveLowerResponse = ` - # HELP xl420_disk_drive_status Current Disk Drive status 1 = OK, 0 = BAD, -1 = DISABLED - # TYPE xl420_disk_drive_status gauge - xl420_disk_drive_status{id="1",location="1I:1:2",name="HpeSmartStorageDiskDrive",serialnumber="DEF456"} 1 - ` - GoodNvmeDriveResponse = ` + GoodNvmeDriveExpected = ` # HELP xl420_nvme_drive_status Current NVME status 1 = OK, 0 = BAD, -1 = DISABLED # TYPE xl420_nvme_drive_status gauge - xl420_nvme_drive_status{id="0",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 + xl420_nvme_drive_status{chassisSerialNumber="SN98765",id="DA000000",protocol="NVMe",serviceLabel="Box 3:Bay 7"} 1 ` ) -var ( - GoodDiskDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "0", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:1", - SerialNumber: "ABC123", - }) - - GoodDiskDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - Name string `json:"Name"` - Model string `json:"Model"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - Location string `json:"Location"` - SerialNumber string `json:"SerialNumber"` - }{ - Id: "1", - CapacityMiB: 572325, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - Name: "HpeSmartStorageDiskDrive", - Model: "TESTMODEL", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - Location: "1I:1:2", - SerialNumber: "DEF456", - }) - - GoodLogicalDriveUpper = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 1", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "ABCDEF12345", - }) - - GoodLogicalDriveLower = MustMarshal(struct { - Id string `json:"Id"` - CapacityMiB int `json:"CapacityMiB"` - Description string `json:"Description"` - InterfaceType string `json:"InterfaceType"` - LogicalDriveName string `json:"LogicalDriveName"` - LogicalDriveNumber int `json:"LogicalDriveNumber"` - Name string `json:"Name"` - Raid string `json:"Raid"` - Status struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"Status"` - StripeSizebytes int `json:"StripeSizebytes"` - VolumeUniqueIdentifier string `json:"VolumeUniqueIdentifier"` - }{ - Id: "1", - CapacityMiB: 572293, - Description: "HPE Smart Storage Disk Drive View", - InterfaceType: "SAS", - LogicalDriveName: "TESTDRIVE NAME 2", - LogicalDriveNumber: 1, - Name: "HpeSmartStorageLogicalDrive", - Raid: "1", - Status: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - StripeSizebytes: 262144, - VolumeUniqueIdentifier: "FEDCBA12345", - }) - - GoodNvmeDrive = MustMarshal(struct { - Id string `json:"Id"` - Model string `json:"Model"` - Name string `json:"Name"` - MediaType string `json:"MediaType"` - Oem struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - } `json:"Oem"` - PhysicalLocation struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - } `json:"PhysicalLocation"` - Protocol string `json:"Protocol"` - FailurePredicted bool `json:"FailurePredicted"` - CapacityBytes int `json:"CapacityBytes"` - }{ - Id: "0", - Model: "TESTMODEL", - Name: "TESTNAME", - MediaType: "SSD", - Oem: struct { - Hpe struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - } `json:"Hpe"` - }{ - Hpe: struct { - DriveStatus struct { - Health string `json:"Health"` - State string `json:"State"` - } `json:"DriveStatus"` - }{ - DriveStatus: struct { - Health string `json:"Health"` - State string `json:"State"` - }{ - Health: "OK", - State: "Enabled", - }, - }, - }, - PhysicalLocation: struct { - PartLocation struct { - ServiceLabel string `json:"ServiceLabel"` - } `json:"PartLocation"` - }{ - PartLocation: struct { - ServiceLabel string `json:"ServiceLabel"` - }{ - ServiceLabel: "Box 3:Bay 7", - }, - }, - Protocol: "NVMe", - FailurePredicted: false, - CapacityBytes: 1600321314816, - }) -) - type TestErrorResponse struct { Error TestError `json:"error"` } @@ -285,7 +76,7 @@ func MustMarshal(v interface{}) []byte { func Test_XL420_Exporter(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/badcred/Systems/1/SmartStorage/ArrayControllers/" { + if r.URL.Path == "/redfish/v1/badcred/Managers/1/" { w.WriteHeader(http.StatusUnauthorized) w.Write(MustMarshal(TestErrorResponse{ Error: TestError{ @@ -323,7 +114,7 @@ func Test_XL420_Exporter(t *testing.T) { metricName: "up", metricRef1: "up", metricRef2: "up", - expected: up2Response, + expected: up2Expected, }, } @@ -348,200 +139,79 @@ func Test_XL420_Exporter(t *testing.T) { } } -func Test_XL420_Drives(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 2, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/", - }, - { - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - } `json:"Links"` - }{ - LinksUpper: struct { - LogicalDrives struct { - URL string `json:"@odata.id"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"@odata.id"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"@odata.id"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksLower struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - } `json:"links"` - }{ - LinksLower: struct { - LogicalDrives struct { - URL string `json:"href"` - } `json:"LogicalDrives"` - PhysicalDrives struct { - URL string `json:"href"` - } `json:"PhysicalDrives"` - }{ - LogicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/", - }, - PhysicalDrives: struct { - URL string `json:"href"` - }{ - URL: "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/LogicalDrives/1/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - MembersCount int `json:"Members@odata.count"` - Members []struct { - URL string `json:"@odata.id"` - } `json:"Members"` - }{ - MembersCount: 1, - Members: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/2/DiskDrives/0/", - }, - }, - })) - return - } else if r.URL.Path == "/redfish/v1/good/Chassis/1/" { - w.WriteHeader(http.StatusOK) - w.Write(MustMarshal(struct { - LinksUpper struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - } `json:"Links"` - }{ - LinksUpper: struct { - Drives []struct { - URL string `json:"@odata.id"` - } `json:"Drives"` - }{ - Drives: []struct { - URL string `json:"@odata.id"` - }{ - { - URL: "/redfish/v1/Systems/1/Storage/DA000000/Drives/DA000000/", - }, - }, - }, - })) - return - } - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Unknown path - please create test case(s) for it")) - })) - defer server.Close() +func Test_XL420_Metrics_Handling(t *testing.T) { + + var GoodLogicalDriveResponse = []byte(`{ + "Id": "1", + "CapacityMiB": 915683, + "Description": "HPE Smart Storage Logical Drive View", + "InterfaceType": "SATA", + "LogicalDriveName": "TESTDRIVE NAME 1", + "LogicalDriveNumber": 1, + "Name": "HpeSmartStorageLogicalDrive", + "Raid": "1", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "StripeSizeBytes": 262144, + "VolumeUniqueIdentifier": "ABCDEF12345" + }`) + var GoodDiskDriveResponse = []byte(`{ + "Id": "0", + "CapacityMiB": 915715, + "Description": "HPE Smart Storage Disk Drive View", + "InterfaceType": "SATA", + "Location": "1I:1:1", + "Model": "model name", + "Name": "HpeSmartStorageDiskDrive", + "SerialNumber": "ABC123", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }`) + var GoodNvmeDriveResponse = []byte(`{ + "Id": "DA000000", + "CapacityBytes": 1600321314816, + "FailurePredicted": false, + "MediaType": "SSD", + "Model": "model name", + "Name": "Secondary Storage Device", + "Oem": { + "Hpe": { + "CurrentTemperatureCelsius": 33, + "DriveStatus": { + "Health": "OK", + "State": "Enabled" + }, + "NVMeId": "drive id" + } + }, + "PhysicalLocation": { + "PartLocation": { + "ServiceLabel": "Box 3:Bay 7" + } + }, + "Protocol": "NVMe" + }`) + + var exporter prometheus.Collector - ctx := context.Background() assert := assert.New(t) + metrx := NewDeviceMetrics() + + exporter = &Exporter{ + ctx: context.Background(), + host: "fishymetrics.com", + biosVersion: "U99 v0.00 (xx/xx/xxxx)", + chassisSerialNumber: "SN98765", + deviceMetrics: metrx, + } + + prometheus.MustRegister(exporter) + logicalDevMetrics := func(exp *Exporter, payload []byte) error { err := exp.exportLogicalDriveMetrics(payload) if err != nil { @@ -568,88 +238,55 @@ func Test_XL420_Drives(t *testing.T) { tests := []struct { name string - uri string metricName string metricRef1 string metricRef2 string - exportFunc func(*Exporter, []byte) error - payload []byte + handleFunc func(*Exporter, []byte) error + response []byte expected string }{ { - name: "Good Logical Drive Links Uppercase", - uri: "/redfish/v1/good", + name: "Good Logical Drive", metricName: "xl420_logical_drive_status", metricRef1: "logicalDriveMetrics", metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveUpper, - expected: GoodLogicalDriveUpperResponse, + handleFunc: logicalDevMetrics, + response: GoodLogicalDriveResponse, + expected: GoodLogicalDriveExpected, }, { - name: "Good Logical Drive Links Lowercase", - uri: "/redfish/v1/good", - metricName: "xl420_logical_drive_status", - metricRef1: "logicalDriveMetrics", - metricRef2: "raidStatus", - exportFunc: logicalDevMetrics, - payload: GoodLogicalDriveLower, - expected: GoodLogicalDriveLowerResponse, - }, - { - name: "Good Disk Drive Links Uppercase", - uri: "/redfish/v1/good", - metricName: "xl420_disk_drive_status", - metricRef1: "diskDriveMetrics", - metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveUpper, - expected: GoodDiskDriveUpperResponse, - }, - { - name: "Good Disk Drive Links Lowercase", - uri: "/redfish/v1/good", + name: "Good Disk Drive", metricName: "xl420_disk_drive_status", metricRef1: "diskDriveMetrics", metricRef2: "driveStatus", - exportFunc: physDevMetrics, - payload: GoodDiskDriveLower, - expected: GoodDiskDriveLowerResponse, + handleFunc: physDevMetrics, + response: GoodDiskDriveResponse, + expected: GoodDiskDriveExpected, }, { name: "Good Nvme Drive", - uri: "/redfish/v1/good", metricName: "xl420_nvme_drive_status", metricRef1: "nvmeMetrics", metricRef2: "nvmeDriveStatus", - exportFunc: nvmeDevMetrics, - payload: GoodNvmeDrive, - expected: GoodNvmeDriveResponse, + handleFunc: nvmeDevMetrics, + response: GoodNvmeDriveResponse, + expected: GoodNvmeDriveExpected, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var exporter prometheus.Collector - var err error - exporter, err = NewExporter(ctx, server.URL, test.uri, "") - assert.Nil(err) - assert.NotNil(exporter) - - prometheus.MustRegister(exporter) + metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] + m := (*metric)[test.metricRef2] + m.Reset() - err = test.exportFunc(exporter.(*Exporter), test.payload) + err := test.handleFunc(exporter.(*Exporter), test.response) if err != nil { t.Error(err) } - metric := (*exporter.(*Exporter).deviceMetrics)[test.metricRef1] - m := (*metric)[test.metricRef2] - assert.Empty(testutil.CollectAndCompare(m, strings.NewReader(test.expected), test.metricName)) - - prometheus.Unregister(exporter) - }) } + prometheus.Unregister(exporter) } diff --git a/hpe/xl420/metrics.go b/hpe/xl420/metrics.go index 516bd87..bcea293 100644 --- a/hpe/xl420/metrics.go +++ b/hpe/xl420/metrics.go @@ -49,8 +49,8 @@ func NewDeviceMetrics() *map[string]*metrics { PowerMetrics = &metrics{ "voltageOutput": newServerMetric("xl420_power_voltage_output", "Power voltage output in watts", nil, []string{"name", "chassisSerialNumber"}), "voltageStatus": newServerMetric("xl420_power_voltage_status", "Current power voltage status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber"}), - "supplyOutput": newServerMetric("xl420_power_supply_output", "Power supply output in watts", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), - "supplyStatus": newServerMetric("xl420_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"memberId", "chassisSerialNumber", "sparePartNumber"}), + "supplyOutput": newServerMetric("xl420_power_supply_output", "Power supply output in watts", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), + "supplyStatus": newServerMetric("xl420_power_supply_status", "Current power supply status 1 = OK, 0 = BAD", nil, []string{"name", "chassisSerialNumber", "manufacturer", "partNumber", "serialNumber", "powerSupplyType", "model"}), "supplyTotalConsumed": newServerMetric("xl420_power_supply_total_consumed", "Total output of all power supplies in watts", nil, []string{"memberId", "chassisSerialNumber"}), } From 8a4f2bf840a02c862794f6c703edcbe4971fe1c0 Mon Sep 17 00:00:00 2001 From: Derrick DaCosta Date: Mon, 1 Apr 2024 15:37:08 -0400 Subject: [PATCH 4/4] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ca5026..5eb9876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ log is based on the [Keep a CHANGELOG](http://keepachangelog.com/) project. - Cisco S3260M5 module to support FW Ver 4.2(xx) [#18](https://github.com/Comcast/fishymetrics/issues/18) - HP DL360 module to support responses from iLO4 [#34](https://github.com/Comcast/fishymetrics/issues/34) - HP DL360 & XL420 to include processor, iloselftest and smart storage battery metrics [#43](https://github.com/Comcast/fishymetrics/issues/43) +- consolidate hardware component structs to a single package [#45](https://github.com/Comcast/fishymetrics/issues/45) ## [0.7.1]