From 67d348bae0786fad1b28edbfc67043a1d7fc7c57 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 24 Nov 2020 11:04:41 -0600 Subject: [PATCH] Handle non-conforming implementation values Some implementations are not following the Redfish spec exactly and returning values in types other than what is defined in the spec. This adds some workarounds to try to address some of these in the Power and Processor objects. Closes: #99 Signed-off-by: Sean McGinnis --- redfish/power.go | 70 ++++++- redfish/power_test.go | 394 ++++++++++++++++++++++++++++++++++++++ redfish/processor.go | 28 ++- redfish/processor_test.go | 85 ++++++++ 4 files changed, 573 insertions(+), 4 deletions(-) diff --git a/redfish/power.go b/redfish/power.go index c7da9275..0597e770 100644 --- a/redfish/power.go +++ b/redfish/power.go @@ -7,6 +7,7 @@ package redfish import ( "encoding/json" "reflect" + "strconv" "github.com/stmcginnis/gofish/common" ) @@ -225,6 +226,39 @@ type PowerControl struct { Status common.Status } +// UnmarshalJSON unmarshals a PowerControl object from the raw JSON. +func (powercontrol *PowerControl) UnmarshalJSON(b []byte) error { + type temp PowerControl + type t1 struct { + temp + } + var t t1 + + err := json.Unmarshal(b, &t) + if err != nil { + // See if we need to handle converting MemberID + var t2 struct { + t1 + MemberID int `json:"MemberId"` + } + err2 := json.Unmarshal(b, &t2) + + if err2 != nil { + // Return the original error + return err + } + + // Convert the numeric member ID to a string + t = t2.t1 + t.temp.MemberID = strconv.Itoa(t2.MemberID) + } + + // Extract the links to other entities for later + *powercontrol = PowerControl(t.temp) + + return nil +} + // PowerLimit shall contain power limit status and // configuration information for this chassis. type PowerLimit struct { @@ -254,7 +288,8 @@ type PowerMetric struct { // IntervalInMin shall represent the time // interval (or window), in minutes, in which the PowerMetrics properties // are measured over. - IntervalInMin int + // Should be an integer, but some Dell implementations return as a float. + IntervalInMin float32 // MaxConsumedWatts shall represent the // maximum power level in watts that occurred within the last // IntervalInMin minutes. @@ -448,3 +483,36 @@ type Voltage struct { // Units shall use the same units as the related ReadingVolts property. UpperThresholdNonCritical float32 } + +// UnmarshalJSON unmarshals a Voltage object from the raw JSON. +func (voltage *Voltage) UnmarshalJSON(b []byte) error { + type temp Voltage + type t1 struct { + temp + } + var t t1 + + err := json.Unmarshal(b, &t) + if err != nil { + // See if we need to handle converting MemberID + var t2 struct { + t1 + MemberID int `json:"MemberId"` + } + err2 := json.Unmarshal(b, &t2) + + if err2 != nil { + // Return the original error + return err + } + + // Convert the numeric member ID to a string + t = t2.t1 + t.temp.MemberID = strconv.Itoa(t2.MemberID) + } + + // Extract the links to other entities for later + *voltage = Voltage(t.temp) + + return nil +} diff --git a/redfish/power_test.go b/redfish/power_test.go index ba462906..fd56ddbe 100644 --- a/redfish/power_test.go +++ b/redfish/power_test.go @@ -127,6 +127,379 @@ var powerBody = strings.NewReader( "Voltages@odata.count": 1 }`) +var invalidPowerBody = strings.NewReader( + `{ + "@odata.context": "/redfish/v1/$metadata#Chassis/Members(*)/Self/Power/$entity", + "@odata.etag": "W/\"1604509181\"", + "@odata.id": "/redfish/v1/Chassis/Self/Power", + "@odata.type": "#Power.v1_2_1.Power", + "Id": "Power", + "Name": "Power", + "PowerControl": [ + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/PowerControl/0", + "MemberId": 0, + "Name": "Chassis Power Control", + "PowerLimit": { + "CorrectionInMs": 1000, + "LimitException": "NoAction", + "LimitInWatts": 500 + }, + "PowerMetrics": { + "AverageConsumedWatts": 148, + "IntervalInMin": 0.083333333333333, + "MaxConsumedWatts": 301, + "MinConsumedWatts": 0 + }, + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "PowerSupplies": [ + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/PowerSupplies/0", + "@odata.type": "#Power.v1_2_1.PowerSupply", + "FirmwareVersion": "00.04.04", + "InputRanges": [ + { + "MaximumVoltage": 264, + "MinimumVoltage": 90, + "OutputWattage": 128 + } + ], + "LastPowerOutputWatts": 103, + "LineInputVoltage": 241, + "Manufacturer": "Liteon Power", + "MemberId": "1", + "Model": "PS-2122-7Q", + "Name": "PSU1", + "PowerCapacityWatts": 1200, + "SerialNumber": "6D7QX0101J224CV", + "Status": { + "Health": "OK", + "State": "Enabled" + } + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/PowerSupplies/1", + "@odata.type": "#Power.v1_2_1.PowerSupply", + "FirmwareVersion": "00.04.04", + "InputRanges": [ + { + "MaximumVoltage": 264, + "MinimumVoltage": 90, + "OutputWattage": 150 + } + ], + "LastPowerOutputWatts": 123, + "LineInputVoltage": 241, + "Manufacturer": "Liteon Power", + "MemberId": "0", + "Model": "PS-2122-7Q", + "Name": "PSU0", + "PowerCapacityWatts": 1200, + "SerialNumber": "6D7QX0101J2247A", + "Status": { + "Health": "OK", + "State": "Enabled" + } + } + ], + "Voltages": [ + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/0", + "LowerThresholdCritical": 1.431, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 218, + "MinReadingRange": 0, + "Name": "Volt_PVCCIN_CPU1", + "ReadingVolts": 1.692, + "SensorNumber": 218, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 2.205, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/1", + "LowerThresholdCritical": 1.078, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 223, + "MinReadingRange": 0, + "Name": "Volt_CPU1_DEF", + "ReadingVolts": 1.218, + "SensorNumber": 223, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.323, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/2", + "LowerThresholdCritical": 2.975, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 209, + "MinReadingRange": 0, + "Name": "Volt_P3V3", + "ReadingVolts": 3.264, + "SensorNumber": 209, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 3.621, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/3", + "LowerThresholdCritical": 1.078, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 222, + "MinReadingRange": 0, + "Name": "Volt_CPU1_ABC", + "ReadingVolts": 1.218, + "SensorNumber": 222, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.323, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/4", + "LowerThresholdCritical": 10.773, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 208, + "MinReadingRange": 0, + "Name": "Volt_P12V", + "ReadingVolts": 12.033, + "SensorNumber": 208, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 13.23, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/5", + "LowerThresholdCritical": 0.882, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 217, + "MinReadingRange": 0, + "Name": "Volt_PVCCIO_CPU0", + "ReadingVolts": 0.973, + "SensorNumber": 217, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.057, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/6", + "LowerThresholdCritical": 1.078, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 220, + "MinReadingRange": 0, + "Name": "Volt_CPU0_ABC", + "ReadingVolts": 1.218, + "SensorNumber": 220, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.323, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/7", + "LowerThresholdCritical": 0.763, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 213, + "MinReadingRange": 0, + "Name": "Volt_PVNN_PCH", + "ReadingVolts": 0.987, + "SensorNumber": 213, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.106, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/8", + "LowerThresholdCritical": 0.882, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 219, + "MinReadingRange": 0, + "Name": "Volt_PVCCIO_CPU1", + "ReadingVolts": 0.987, + "SensorNumber": 219, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.057, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/9", + "LowerThresholdCritical": 11.214, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 212, + "MinReadingRange": 0, + "Name": "Volt_P12V_AUX", + "ReadingVolts": 12.033, + "SensorNumber": 212, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 13.041, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/10", + "LowerThresholdCritical": 4.498, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 210, + "MinReadingRange": 0, + "Name": "Volt_P5V", + "ReadingVolts": 5.018, + "SensorNumber": 210, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 5.538, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/11", + "LowerThresholdCritical": 1.078, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 221, + "MinReadingRange": 0, + "Name": "Volt_CPU0_DEF", + "ReadingVolts": 1.218, + "SensorNumber": 221, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.323, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/12", + "LowerThresholdCritical": 1.62, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 214, + "MinReadingRange": 0, + "Name": "Volt_P1V8_PCH", + "ReadingVolts": 1.71, + "SensorNumber": 214, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.989, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/13", + "LowerThresholdCritical": 0.945, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 211, + "MinReadingRange": 0, + "Name": "Volt_P1V05_PCH", + "ReadingVolts": 1.036, + "SensorNumber": 211, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 1.155, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/14", + "LowerThresholdCritical": 1.431, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 216, + "MinReadingRange": 0, + "Name": "Volt_PVCCIN_CPU0", + "ReadingVolts": 1.692, + "SensorNumber": 216, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 2.205, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + }, + { + "@odata.id": "/redfish/v1/Chassis/Self/Power#/Voltages/15", + "LowerThresholdCritical": 2.52, + "LowerThresholdFatal": null, + "LowerThresholdNonCritical": null, + "MemberId": 215, + "MinReadingRange": 0, + "Name": "Volt_P3V_BAT", + "ReadingVolts": 3.003, + "SensorNumber": 215, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 3.591, + "UpperThresholdFatal": null, + "UpperThresholdNonCritical": null + } + ] + } + }`) + // TestPower tests the parsing of Power objects. func TestPower(t *testing.T) { var result Power @@ -165,3 +538,24 @@ func TestPower(t *testing.T) { t.Errorf("Invalid MaxReadingRange: %f", result.Voltages[0].MaxReadingRange) } } + +// TestNonconformingPower tests the parsing of nonconforming Power objects. +// Some Dell implementations return MemberID as an integer when they should be +// strings. +func TestNonconformingPower(t *testing.T) { + var result Power + err := json.NewDecoder(invalidPowerBody).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + if result.PowerControl[0].MemberID != "0" { + t.Errorf("Expected first PowerController MemberID to be '0': %s", result.PowerControl[0].MemberID) + } + + voltage := result.Voltages[0] + if voltage.MemberID != "218" { + t.Errorf("Expected first Voltage MemberID to be '218': %s", voltage.MemberID) + } +} diff --git a/redfish/processor.go b/redfish/processor.go index 99c3ce6f..53ce3dd7 100644 --- a/redfish/processor.go +++ b/redfish/processor.go @@ -6,6 +6,7 @@ package redfish import ( "encoding/json" + "strconv" "github.com/stmcginnis/gofish/common" ) @@ -291,7 +292,7 @@ type Processor struct { Manufacturer string // MaxSpeedMHz shall indicate the maximum rated clock // speed of the processor in MHz. - MaxSpeedMHz int + MaxSpeedMHz float32 // MaxTDPWatts shall be the maximum Thermal // Design Power (TDP) in watts. MaxTDPWatts int @@ -370,7 +371,7 @@ type Processor struct { // UnmarshalJSON unmarshals a Processor object from the raw JSON. func (processor *Processor) UnmarshalJSON(b []byte) error { type temp Processor - var t struct { + type t1 struct { temp AccelerationFunctions common.Link Assembly common.Link @@ -387,10 +388,31 @@ func (processor *Processor) UnmarshalJSON(b []byte) error { PCIeFunctionsCount int `json:"PCIeFunctions@odata.count"` } } + var t t1 err := json.Unmarshal(b, &t) if err != nil { - return err + // Handle invalid data type returned for MaxSpeedMHz + var t2 struct { + t1 + MaxSpeedMHz string + } + err2 := json.Unmarshal(b, &t2) + + if err2 != nil { + // Return the original error + return err + } + + // Extract the real Processor struct and replace its MaxSpeedMHz with + // the parsed string version + t = t2.t1 + if t2.MaxSpeedMHz != "" { + mhz, err := strconv.ParseFloat(t2.MaxSpeedMHz, 32) + if err != nil { + t.MaxSpeedMHz = float32(mhz) + } + } } *processor = Processor(t.temp) diff --git a/redfish/processor_test.go b/redfish/processor_test.go index 335d7900..a941d33e 100644 --- a/redfish/processor_test.go +++ b/redfish/processor_test.go @@ -148,6 +148,76 @@ var processorBody = strings.NewReader( "TotalThreads":24 }`) +var invalidProcessorBody = strings.NewReader( + `{ + "@odata.context": "/redfish/v1/$metadata#Processor.Processor(*)", + "@odata.etag": "W/\"1604509181\"", + "@odata.id": "/redfish/v1/Systems/Self/Processors/1", + "@odata.type": "#Processor.v1_0_3.Processor", + "Id": "1", + "InstructionSet": "X86-64", + "Manufacturer": "Intel(R) Corporation", + "MaxSpeedMHz": "", + "Model": "Intel Xeon", + "Name": "Processor1", + "Oem": { + "Intel_RackScale": { + "@odata.type": "#Intel.Oem.Processor", + "Brand": "E5", + "Capabilities": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse-36", + "clfsh", + "ds", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "htt", + "tm", + "pbe" + ] + }, + "Quanta_RackScale": { + "Version": "Intel(R) Xeon(R) Gold 6242 CPU @ 2.80GHz" + } + }, + "ProcessorArchitecture": "x86", + "ProcessorId": { + "EffectiveFamily": "0x6", + "EffectiveModel": "0x55", + "IdentificationRegisters": "0xbfebfbff00050657", + "MicrocodeInfo": "0x50024", + "Step": "0x7", + "VendorId": "GenuineIntel" + }, + "ProcessorType": "CPU", + "Socket": "CPU_0", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "TotalCores": 16, + "TotalThreads": 32 + }`) + // TestProcessor tests the parsing of Processor objects. func TestProcessor(t *testing.T) { var result Processor @@ -181,3 +251,18 @@ func TestProcessor(t *testing.T) { t.Errorf("Expected 2 ReconfigurationSlots, got %d", len(result.FPGA.ReconfigurationSlots)) } } + +// TestNonconformingProcessor tests the parsing of Processor objects from certain +// Dell implementations that do not fully conform to the spec. +func TestNonconformingProcessor(t *testing.T) { + var result Processor + err := json.NewDecoder(invalidProcessorBody).Decode(&result) + + if err != nil { + t.Errorf("Error decoding JSON: %s", err) + } + + if result.MaxSpeedMHz != 0 { + t.Errorf("Expected MaxSpeedMhz to be 0 but got %f", result.MaxSpeedMHz) + } +}