diff --git a/plugins/inputs/redfish/README.md b/plugins/inputs/redfish/README.md index cf0ab5cb09363..908b0a6dc60cf 100644 --- a/plugins/inputs/redfish/README.md +++ b/plugins/inputs/redfish/README.md @@ -42,6 +42,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## Available sets are: "chassis.location" and "chassis" # include_tag_sets = ["chassis.location"] + ## Workarounds + ## Defines workarounds for certain hardware vendors. Choose from: + ## * ilo4-thermal - Do not pass 0Data-Version header to Thermal endpoint + # workarounds = [] + ## Amount of time allowed to complete the HTTP request # timeout = "5s" diff --git a/plugins/inputs/redfish/redfish.go b/plugins/inputs/redfish/redfish.go index 444527f2389a0..9e01a712b6d04 100644 --- a/plugins/inputs/redfish/redfish.go +++ b/plugins/inputs/redfish/redfish.go @@ -10,6 +10,8 @@ import ( "net/http" "net/url" "path" + "slices" + "strings" "time" "github.com/influxdata/telegraf" @@ -28,6 +30,7 @@ type Redfish struct { ComputerSystemID string `toml:"computer_system_id"` IncludeMetrics []string `toml:"include_metrics"` IncludeTagSets []string `toml:"include_tag_sets"` + Workarounds []string `toml:"workarounds"` Timeout config.Duration `toml:"timeout"` tagSet map[string]bool @@ -111,6 +114,8 @@ type Thermal struct { Fans []struct { Name string MemberID string + FanName string + CurrentReading *int64 Reading *int64 ReadingUnits *string UpperThresholdCritical *int64 @@ -175,6 +180,13 @@ func (r *Redfish) Init() error { } } + for _, workaround := range r.Workarounds { + switch workaround { + case "ilo4-thermal": + default: + return fmt.Errorf("unknown workaround requested: %s", workaround) + } + } r.tagSet = make(map[string]bool, len(r.IncludeTagSets)) for _, setLabel := range r.IncludeTagSets { r.tagSet[setLabel] = true @@ -212,6 +224,12 @@ func (r *Redfish) getData(address string, payload interface{}) error { req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.Header.Set("OData-Version", "4.0") + + // workaround for iLO4 thermal data + if slices.Contains(r.Workarounds, "ilo4-thermal") && strings.Contains(address, "/Thermal") { + req.Header.Del("OData-Version") + } + resp, err := r.client.Do(req) if err != nil { return err @@ -364,6 +382,9 @@ func (r *Redfish) gatherThermal(acc telegraf.Accumulator, address string, system tags["member_id"] = j.MemberID tags["address"] = address tags["name"] = j.Name + if j.FanName != "" { + tags["name"] = j.FanName + } tags["source"] = system.Hostname tags["state"] = j.Status.State tags["health"] = j.Status.Health @@ -383,6 +404,8 @@ func (r *Redfish) gatherThermal(acc telegraf.Accumulator, address string, system fields["lower_threshold_critical"] = j.LowerThresholdCritical fields["lower_threshold_fatal"] = j.LowerThresholdFatal fields["reading_rpm"] = j.Reading + } else if j.CurrentReading != nil { + fields["reading_percent"] = j.CurrentReading } else { fields["reading_percent"] = j.Reading } diff --git a/plugins/inputs/redfish/redfish_test.go b/plugins/inputs/redfish/redfish_test.go index b6a29f379700c..5f225aa76e611 100644 --- a/plugins/inputs/redfish/redfish_test.go +++ b/plugins/inputs/redfish/redfish_test.go @@ -616,6 +616,103 @@ func TestHPApis(t *testing.T) { testutil.IgnoreTime()) } +func TestHPilo4Apis(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !checkAuth(r, "test", "test") { + http.Error(w, "Unauthorized.", 401) + return + } + + switch r.URL.Path { + case "/redfish/v1/Chassis/1/Thermal": + http.ServeFile(w, r, "testdata/hp_thermal_ilo4.json") + case "/redfish/v1/Chassis/1/Power": + http.ServeFile(w, r, "testdata/hp_power.json") + case "/redfish/v1/Systems/1": + http.ServeFile(w, r, "testdata/hp_systems.json") + case "/redfish/v1/Chassis/1/": + http.ServeFile(w, r, "testdata/hp_chassis.json") + default: + w.WriteHeader(http.StatusNotFound) + } + })) + + defer ts.Close() + + u, err := url.Parse(ts.URL) + require.NoError(t, err) + address, _, err := net.SplitHostPort(u.Host) + require.NoError(t, err) + + expectedMetricsHp := []telegraf.Metric{ + testutil.MustMetric( + "redfish_thermal_temperatures", + map[string]string{ + "name": "01-Inlet Ambient", + "member_id": "0", + "source": "tpa-hostname", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_celsius": 19.0, + "upper_threshold_critical": 42.0, + "upper_threshold_fatal": 47.0, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_thermal_temperatures", + map[string]string{ + "name": "44-P/S 2 Zone", + "member_id": "42", + "source": "tpa-hostname", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_celsius": 34.0, + "upper_threshold_critical": 75.0, + "upper_threshold_fatal": 80.0, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_thermal_fans", + map[string]string{ + "address": address, + "health": "OK", + "member_id": "", + "name": "Fan 1", + "source": "tpa-hostname", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_percent": 17, + }, + time.Unix(0, 0), + ), + } + + hpPlugin := &Redfish{ + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemID: "1", + IncludeMetrics: []string{"thermal"}, + } + require.NoError(t, hpPlugin.Init()) + var hpAcc testutil.Accumulator + + err = hpPlugin.Gather(&hpAcc) + require.NoError(t, err) + require.True(t, hpAcc.HasMeasurement("redfish_thermal_temperatures")) + testutil.RequireMetricsEqual(t, expectedMetricsHp, hpAcc.GetTelegrafMetrics(), + testutil.IgnoreTime()) +} + func checkAuth(r *http.Request, username, password string) bool { user, pass, ok := r.BasicAuth() if !ok { diff --git a/plugins/inputs/redfish/sample.conf b/plugins/inputs/redfish/sample.conf index 661169ec24368..21ea48f5da490 100644 --- a/plugins/inputs/redfish/sample.conf +++ b/plugins/inputs/redfish/sample.conf @@ -21,6 +21,11 @@ ## Available sets are: "chassis.location" and "chassis" # include_tag_sets = ["chassis.location"] + ## Workarounds + ## Defines workarounds for certain hardware vendors. Choose from: + ## * ilo4-thermal - Do not pass 0Data-Version header to Thermal endpoint + # workarounds = [] + ## Amount of time allowed to complete the HTTP request # timeout = "5s" diff --git a/plugins/inputs/redfish/testdata/hp_thermal_ilo4.json b/plugins/inputs/redfish/testdata/hp_thermal_ilo4.json new file mode 100644 index 0000000000000..da088e3f7f3b3 --- /dev/null +++ b/plugins/inputs/redfish/testdata/hp_thermal_ilo4.json @@ -0,0 +1,72 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Thermal.Thermal", + "@odata.etag": "W/\"14E8662D\"", + "@odata.id": "/redfish/v1/Chassis/1/Thermal", + "@odata.type": "#Thermal.v1_1_0.Thermal", + "Id": "Thermal", + "Fans": [ + { + "CurrentReading": 17, + "FanName": "Fan 1", + "Oem": { + "Hp": { + "@odata.type": "#HpServerFan.1.0.0.HpServerFan", + "Location": "System", + "Type": "HpServerFan.1.0.0" + } + }, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "Units": "Percent" + } + ], + "Name": "Thermal", + "Temperatures": [ + { + "@odata.id": "/redfish/v1/Chassis/1/Thermal#Temperatures/0", + "MemberId": "0", + "Name": "01-Inlet Ambient", + "Oem": { + "Hpe": { + "@odata.context": "/redfish/v1/$metadata#HpeSeaOfSensors.HpeSeaOfSensors", + "@odata.type": "#HpeSeaOfSensors.v2_0_0.HpeSeaOfSensors", + "LocationXmm": 15, + "LocationYmm": 0 + } + }, + "PhysicalContext": "Intake", + "ReadingCelsius": 19, + "SensorNumber": 1, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 42, + "UpperThresholdFatal": 47 + }, + { + "@odata.id": "/redfish/v1/Chassis/1/Thermal#Temperatures/42", + "MemberId": "42", + "Name": "44-P/S 2 Zone", + "Oem": { + "Hpe": { + "@odata.context": "/redfish/v1/$metadata#HpeSeaOfSensors.HpeSeaOfSensors", + "@odata.type": "#HpeSeaOfSensors.v2_0_0.HpeSeaOfSensors", + "LocationXmm": 4, + "LocationYmm": 7 + } + }, + "PhysicalContext": "PowerSupply", + "ReadingCelsius": 34, + "SensorNumber": 43, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UpperThresholdCritical": 75, + "UpperThresholdFatal": 80 + } + ] + }