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) + } +}