From a19befe376fd66e36d59201ce12a25ca8301bc03 Mon Sep 17 00:00:00 2001 From: Daniel Nelson Date: Tue, 7 Jul 2020 14:25:55 -0700 Subject: [PATCH] Traverse redfish api using resource links (#7722) --- plugins/inputs/redfish/README.md | 6 + plugins/inputs/redfish/redfish.go | 466 ++++++++------- plugins/inputs/redfish/redfish_test.go | 542 ++++++++++-------- .../inputs/redfish/testdata/hp_chassis.json | 8 + 4 files changed, 554 insertions(+), 468 deletions(-) create mode 100644 plugins/inputs/redfish/testdata/hp_chassis.json diff --git a/plugins/inputs/redfish/README.md b/plugins/inputs/redfish/README.md index d1c4a128aa74b..26f5a1844ec13 100644 --- a/plugins/inputs/redfish/README.md +++ b/plugins/inputs/redfish/README.md @@ -45,6 +45,8 @@ The `redfish` plugin gathers metrics and status information about CPU temperatu - reading_celsius - upper_threshold_critical - upper_threshold_fatal + - lower_threshold_critical + - lower_threshold_fatal + redfish_thermal_fans @@ -62,6 +64,8 @@ The `redfish` plugin gathers metrics and status information about CPU temperatu - reading_rpm (or) reading_percent - upper_threshold_critical - upper_threshold_fatal + - lower_threshold_critical + - lower_threshold_fatal - redfish_power_powersupplies @@ -98,6 +102,8 @@ The `redfish` plugin gathers metrics and status information about CPU temperatu - reading_volts - upper_threshold_critical - upper_threshold_fatal + - lower_threshold_critical + - lower_threshold_fatal ### Example Output diff --git a/plugins/inputs/redfish/redfish.go b/plugins/inputs/redfish/redfish.go index c16db567aee98..54d1d15b8c097 100644 --- a/plugins/inputs/redfish/redfish.go +++ b/plugins/inputs/redfish/redfish.go @@ -4,106 +4,29 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "net/http" + "net/url" + "path" + "time" "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/plugins/common/tls" "github.com/influxdata/telegraf/plugins/inputs" ) -type Cpu struct { - Name string - ReadingCelsius int - UpperThresholdCritical int - UpperThresholdFatal int - Status CpuStatus -} -type Payload struct { - Temperatures []Cpu `json:",omitempty"` - Fans []Speed `json:",omitempty"` - PowerSupplies []Watt `json:",omitempty"` - Hostname string `json:",omitempty"` - Voltages []Volt `json:",omitempty"` - Location *Address `json:",omitempty"` -} -type CpuStatus struct { - State string - Health string -} -type Speed struct { - Name string - Reading int - ReadingUnits string - UpperThresholdCritical int - UpperThresholdFatal int - Status FansStatus -} -type FansStatus struct { - State string - Health string -} -type Watt struct { - Name string - PowerInputWatts float64 - PowerCapacityWatts float64 - PowerOutputWatts float64 - LastPowerOutputWatts float64 - Status PowerStatus - LineInputVoltage float64 -} -type PowerStatus struct { - State string - Health string -} -type Volt struct { - Name string - ReadingVolts float64 - UpperThresholdCritical float64 - UpperThresholdFatal float64 - Status VoltStatus -} -type VoltStatus struct { - State string - Health string -} -type Address struct { - PostalAddress PostalAddress - Placement Placement -} -type PostalAddress struct { - DataCenter string - Room string -} -type Placement struct { - Rack string - Row string -} -type Redfish struct { - Address string `toml:"address"` - BasicAuthUsername string `toml:"username"` - BasicAuthPassword string `toml:"password"` - ComputerSystemId string `toml:"computer_system_id"` - client http.Client - tls.ClientConfig - Timeout internal.Duration `toml:"timeout"` - payload Payload -} - -func (r *Redfish) Description() string { - return "Read CPU, Fans, Powersupply and Voltage metrics of hardware server through redfish APIs" -} - -var redfishConfig = ` - ## Redfish API Base URL. +const description = "Read CPU, Fans, Powersupply and Voltage metrics of hardware server through redfish APIs" +const sampleConfig = ` + ## Server url address = "https://127.0.0.1:5000" - ## Credentials for the Redfish API. + ## Username, Password for hardware server username = "root" password = "password123456" - ## System Id to collect data for in Redfish APIs. - computer_system_id="System.Embedded.1" + ## ComputerSystemId + computer_system_id="2M220100SL" ## Amount of time allowed to complete the HTTP request # timeout = "5s" @@ -116,38 +39,146 @@ var redfishConfig = ` # insecure_skip_verify = false ` +type Redfish struct { + Address string `toml:"address"` + Username string `toml:"username"` + Password string `toml:"password"` + ComputerSystemId string `toml:"computer_system_id"` + Timeout config.Duration `toml:"timeout"` + + client http.Client + tls.ClientConfig + baseURL *url.URL +} + +type System struct { + Hostname string `json:"hostname"` + Links struct { + Chassis []struct { + Ref string `json:"@odata.id"` + } + } +} + +type Chassis struct { + Location *Location + Power struct { + Ref string `json:"@odata.id"` + } + Thermal struct { + Ref string `json:"@odata.id"` + } +} + +type Power struct { + PowerSupplies []struct { + Name string + PowerInputWatts *float64 + PowerCapacityWatts *float64 + PowerOutputWatts *float64 + LastPowerOutputWatts *float64 + Status Status + LineInputVoltage *float64 + } + Voltages []struct { + Name string + ReadingVolts *float64 + UpperThresholdCritical *float64 + UpperThresholdFatal *float64 + LowerThresholdCritical *float64 + LowerThresholdFatal *float64 + Status Status + } +} + +type Thermal struct { + Fans []struct { + Name string + Reading *int64 + ReadingUnits *string + UpperThresholdCritical *int64 + UpperThresholdFatal *int64 + LowerThresholdCritical *int64 + LowerThresholdFatal *int64 + Status Status + } + Temperatures []struct { + Name string + ReadingCelsius *float64 + UpperThresholdCritical *float64 + UpperThresholdFatal *float64 + LowerThresholdCritical *float64 + LowerThresholdFatal *float64 + Status Status + } +} + +type Location struct { + PostalAddress struct { + DataCenter string + Room string + } + Placement struct { + Rack string + Row string + } +} + +type Status struct { + State string + Health string +} + +func (r *Redfish) Description() string { + return description +} + func (r *Redfish) SampleConfig() string { - return redfishConfig + return sampleConfig } func (r *Redfish) Init() error { - if len(r.Address) == 0 || len(r.BasicAuthUsername) == 0 || len(r.BasicAuthPassword) == 0 { - return fmt.Errorf("did not provide IP or username and password") + if r.Address == "" { + return fmt.Errorf("did not provide IP") + } + + if r.Username == "" && r.Password == "" { + return fmt.Errorf("did not provide username and password") } - if len(r.ComputerSystemId) == 0 { + + if r.ComputerSystemId == "" { return fmt.Errorf("did not provide the computer system ID of the resource") } + + var err error + r.baseURL, err = url.Parse(r.Address) + if err != nil { + return err + } + tlsCfg, err := r.ClientConfig.TLSConfig() if err != nil { return err } + r.client = http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsCfg, Proxy: http.ProxyFromEnvironment, }, - Timeout: r.Timeout.Duration, + Timeout: time.Duration(r.Timeout), } return nil } -func (r *Redfish) GetData(url string) error { +func (r *Redfish) getData(url string, payload interface{}) error { req, err := http.NewRequest("GET", url, nil) if err != nil { return err } - req.SetBasicAuth(r.BasicAuthUsername, r.BasicAuthPassword) + + req.SetBasicAuth(r.Username, r.Password) req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") resp, err := r.client.Do(req) @@ -155,91 +186,157 @@ func (r *Redfish) GetData(url string) error { return err } defer resp.Body.Close() - if resp.StatusCode == 200 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - err = json.Unmarshal(body, &r.payload) - if err != nil { - return fmt.Errorf("error parsing input: %v", err) - } - } else { + if resp.StatusCode != 200 { return fmt.Errorf("received status code %d (%s), expected 200", resp.StatusCode, http.StatusText(resp.StatusCode)) } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + err = json.Unmarshal(body, &payload) + if err != nil { + return fmt.Errorf("error parsing input: %v", err) + } + return nil } +func (r *Redfish) getComputerSystem(id string) (*System, error) { + loc := r.baseURL.ResolveReference(&url.URL{Path: path.Join("/redfish/v1/Systems/", id)}) + system := &System{} + err := r.getData(loc.String(), system) + if err != nil { + return nil, err + } + return system, nil +} + +func (r *Redfish) getChassis(ref string) (*Chassis, error) { + loc := r.baseURL.ResolveReference(&url.URL{Path: ref}) + chassis := &Chassis{} + err := r.getData(loc.String(), chassis) + if err != nil { + return nil, err + } + return chassis, nil +} + +func (r *Redfish) getPower(ref string) (*Power, error) { + loc := r.baseURL.ResolveReference(&url.URL{Path: ref}) + power := &Power{} + err := r.getData(loc.String(), power) + if err != nil { + return nil, err + } + return power, nil +} + +func (r *Redfish) getThermal(ref string) (*Thermal, error) { + loc := r.baseURL.ResolveReference(&url.URL{Path: ref}) + thermal := &Thermal{} + err := r.getData(loc.String(), thermal) + if err != nil { + return nil, err + } + return thermal, nil +} + func (r *Redfish) Gather(acc telegraf.Accumulator) error { - var url []string - url = append(url, fmt.Sprint(r.Address, "/redfish/v1/Chassis/", r.ComputerSystemId, "/Thermal"), fmt.Sprint(r.Address, "/redfish/v1/Chassis/", r.ComputerSystemId, "/Power"), fmt.Sprint(r.Address, "/redfish/v1/Systems/", r.ComputerSystemId), fmt.Sprint(r.Address, "/redfish/v1/Chassis/", r.ComputerSystemId, "/")) - for _, i := range url { - err := r.GetData(i) + address, _, err := net.SplitHostPort(r.baseURL.Host) + if err != nil { + address = r.baseURL.Host + } + + system, err := r.getComputerSystem(r.ComputerSystemId) + if err != nil { + return err + } + + for _, link := range system.Links.Chassis { + chassis, err := r.getChassis(link.Ref) if err != nil { return err } - } - if r.payload.Location != nil { - for _, j := range r.payload.Temperatures { - // Tags + + thermal, err := r.getThermal(chassis.Thermal.Ref) + if err != nil { + return err + } + + for _, j := range thermal.Temperatures { tags := map[string]string{} - tags["address"] = r.Address + tags["address"] = address tags["name"] = j.Name - tags["source"] = r.payload.Hostname + tags["source"] = system.Hostname tags["state"] = j.Status.State tags["health"] = j.Status.Health - tags["datacenter"] = r.payload.Location.PostalAddress.DataCenter - tags["room"] = r.payload.Location.PostalAddress.Room - tags["rack"] = r.payload.Location.Placement.Rack - tags["row"] = r.payload.Location.Placement.Row - // Fields + if chassis.Location != nil { + tags["datacenter"] = chassis.Location.PostalAddress.DataCenter + tags["room"] = chassis.Location.PostalAddress.Room + tags["rack"] = chassis.Location.Placement.Rack + tags["row"] = chassis.Location.Placement.Row + } + fields := make(map[string]interface{}) fields["reading_celsius"] = j.ReadingCelsius fields["upper_threshold_critical"] = j.UpperThresholdCritical fields["upper_threshold_fatal"] = j.UpperThresholdFatal + fields["lower_threshold_critical"] = j.LowerThresholdCritical + fields["lower_threshold_fatal"] = j.LowerThresholdFatal acc.AddFields("redfish_thermal_temperatures", fields, tags) } - for _, j := range r.payload.Fans { - // Tags + + for _, j := range thermal.Fans { tags := map[string]string{} fields := make(map[string]interface{}) - tags["address"] = r.Address + tags["address"] = address tags["name"] = j.Name - tags["source"] = r.payload.Hostname + tags["source"] = system.Hostname tags["state"] = j.Status.State tags["health"] = j.Status.Health - tags["datacenter"] = r.payload.Location.PostalAddress.DataCenter - tags["room"] = r.payload.Location.PostalAddress.Room - tags["rack"] = r.payload.Location.Placement.Rack - tags["row"] = r.payload.Location.Placement.Row + if chassis.Location != nil { + tags["datacenter"] = chassis.Location.PostalAddress.DataCenter + tags["room"] = chassis.Location.PostalAddress.Room + tags["rack"] = chassis.Location.Placement.Rack + tags["row"] = chassis.Location.Placement.Row + } - // Fields - if j.ReadingUnits == "RPM" { + if j.ReadingUnits != nil && *j.ReadingUnits == "RPM" { fields["upper_threshold_critical"] = j.UpperThresholdCritical fields["upper_threshold_fatal"] = j.UpperThresholdFatal + fields["lower_threshold_critical"] = j.LowerThresholdCritical + fields["lower_threshold_fatal"] = j.LowerThresholdFatal fields["reading_rpm"] = j.Reading - } else { fields["reading_percent"] = j.Reading } acc.AddFields("redfish_thermal_fans", fields, tags) } - for _, j := range r.payload.PowerSupplies { - // Tags + + power, err := r.getPower(chassis.Power.Ref) + if err != nil { + return err + } + + for _, j := range power.PowerSupplies { tags := map[string]string{} - tags["address"] = r.Address + tags["address"] = address tags["name"] = j.Name - tags["source"] = r.payload.Hostname + tags["source"] = system.Hostname tags["state"] = j.Status.State tags["health"] = j.Status.Health - tags["datacenter"] = r.payload.Location.PostalAddress.DataCenter - tags["room"] = r.payload.Location.PostalAddress.Room - tags["rack"] = r.payload.Location.Placement.Rack - tags["row"] = r.payload.Location.Placement.Row - // Fields + if chassis.Location != nil { + tags["datacenter"] = chassis.Location.PostalAddress.DataCenter + tags["room"] = chassis.Location.PostalAddress.Room + tags["rack"] = chassis.Location.Placement.Rack + tags["row"] = chassis.Location.Placement.Row + } + fields := make(map[string]interface{}) fields["power_input_watts"] = j.PowerInputWatts fields["power_output_watts"] = j.PowerOutputWatts @@ -248,88 +345,27 @@ func (r *Redfish) Gather(acc telegraf.Accumulator) error { fields["power_capacity_watts"] = j.PowerCapacityWatts acc.AddFields("redfish_power_powersupplies", fields, tags) } - for _, j := range r.payload.Voltages { - // Tags - tags := map[string]string{} - tags["address"] = r.Address - tags["name"] = j.Name - tags["source"] = r.payload.Hostname - tags["state"] = j.Status.State - tags["health"] = j.Status.Health - tags["datacenter"] = r.payload.Location.PostalAddress.DataCenter - tags["room"] = r.payload.Location.PostalAddress.Room - tags["rack"] = r.payload.Location.Placement.Rack - tags["row"] = r.payload.Location.Placement.Row - // Fields - fields := make(map[string]interface{}) - fields["reading_volts"] = j.ReadingVolts - fields["upper_threshold_critical"] = j.UpperThresholdCritical - fields["upper_threshold_fatal"] = j.UpperThresholdFatal - acc.AddFields("redfish_power_voltages", fields, tags) - } - } else { - for _, j := range r.payload.Temperatures { - // Tags - tags := map[string]string{} - tags["address"] = r.Address - tags["name"] = j.Name - tags["source"] = r.payload.Hostname - tags["state"] = j.Status.State - tags["health"] = j.Status.Health - // Fields - fields := make(map[string]interface{}) - fields["reading_celsius"] = j.ReadingCelsius - fields["upper_threshold_critical"] = j.UpperThresholdCritical - fields["upper_threshold_fatal"] = j.UpperThresholdFatal - acc.AddFields("redfish_thermal_temperatures", fields, tags) - } - for _, j := range r.payload.Fans { - // Tags + + for _, j := range power.Voltages { tags := map[string]string{} - fields := make(map[string]interface{}) - tags["address"] = r.Address + tags["address"] = address tags["name"] = j.Name - tags["source"] = r.payload.Hostname + tags["source"] = system.Hostname tags["state"] = j.Status.State tags["health"] = j.Status.Health - // Fields - if j.ReadingUnits == "RPM" { - fields["upper_threshold_critical"] = j.UpperThresholdCritical - fields["upper_threshold_fatal"] = j.UpperThresholdFatal - fields["reading_rpm"] = j.Reading - } else { - fields["reading_percent"] = j.Reading + if chassis.Location != nil { + tags["datacenter"] = chassis.Location.PostalAddress.DataCenter + tags["room"] = chassis.Location.PostalAddress.Room + tags["rack"] = chassis.Location.Placement.Rack + tags["row"] = chassis.Location.Placement.Row } - acc.AddFields("redfish_thermal_fans", fields, tags) - } - for _, j := range r.payload.PowerSupplies { - // Tags - tags := map[string]string{} - tags["address"] = r.Address - tags["name"] = j.Name //j.Name - tags["source"] = r.payload.Hostname - tags["state"] = j.Status.State - tags["health"] = j.Status.Health - // Fields - fields := make(map[string]interface{}) - fields["line_input_voltage"] = j.LineInputVoltage - fields["last_power_output_watts"] = j.LastPowerOutputWatts - fields["power_capacity_watts"] = j.PowerCapacityWatts - acc.AddFields("redfish_power_powersupplies", fields, tags) - } - for _, j := range r.payload.Voltages { - // Tags - tags := map[string]string{} - tags["address"] = r.Address - tags["name"] = j.Name - tags["source"] = r.payload.Hostname - tags["state"] = j.Status.State - tags["health"] = j.Status.Health - // Fields + fields := make(map[string]interface{}) fields["reading_volts"] = j.ReadingVolts fields["upper_threshold_critical"] = j.UpperThresholdCritical fields["upper_threshold_fatal"] = j.UpperThresholdFatal + fields["lower_threshold_critical"] = j.LowerThresholdCritical + fields["lower_threshold_fatal"] = j.LowerThresholdFatal acc.AddFields("redfish_power_voltages", fields, tags) } } @@ -338,5 +374,7 @@ func (r *Redfish) Gather(acc telegraf.Accumulator) error { } func init() { - inputs.Add("redfish", func() telegraf.Input { return &Redfish{} }) + inputs.Add("redfish", func() telegraf.Input { + return &Redfish{} + }) } diff --git a/plugins/inputs/redfish/redfish_test.go b/plugins/inputs/redfish/redfish_test.go index 86f9012947c7a..8821b3d97557f 100644 --- a/plugins/inputs/redfish/redfish_test.go +++ b/plugins/inputs/redfish/redfish_test.go @@ -1,16 +1,19 @@ package redfish import ( - "github.com/influxdata/telegraf" - "github.com/influxdata/telegraf/testutil" - "github.com/stretchr/testify/require" + "net" "net/http" "net/http/httptest" + "net/url" "testing" "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/require" ) -func TestApis(t *testing.T) { +func TestDellApis(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -24,133 +27,21 @@ func TestApis(t *testing.T) { http.ServeFile(w, r, "testdata/dell_thermal.json") case "/redfish/v1/Chassis/System.Embedded.1/Power": http.ServeFile(w, r, "testdata/dell_power.json") - case "/redfish/v1/Chassis/System.Embedded.1/": + case "/redfish/v1/Chassis/System.Embedded.1": http.ServeFile(w, r, "testdata/dell_chassis.json") case "/redfish/v1/Systems/System.Embedded.1": http.ServeFile(w, r, "testdata/dell_systems.json") - case "/redfish/v1/Chassis/System.Embedded.2/Thermal": - http.ServeFile(w, r, "testdata/hp_thermal.json") - case "/redfish/v1/Chassis/System.Embedded.2/Power": - http.ServeFile(w, r, "testdata/hp_power.json") - case "/redfish/v1/Systems/System.Embedded.2": - http.ServeFile(w, r, "testdata/hp_systems.json") - case "/redfish/v1/Chassis/System.Embedded.2/": - http.ServeFile(w, r, "testdata/hp_power.json") default: - panic("Cannot handle request") + w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() - expected_metrics_hp := []telegraf.Metric{ - testutil.MustMetric( - "redfish_thermal_temperatures", - map[string]string{ - "name": "01-Inlet Ambient", - "source": "tpa-hostname", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "reading_celsius": 19, - "upper_threshold_critical": 42, - "upper_threshold_fatal": 47, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_thermal_temperatures", - map[string]string{ - "name": "44-P/S 2 Zone", - "source": "tpa-hostname", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "reading_celsius": 34, - "upper_threshold_critical": 75, - "upper_threshold_fatal": 80, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_thermal_fans", - map[string]string{ - "source": "tpa-hostname", - "name": "Fan 1", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "reading_percent": 23, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_thermal_fans", - map[string]string{ - "source": "tpa-hostname", - "name": "Fan 2", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "reading_percent": 23, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_thermal_fans", - map[string]string{ - "source": "tpa-hostname", - "name": "Fan 3", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "reading_percent": 23, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_power_powersupplies", - map[string]string{ - "source": "tpa-hostname", - "name": "HpeServerPowerSupply", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "power_capacity_watts": 800.0, - "line_input_voltage": 205.0, - "last_power_output_watts": 0.0, - }, - time.Unix(0, 0), - ), - testutil.MustMetric( - "redfish_power_powersupplies", - map[string]string{ - "source": "tpa-hostname", - "name": "HpeServerPowerSupply", - "address": ts.URL, - "health": "OK", - "state": "Enabled", - }, - map[string]interface{}{ - "power_capacity_watts": 800.0, - "line_input_voltage": 205.0, - "last_power_output_watts": 90.0, - }, - time.Unix(0, 0), - ), - } + u, err := url.Parse(ts.URL) + require.NoError(t, err) + address, _, err := net.SplitHostPort(u.Host) + require.NoError(t, err) expected_metrics := []telegraf.Metric{ testutil.MustMetric( @@ -158,7 +49,7 @@ func TestApis(t *testing.T) { map[string]string{ "name": "CPU1 Temp", "source": "tpa-hostname", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -167,9 +58,11 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ - "reading_celsius": 40, - "upper_threshold_critical": 93, - "upper_threshold_fatal": 93, + "lower_threshold_critical": 3.0, + "lower_threshold_fatal": 3.0, + "reading_celsius": 40.0, + "upper_threshold_critical": 93.0, + "upper_threshold_fatal": 93.0, }, time.Unix(0, 0), ), @@ -178,7 +71,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan1A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -188,8 +81,8 @@ func TestApis(t *testing.T) { }, map[string]interface{}{ "reading_rpm": 17760, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, }, time.Unix(0, 0), ), @@ -198,7 +91,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan1B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -207,9 +100,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15360, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -218,7 +111,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan2A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -227,9 +120,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 17880, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -238,7 +131,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan2B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -247,9 +140,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15120, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -258,7 +151,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan3A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -267,9 +160,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 18000, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -278,7 +171,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan3B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -287,9 +180,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15600, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -298,7 +191,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan4A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -307,9 +200,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 17280, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -318,7 +211,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan4B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -327,9 +220,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15360, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -338,7 +231,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan5A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -347,9 +240,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 17640, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -358,7 +251,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan5B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -367,9 +260,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15600, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -378,7 +271,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan6A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -387,9 +280,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 17760, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -398,7 +291,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan6B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -407,9 +300,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15600, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -418,7 +311,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan7A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -427,9 +320,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 17400, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -438,7 +331,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan7B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -447,9 +340,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15720, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -458,7 +351,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan8A", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -467,9 +360,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 18000, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -478,7 +371,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board Fan8B", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -487,9 +380,9 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ + "lower_threshold_critical": 600, + "lower_threshold_fatal": 600, "reading_rpm": 15840, - "upper_threshold_critical": 0, - "upper_threshold_fatal": 0, }, time.Unix(0, 0), ), @@ -498,7 +391,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "PS1 Status", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -507,11 +400,10 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ - "power_capacity_watts": 750.00, - "power_input_watts": 900.0, - "power_output_watts": 203.0, - "last_power_output_watts": 0.0, - "line_input_voltage": 206.00, + "power_capacity_watts": 750.00, + "power_input_watts": 900.0, + "power_output_watts": 203.0, + "line_input_voltage": 206.00, }, time.Unix(0, 0), ), @@ -520,7 +412,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board DIMM PG", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -529,9 +421,7 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ - "reading_volts": 1.0, - "upper_threshold_critical": 0.0, - "upper_threshold_fatal": 0.0, + "reading_volts": 1.0, }, time.Unix(0, 0), ), @@ -540,7 +430,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board NDC PG", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -549,9 +439,7 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ - "reading_volts": 1.0, - "upper_threshold_critical": 0.0, - "upper_threshold_fatal": 0.0, + "reading_volts": 1.0, }, time.Unix(0, 0), ), @@ -561,7 +449,7 @@ func TestApis(t *testing.T) { map[string]string{ "source": "tpa-hostname", "name": "System Board PS1 PG FAIL", - "address": ts.URL, + "address": address, "datacenter": "", "health": "OK", "rack": "", @@ -570,33 +458,171 @@ func TestApis(t *testing.T) { "state": "Enabled", }, map[string]interface{}{ - "reading_volts": 1.0, - "upper_threshold_critical": 0.0, - "upper_threshold_fatal": 0.0, + "reading_volts": 1.0, }, time.Unix(0, 0), ), } plugin := &Redfish{ - Address: ts.URL, - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.1", + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemId: "System.Embedded.1", } plugin.Init() var acc testutil.Accumulator - err := plugin.Gather(&acc) + err = plugin.Gather(&acc) require.NoError(t, err) require.True(t, acc.HasMeasurement("redfish_thermal_temperatures")) testutil.RequireMetricsEqual(t, expected_metrics, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} + +func TestHPApis(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.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) + + expected_metrics_hp := []telegraf.Metric{ + testutil.MustMetric( + "redfish_thermal_temperatures", + map[string]string{ + "name": "01-Inlet Ambient", + "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", + "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{ + "source": "tpa-hostname", + "name": "Fan 1", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_percent": 23, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_thermal_fans", + map[string]string{ + "source": "tpa-hostname", + "name": "Fan 2", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_percent": 23, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_thermal_fans", + map[string]string{ + "source": "tpa-hostname", + "name": "Fan 3", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "reading_percent": 23, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_power_powersupplies", + map[string]string{ + "source": "tpa-hostname", + "name": "HpeServerPowerSupply", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "power_capacity_watts": 800.0, + "line_input_voltage": 205.0, + "last_power_output_watts": 0.0, + }, + time.Unix(0, 0), + ), + testutil.MustMetric( + "redfish_power_powersupplies", + map[string]string{ + "source": "tpa-hostname", + "name": "HpeServerPowerSupply", + "address": address, + "health": "OK", + "state": "Enabled", + }, + map[string]interface{}{ + "power_capacity_watts": 800.0, + "line_input_voltage": 205.0, + "last_power_output_watts": 90.0, + }, + time.Unix(0, 0), + ), + } hp_plugin := &Redfish{ - Address: ts.URL, - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.2", + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemId: "1", } hp_plugin.Init() var hp_acc testutil.Accumulator @@ -619,10 +645,10 @@ func checkAuth(r *http.Request, username, password string) bool { func TestConnection(t *testing.T) { r := &Redfish{ - Address: "http://127.0.0.1", - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.1", + Address: "http://127.0.0.1", + Username: "test", + Password: "test", + ComputerSystemId: "System.Embedded.1", } var acc testutil.Accumulator @@ -645,16 +671,16 @@ func TestInvalidUsernameorPassword(t *testing.T) { case "/redfish/v1/Chassis/System.Embedded.1/Thermal": http.ServeFile(w, r, "testdata/dell_thermal.json") default: - panic("Cannot handle request") + w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() r := &Redfish{ - Address: ts.URL, - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.1", + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemId: "System.Embedded.1", } var acc testutil.Accumulator @@ -676,7 +702,7 @@ func TestNoUsernameorPasswordConfiguration(t *testing.T) { case "/redfish/v1/Chassis/System.Embedded.1/Thermal": http.ServeFile(w, r, "testdata/dell_thermal.json") default: - panic("Cannot handle request") + w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() @@ -688,7 +714,7 @@ func TestNoUsernameorPasswordConfiguration(t *testing.T) { err := r.Init() require.Error(t, err) - require.EqualError(t, err, "did not provide IP or username and password") + require.EqualError(t, err, "did not provide username and password") } func TestInvalidDellJSON(t *testing.T) { @@ -697,35 +723,35 @@ func TestInvalidDellJSON(t *testing.T) { name string thermalfilename string powerfilename string - locationfilename string + chassisfilename string hostnamefilename string }{ { name: "check Thermal", thermalfilename: "testdata/dell_thermalinvalid.json", powerfilename: "testdata/dell_power.json", - locationfilename: "testdata/dell_chassis.json", + chassisfilename: "testdata/dell_chassis.json", hostnamefilename: "testdata/dell_systems.json", }, { name: "check Power", thermalfilename: "testdata/dell_thermal.json", powerfilename: "testdata/dell_powerinvalid.json", - locationfilename: "testdata/dell_chassis.json", + chassisfilename: "testdata/dell_chassis.json", hostnamefilename: "testdata/dell_systems.json", }, { name: "check Location", thermalfilename: "testdata/dell_thermal.json", powerfilename: "testdata/dell_power.json", - locationfilename: "testdata/dell_chassisinvalid.json", + chassisfilename: "testdata/dell_chassisinvalid.json", hostnamefilename: "testdata/dell_systems.json", }, { name: "check Hostname", thermalfilename: "testdata/dell_thermal.json", powerfilename: "testdata/dell_power.json", - locationfilename: "testdata/dell_chassis.json", + chassisfilename: "testdata/dell_chassis.json", hostnamefilename: "testdata/dell_systemsinvalid.json", }, } @@ -742,21 +768,21 @@ func TestInvalidDellJSON(t *testing.T) { http.ServeFile(w, r, tt.thermalfilename) case "/redfish/v1/Chassis/System.Embedded.1/Power": http.ServeFile(w, r, tt.powerfilename) - case "/redfish/v1/Chassis/System.Embedded.1/": - http.ServeFile(w, r, tt.locationfilename) + case "/redfish/v1/Chassis/System.Embedded.1": + http.ServeFile(w, r, tt.chassisfilename) case "/redfish/v1/Systems/System.Embedded.1": http.ServeFile(w, r, tt.hostnamefilename) default: - panic("Cannot handle request") + w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() plugin := &Redfish{ - Address: ts.URL, - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.1", + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemId: "System.Embedded.1", } plugin.Init() @@ -775,59 +801,67 @@ func TestInvalidHPJSON(t *testing.T) { thermalfilename string powerfilename string hostnamefilename string + chassisfilename string }{ { name: "check Thermal", thermalfilename: "testdata/hp_thermalinvalid.json", powerfilename: "testdata/hp_power.json", hostnamefilename: "testdata/hp_systems.json", + chassisfilename: "testdata/hp_chassis.json", }, { name: "check Power", thermalfilename: "testdata/hp_thermal.json", powerfilename: "testdata/hp_powerinvalid.json", hostnamefilename: "testdata/hp_systems.json", + chassisfilename: "testdata/hp_chassis.json", }, { name: "check Hostname", thermalfilename: "testdata/hp_thermal.json", powerfilename: "testdata/hp_power.json", hostnamefilename: "testdata/hp_systemsinvalid.json", + chassisfilename: "testdata/hp_chassis.json", }, } for _, tt := range tests { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - if !checkAuth(r, "test", "test") { - http.Error(w, "Unauthorized.", 401) - return + t.Run(tt.name, func(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, tt.thermalfilename) + case "/redfish/v1/Chassis/1/Power": + http.ServeFile(w, r, tt.powerfilename) + case "/redfish/v1/Chassis/1/": + http.ServeFile(w, r, tt.chassisfilename) + case "/redfish/v1/Systems/System.Embedded.2": + http.ServeFile(w, r, tt.hostnamefilename) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer ts.Close() + + plugin := &Redfish{ + Address: ts.URL, + Username: "test", + Password: "test", + ComputerSystemId: "System.Embedded.2", } - switch r.URL.Path { - case "/redfish/v1/Chassis/System.Embedded.2/Thermal": - http.ServeFile(w, r, tt.thermalfilename) - case "/redfish/v1/Chassis/System.Embedded.2/Power": - http.ServeFile(w, r, tt.powerfilename) - case "/redfish/v1/Systems/System.Embedded.2": - http.ServeFile(w, r, tt.hostnamefilename) - default: - panic("Cannot handle request") - } - })) - defer ts.Close() + plugin.Init() - plugin := &Redfish{ - Address: ts.URL, - BasicAuthUsername: "test", - BasicAuthPassword: "test", - ComputerSystemId: "System.Embedded.2", - } - - plugin.Init() - - var acc testutil.Accumulator - err := plugin.Gather(&acc) - require.Error(t, err) - require.Contains(t, err.Error(), "error parsing input:") + var acc testutil.Accumulator + err := plugin.Gather(&acc) + require.Error(t, err) + require.Contains(t, err.Error(), "error parsing input:") + }) } } diff --git a/plugins/inputs/redfish/testdata/hp_chassis.json b/plugins/inputs/redfish/testdata/hp_chassis.json new file mode 100644 index 0000000000000..a7da7face3f37 --- /dev/null +++ b/plugins/inputs/redfish/testdata/hp_chassis.json @@ -0,0 +1,8 @@ +{ + "Power": { + "@odata.id": "/redfish/v1/Chassis/1/Power" + }, + "Thermal": { + "@odata.id": "/redfish/v1/Chassis/1/Thermal" + } +}