Traverse redfish api using resource links (#7722)

This commit is contained in:
Daniel Nelson 2020-07-07 14:25:55 -07:00 committed by GitHub
parent bf9c9bfe4d
commit a19befe376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 824 additions and 738 deletions

View File

@ -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

View File

@ -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 len(r.ComputerSystemId) == 0 {
if r.Username == "" && r.Password == "" {
return fmt.Errorf("did not provide username and password")
}
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
for _, j := range power.Voltages {
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
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
tags := map[string]string{}
fields := make(map[string]interface{})
tags["address"] = r.Address
tags["name"] = j.Name
tags["source"] = r.payload.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{}
})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
{
"Power": {
"@odata.id": "/redfish/v1/Chassis/1/Power"
},
"Thermal": {
"@odata.id": "/redfish/v1/Chassis/1/Thermal"
}
}