Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
536
plugins/inputs/redfish/redfish.go
Normal file
536
plugins/inputs/redfish/redfish.go
Normal file
|
@ -0,0 +1,536 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package redfish
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
// tag sets used for including redfish OData link parent data
|
||||
tagSetChassisLocation = "chassis.location"
|
||||
tagSetChassis = "chassis"
|
||||
)
|
||||
|
||||
type Redfish struct {
|
||||
Address string `toml:"address"`
|
||||
Username config.Secret `toml:"username"`
|
||||
Password config.Secret `toml:"password"`
|
||||
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
|
||||
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 {
|
||||
ChassisType string
|
||||
Location *location
|
||||
Manufacturer string
|
||||
Model string
|
||||
PartNumber string
|
||||
Power struct {
|
||||
Ref string `json:"@odata.id"`
|
||||
}
|
||||
PowerState string
|
||||
SKU string
|
||||
SerialNumber string
|
||||
Status status
|
||||
Thermal struct {
|
||||
Ref string `json:"@odata.id"`
|
||||
}
|
||||
}
|
||||
|
||||
type power struct {
|
||||
PowerControl []struct {
|
||||
Name string
|
||||
MemberID string
|
||||
PowerAllocatedWatts *float64
|
||||
PowerAvailableWatts *float64
|
||||
PowerCapacityWatts *float64
|
||||
PowerConsumedWatts *float64
|
||||
PowerRequestedWatts *float64
|
||||
PowerMetrics struct {
|
||||
AverageConsumedWatts *float64
|
||||
IntervalInMin int
|
||||
MaxConsumedWatts *float64
|
||||
MinConsumedWatts *float64
|
||||
}
|
||||
}
|
||||
PowerSupplies []struct {
|
||||
Name string
|
||||
MemberID string
|
||||
PowerInputWatts *float64
|
||||
PowerCapacityWatts *float64
|
||||
PowerOutputWatts *float64
|
||||
LastPowerOutputWatts *float64
|
||||
Status status
|
||||
LineInputVoltage *float64
|
||||
}
|
||||
Voltages []struct {
|
||||
Name string
|
||||
MemberID string
|
||||
ReadingVolts *float64
|
||||
UpperThresholdCritical *float64
|
||||
UpperThresholdFatal *float64
|
||||
LowerThresholdCritical *float64
|
||||
LowerThresholdFatal *float64
|
||||
Status status
|
||||
}
|
||||
}
|
||||
|
||||
type thermal struct {
|
||||
Fans []struct {
|
||||
Name string
|
||||
MemberID string
|
||||
FanName string
|
||||
CurrentReading *int64
|
||||
Reading *int64
|
||||
ReadingUnits *string
|
||||
UpperThresholdCritical *int64
|
||||
UpperThresholdFatal *int64
|
||||
LowerThresholdCritical *int64
|
||||
LowerThresholdFatal *int64
|
||||
Status status
|
||||
}
|
||||
Temperatures []struct {
|
||||
Name string
|
||||
MemberID 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 (*Redfish) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (r *Redfish) Init() error {
|
||||
if r.Address == "" {
|
||||
return errors.New("did not provide IP")
|
||||
}
|
||||
|
||||
if r.Username.Empty() && r.Password.Empty() {
|
||||
return errors.New("did not provide username and password")
|
||||
}
|
||||
|
||||
if r.ComputerSystemID == "" {
|
||||
return errors.New("did not provide the computer system ID of the resource")
|
||||
}
|
||||
|
||||
if len(r.IncludeMetrics) == 0 {
|
||||
return errors.New("no metrics specified to collect")
|
||||
}
|
||||
for _, metric := range r.IncludeMetrics {
|
||||
switch metric {
|
||||
case "thermal", "power":
|
||||
default:
|
||||
return fmt.Errorf("unknown metric requested: %s", metric)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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: time.Duration(r.Timeout),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Redfish) Gather(acc telegraf.Accumulator) error {
|
||||
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
|
||||
}
|
||||
|
||||
for _, metric := range r.IncludeMetrics {
|
||||
var err error
|
||||
switch metric {
|
||||
case "thermal":
|
||||
err = r.gatherThermal(acc, address, system, chassis)
|
||||
case "power":
|
||||
err = r.gatherPower(acc, address, system, chassis)
|
||||
default:
|
||||
return fmt.Errorf("unknown metric requested: %s", metric)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Redfish) getData(address string, payload interface{}) error {
|
||||
req, err := http.NewRequest("GET", address, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username, err := r.Username.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting username failed: %w", err)
|
||||
}
|
||||
user := username.String()
|
||||
username.Destroy()
|
||||
|
||||
password, err := r.Password.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting password failed: %w", err)
|
||||
}
|
||||
pass := password.String()
|
||||
password.Destroy()
|
||||
|
||||
req.SetBasicAuth(user, pass)
|
||||
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
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("received status code %d (%s) for address %s, expected 200",
|
||||
resp.StatusCode,
|
||||
http.StatusText(resp.StatusCode),
|
||||
r.Address)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing input: %w", 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 setChassisTags(chassis *chassis, tags map[string]string) {
|
||||
tags["chassis_chassistype"] = chassis.ChassisType
|
||||
tags["chassis_manufacturer"] = chassis.Manufacturer
|
||||
tags["chassis_model"] = chassis.Model
|
||||
tags["chassis_partnumber"] = chassis.PartNumber
|
||||
tags["chassis_powerstate"] = chassis.PowerState
|
||||
tags["chassis_sku"] = chassis.SKU
|
||||
tags["chassis_serialnumber"] = chassis.SerialNumber
|
||||
tags["chassis_state"] = chassis.Status.State
|
||||
tags["chassis_health"] = chassis.Status.Health
|
||||
}
|
||||
|
||||
func (r *Redfish) gatherThermal(acc telegraf.Accumulator, address string, system *system, chassis *chassis) error {
|
||||
thermal, err := r.getThermal(chassis.Thermal.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, j := range thermal.Temperatures {
|
||||
tags := make(map[string]string, 19)
|
||||
tags["member_id"] = j.MemberID
|
||||
tags["address"] = address
|
||||
tags["name"] = j.Name
|
||||
tags["source"] = system.Hostname
|
||||
tags["state"] = j.Status.State
|
||||
tags["health"] = j.Status.Health
|
||||
if _, ok := r.tagSet[tagSetChassisLocation]; ok && 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
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassis]; ok {
|
||||
setChassisTags(chassis, tags)
|
||||
}
|
||||
|
||||
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 thermal.Fans {
|
||||
tags := make(map[string]string, 20)
|
||||
fields := make(map[string]interface{}, 5)
|
||||
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
|
||||
if _, ok := r.tagSet[tagSetChassisLocation]; ok && 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
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassis]; ok {
|
||||
setChassisTags(chassis, tags)
|
||||
}
|
||||
|
||||
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 if j.CurrentReading != nil {
|
||||
fields["reading_percent"] = j.CurrentReading
|
||||
} else {
|
||||
fields["reading_percent"] = j.Reading
|
||||
}
|
||||
acc.AddFields("redfish_thermal_fans", fields, tags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Redfish) gatherPower(acc telegraf.Accumulator, address string, system *system, chassis *chassis) error {
|
||||
power, err := r.getPower(chassis.Power.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, j := range power.PowerControl {
|
||||
tags := map[string]string{
|
||||
"member_id": j.MemberID,
|
||||
"address": address,
|
||||
"name": j.Name,
|
||||
"source": system.Hostname,
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassisLocation]; ok && 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
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassis]; ok {
|
||||
setChassisTags(chassis, tags)
|
||||
}
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"power_allocated_watts": j.PowerAllocatedWatts,
|
||||
"power_available_watts": j.PowerAvailableWatts,
|
||||
"power_capacity_watts": j.PowerCapacityWatts,
|
||||
"power_consumed_watts": j.PowerConsumedWatts,
|
||||
"power_requested_watts": j.PowerRequestedWatts,
|
||||
"average_consumed_watts": j.PowerMetrics.AverageConsumedWatts,
|
||||
"interval_in_min": j.PowerMetrics.IntervalInMin,
|
||||
"max_consumed_watts": j.PowerMetrics.MaxConsumedWatts,
|
||||
"min_consumed_watts": j.PowerMetrics.MinConsumedWatts,
|
||||
}
|
||||
|
||||
acc.AddFields("redfish_power_powercontrol", fields, tags)
|
||||
}
|
||||
|
||||
for _, j := range power.PowerSupplies {
|
||||
tags := make(map[string]string, 19)
|
||||
tags["member_id"] = j.MemberID
|
||||
tags["address"] = address
|
||||
tags["name"] = j.Name
|
||||
tags["source"] = system.Hostname
|
||||
tags["state"] = j.Status.State
|
||||
tags["health"] = j.Status.Health
|
||||
if _, ok := r.tagSet[tagSetChassisLocation]; ok && 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
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassis]; ok {
|
||||
setChassisTags(chassis, tags)
|
||||
}
|
||||
|
||||
fields := make(map[string]interface{})
|
||||
fields["power_input_watts"] = j.PowerInputWatts
|
||||
fields["power_output_watts"] = j.PowerOutputWatts
|
||||
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 power.Voltages {
|
||||
tags := make(map[string]string, 19)
|
||||
tags["member_id"] = j.MemberID
|
||||
tags["address"] = address
|
||||
tags["name"] = j.Name
|
||||
tags["source"] = system.Hostname
|
||||
tags["state"] = j.Status.State
|
||||
tags["health"] = j.Status.Health
|
||||
if _, ok := r.tagSet[tagSetChassisLocation]; ok && 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
|
||||
}
|
||||
if _, ok := r.tagSet[tagSetChassis]; ok {
|
||||
setChassisTags(chassis, tags)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("redfish", func() telegraf.Input {
|
||||
return &Redfish{
|
||||
// default tag set of chassis.location required for backwards compatibility
|
||||
IncludeTagSets: []string{tagSetChassisLocation},
|
||||
IncludeMetrics: []string{"power", "thermal"},
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue