From d0f1e3c4713e026a8d055efc5c049bff1af3d9d8 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:42:16 +0200 Subject: [PATCH 1/9] devices/sensors: define structs for sensor data --- devices/sensors.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 devices/sensors.go diff --git a/devices/sensors.go b/devices/sensors.go new file mode 100644 index 00000000..e4de96bd --- /dev/null +++ b/devices/sensors.go @@ -0,0 +1,27 @@ +package devices + +type PowerSensor struct { + Name string + ID string + InputWatts float32 + OutputWatts float32 + LastOutputWatts float32 +} + +type TemperatureSensor struct { + ID string + ReadingCelsius float32 + PhysicalContext string +} + +type FanSensor struct { + ID string + Reading float32 + PhysicalContext string +} + +type ChassisHealth struct { + ID string + State string + Health string +} From ba6a2cde9a579a12cf11bdf346387400280ebd2a Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:43:10 +0200 Subject: [PATCH 2/9] bmc/sensors: define methods and interface getters --- bmc/sensors.go | 210 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 bmc/sensors.go diff --git a/bmc/sensors.go b/bmc/sensors.go new file mode 100644 index 00000000..ce7e6367 --- /dev/null +++ b/bmc/sensors.go @@ -0,0 +1,210 @@ +package bmc + +import ( + "context" + "errors" + "fmt" + + "github.com/bmc-toolbox/bmclib/devices" + "github.com/hashicorp/go-multierror" +) + +// PowerSensorsGetter retrieves power consumption values +type PowerSensorsGetter interface { + PowerSensors(ctx context.Context) ([]*devices.PowerSensor, error) +} + +// TemperatureSensorGetter retrieves temperature values +type TemperatureSensorsGetter interface { + TemperatureSensors(ctx context.Context) ([]*devices.TemperatureSensor, error) +} + +// FanSensorsGetter retrieves fan speed data +type FanSensorsGetter interface { + FanSensors(ctx context.Context) ([]*devices.FanSensor, error) +} + +// ChassisHealthGetter retrieves chassis health data +type ChassisHealthGetter interface { + ChassisHealth(ctx context.Context) ([]*devices.ChassisHealth, error) +} + +// PowerGetter interface implementation identifier and passthrough methods + +// GetPowerSensors returns power draw data, trying all interface implementations passed in +func GetPowerSensors(ctx context.Context, p []PowerSensorsGetter) (power []*devices.PowerSensor, err error) { +Loop: + for _, elem := range p { + if elem == nil { + continue + } + select { + case <-ctx.Done(): + err = multierror.Append(err, ctx.Err()) + break Loop + default: + power, vErr := elem.PowerSensors(ctx) + if vErr != nil { + err = multierror.Append(err, vErr) + continue + } + return power, nil + } + } + + return power, multierror.Append(err, errors.New("failed to get power sensor data")) +} + +// GetPowerSensorsFromInterfaces identfies implementations of the PowerSensorGetter interface and acts as a pass through method +func GetPowerSensorsFromInterfaces(ctx context.Context, generic []interface{}) (power []*devices.PowerSensor, err error) { + powerDrawGetter := make([]PowerSensorsGetter, 0) + for _, elem := range generic { + switch p := elem.(type) { + case PowerSensorsGetter: + powerDrawGetter = append(powerDrawGetter, p) + default: + e := fmt.Sprintf("not a PowerSensorGetter implementation: %T", p) + err = multierror.Append(err, errors.New(e)) + } + } + if len(powerDrawGetter) == 0 { + return power, multierror.Append(err, errors.New("no PowerSensorGetter implementations found")) + } + + return GetPowerSensors(ctx, powerDrawGetter) +} + +// TemperatureSensorsGetter interface identifier and passthrough methods + +// GetTemperatureSensors returns temperature data, trying all interface implementations passed in +func GetTemperatureSensors(ctx context.Context, p []TemperatureSensorsGetter) (temps []*devices.TemperatureSensor, err error) { +Loop: + for _, elem := range p { + if elem == nil { + continue + } + select { + case <-ctx.Done(): + err = multierror.Append(err, ctx.Err()) + break Loop + default: + temps, vErr := elem.TemperatureSensors(ctx) + if vErr != nil { + err = multierror.Append(err, vErr) + continue + } + return temps, nil + } + } + + return temps, multierror.Append(err, errors.New("failed to get temperature sensor data")) +} + +// GetTemperatureSensorsFromInterfaces identfies implementations of the TemperatureGetter interface and acts as a pass through method +func GetTemperatureSensorsFromInterfaces(ctx context.Context, generic []interface{}) (temps []*devices.TemperatureSensor, err error) { + gets := make([]TemperatureSensorsGetter, 0) + for _, elem := range generic { + switch p := elem.(type) { + case TemperatureSensorsGetter: + gets = append(gets, p) + default: + e := fmt.Sprintf("not a TemperatureGetter implementation: %T", p) + err = multierror.Append(err, errors.New(e)) + } + } + if len(gets) == 0 { + return temps, multierror.Append(err, errors.New("no TemperatureGetter implementations found")) + } + + return GetTemperatureSensors(ctx, gets) +} + +// FanSensorsGetter interface identifier and passthrough methods + +// GetFanSensors returns fan speed data, trying all interface implementations passed in +func GetFanSensors(ctx context.Context, p []FanSensorsGetter) (fanSensors []*devices.FanSensor, err error) { +Loop: + for _, elem := range p { + if elem == nil { + continue + } + select { + case <-ctx.Done(): + err = multierror.Append(err, ctx.Err()) + break Loop + default: + fanSensors, vErr := elem.FanSensors(ctx) + if vErr != nil { + err = multierror.Append(err, vErr) + continue + } + return fanSensors, nil + } + } + + return fanSensors, multierror.Append(err, errors.New("failed to get fan sensor data")) +} + +// GetFanSensorsFromInterfaces identfies implementations of the FanSpeedGetter interface and acts as a pass through method +func GetFanSensorsFromInterfaces(ctx context.Context, generic []interface{}) (fanSensors []*devices.FanSensor, err error) { + gets := make([]FanSensorsGetter, 0) + for _, elem := range generic { + switch p := elem.(type) { + case FanSensorsGetter: + gets = append(gets, p) + default: + e := fmt.Sprintf("not a FanSensorGetter implementation: %T", p) + err = multierror.Append(err, errors.New(e)) + } + } + if len(gets) == 0 { + return fanSensors, multierror.Append(err, errors.New("no FanSensorGetter implementations found")) + } + + return GetFanSensors(ctx, gets) +} + +// ChassisHealthGetter interface identifier and passthrough methods + +// GetChassisHealth gets all chassis health data, trying all interface implementations passed in +func GetChassisHealth(ctx context.Context, p []ChassisHealthGetter) (health []*devices.ChassisHealth, err error) { +Loop: + for _, elem := range p { + if elem == nil { + continue + } + select { + case <-ctx.Done(): + err = multierror.Append(err, ctx.Err()) + break Loop + default: + health, vErr := elem.ChassisHealth(ctx) + if vErr != nil { + err = multierror.Append(err, vErr) + continue + } + return health, nil + } + } + + return health, multierror.Append(err, errors.New("failed to get chassis health")) +} + +// GetChassisHealthFromInterfaces identfies implementations of the ChassisHealthGetter interface and acts as a pass through method +func GetChassisHealthFromInterfaces(ctx context.Context, generic []interface{}) (health []*devices.ChassisHealth, err error) { + gets := make([]ChassisHealthGetter, 0) + for _, elem := range generic { + switch p := elem.(type) { + case ChassisHealthGetter: + gets = append(gets, p) + default: + e := fmt.Sprintf("not a ChassisHealthGetter implementation: %T", p) + err = multierror.Append(err, errors.New(e)) + } + } + if len(gets) == 0 { + return health, multierror.Append(err, errors.New("no FanSensorGetter implementations found")) + } + + return GetChassisHealth(ctx, gets) +} From 2b5a0ee5ae0f5a9cc509a07df69a23a4c62f76f6 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:44:12 +0200 Subject: [PATCH 3/9] providers/redfish: declare sensor features supported, consts --- providers/providers.go | 8 ++++++++ providers/redfish/redfish.go | 30 ++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/providers/providers.go b/providers/providers.go index 887fa01a..e668ac15 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -29,4 +29,12 @@ const ( FeatureBmcFirmwareUpdate registrar.Feature = "bmcfirwareupdate" // FeatureBiosFirmwareUpdate means an implementation that updates the BIOS firmware FeatureBiosFirmwareUpdate registrar.Feature = "biosfirwareupdate" + // FeaturePowersensors indicates an implementation that returns Power sensor information + FeaturePowersensors registrar.Feature = "powersensors" + // FeatureTemperatureSensors indicates an implementation that returns Temperature sensor information + FeatureTemperatureSensors registrar.Feature = "temperaturesensors" + // FeatureFanSensors indicates an implementation that returns Fan sensor information + FeatureFanSensors registrar.Feature = "fansensors" + // FeatureChassisHealth indicates an implementation that returns ChassisHealth information + FeatureChassisHealth registrar.Feature = "chassishealth" ) diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index a1b453eb..2bf4cd1d 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -26,17 +26,35 @@ var ( Features = registrar.Features{ providers.FeaturePowerSet, providers.FeaturePowerState, + providers.FeaturePowersensors, + providers.FeatureTemperatureSensors, + providers.FeatureFanSensors, + providers.FeatureChassisHealth, } + + // Supported Chassis Odata IDs + chassisOdataIdURLs = []string{ + // Dells + "/redfish/v1/Chassis/System.Embedded.1", + // Supermicro + "/redfish/v1/Chassis/1", + // MegaRAC + "/redfish/v1/Chassis/Self", + } + + ErrRedfishChassisOdataID = errors.New("no compatible chassis Odata IDs identified") + ErrRedfishServiceNil = errors.New("redfish connection returned a nil redfish Service object") ) // Conn details for redfish client type Conn struct { - Host string - Port string - User string - Pass string - conn *gofish.APIClient - Log logr.Logger + Host string + Port string + User string + Pass string + Vendor string + conn *gofish.APIClient + Log logr.Logger } // Open a connection to a BMC via redfish From 71c8e9c49d14139ee8daac19cb57374f304d7351 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:44:53 +0200 Subject: [PATCH 4/9] redfish/sensors: Add sensors interface implementations --- providers/redfish/sensors.go | 206 +++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 providers/redfish/sensors.go diff --git a/providers/redfish/sensors.go b/providers/redfish/sensors.go new file mode 100644 index 00000000..c1fe5680 --- /dev/null +++ b/providers/redfish/sensors.go @@ -0,0 +1,206 @@ +package redfish + +import ( + "context" + "fmt" + "strconv" + + "github.com/bmc-toolbox/bmclib/devices" +) + +func chassisCompatible(chassisOdataID string) bool { + for _, url := range chassisOdataIdURLs { + if url == chassisOdataID { + return true + } + } + + return false +} + +func (c *Conn) PowerSensors(ctx context.Context) ([]*devices.PowerSensor, error) { + + data := make([]*devices.PowerSensor, 0) + + service := c.conn.Service + if service == nil { + return nil, ErrRedfishServiceNil + } + + chassis, err := service.Chassis() + if err != nil { + return nil, err + } + + compatible := 0 + for _, ch := range chassis { + fmt.Println(ch.ODataID) + if !chassisCompatible(ch.ODataID) { + continue + } + + compatible++ + p, err := ch.Power() + if err != nil { + return nil, err + } + + for idx, supply := range p.PowerSupplies { + id := p.ID + + if p.ID == "" { + id = strconv.Itoa(idx) + } + + c := &devices.PowerSensor{ + Name: supply.Name, + ID: id, + InputWatts: supply.PowerInputWatts, + OutputWatts: supply.PowerOutputWatts, + LastOutputWatts: supply.LastPowerOutputWatts, + } + data = append(data, c) + } + + } + + if compatible == 0 { + return nil, ErrRedfishChassisOdataID + } + + return data, nil +} + +func (c *Conn) TemperatureSensors(ctx context.Context) ([]*devices.TemperatureSensor, error) { + + data := make([]*devices.TemperatureSensor, 0) + + service := c.conn.Service + if service == nil { + return nil, ErrRedfishServiceNil + } + + chassis, err := service.Chassis() + if err != nil { + return nil, err + } + + compatible := 0 + for _, ch := range chassis { + if !chassisCompatible(ch.ODataID) { + continue + } + + compatible++ + p, err := ch.Thermal() + if err != nil { + return nil, err + } + + for idx, s := range p.Temperatures { + id := s.ID + + if s.ID == "" { + id = strconv.Itoa(idx) + } + + t := &devices.TemperatureSensor{ + ID: id, + PhysicalContext: s.PhysicalContext, + ReadingCelsius: s.ReadingCelsius, + } + + data = append(data, t) + } + } + + if compatible == 0 { + return nil, ErrRedfishChassisOdataID + } + + return data, nil +} + +func (c *Conn) FanSensors(ctx context.Context) ([]*devices.FanSensor, error) { + data := make([]*devices.FanSensor, 0) + + service := c.conn.Service + if service == nil { + return nil, ErrRedfishServiceNil + } + + chassis, err := service.Chassis() + if err != nil { + return nil, err + } + + compatible := 0 + for _, ch := range chassis { + if !chassisCompatible(ch.ODataID) { + continue + } + + compatible++ + p, err := ch.Thermal() + if err != nil { + return nil, err + } + + for idx, s := range p.Fans { + id := s.ID + + if s.ID == "" { + id = strconv.Itoa(idx) + } + + t := &devices.FanSensor{ + ID: id, + PhysicalContext: s.PhysicalContext, + Reading: s.Reading, + } + + data = append(data, t) + } + } + + if compatible == 0 { + return nil, ErrRedfishChassisOdataID + } + + return data, nil +} + +func (c *Conn) ChassisHealth(ctx context.Context) ([]*devices.ChassisHealth, error) { + + data := make([]*devices.ChassisHealth, 0) + + service := c.conn.Service + if service == nil { + return nil, ErrRedfishServiceNil + } + + chassis, err := service.Chassis() + if err != nil { + return nil, err + } + + compatible := 0 + for _, ch := range chassis { + if !chassisCompatible(ch.ODataID) { + continue + } + + compatible++ + h := &devices.ChassisHealth{ + State: string(ch.Status.State), + Health: string(ch.Status.Health), + } + data = append(data, h) + } + + if compatible == 0 { + return nil, ErrRedfishChassisOdataID + } + + return data, nil +} From 443515c7d149e1866d944117739172c8c780509b Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:45:36 +0200 Subject: [PATCH 5/9] client: define sensor passthrough methods --- client.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client.go b/client.go index 477a7e55..1f76ae53 100644 --- a/client.go +++ b/client.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/bmc-toolbox/bmclib/bmc" + "github.com/bmc-toolbox/bmclib/devices" "github.com/bmc-toolbox/bmclib/providers/asrockrack" "github.com/bmc-toolbox/bmclib/providers/dell/idrac9" "github.com/bmc-toolbox/bmclib/providers/goipmi" @@ -221,3 +222,23 @@ func (c *Client) GetBIOSVersion(ctx context.Context) (version string, err error) func (c *Client) UpdateBIOSFirmware(ctx context.Context, fileReader io.Reader, fileSize int64) (err error) { return bmc.UpdateBIOSFirmwareFromInterfaces(ctx, fileReader, fileSize, c.Registry.GetDriverInterfaces()) } + +// GetPowerSensors pass through library function +func (c *Client) GetPowerSensors(ctx context.Context) (sensors []*devices.PowerSensor, err error) { + return bmc.GetPowerSensorsFromInterfaces(ctx, c.Registry.GetDriverInterfaces()) +} + +// GetTemperatureSensors pass through library function +func (c *Client) GetTemperatureSensors(ctx context.Context) (sensors []*devices.TemperatureSensor, err error) { + return bmc.GetTemperatureSensorsFromInterfaces(ctx, c.Registry.GetDriverInterfaces()) +} + +// GetFanSensors pass through library function +func (c *Client) GetFanSensors(ctx context.Context) (sensors []*devices.FanSensor, err error) { + return bmc.GetFanSensorsFromInterfaces(ctx, c.Registry.GetDriverInterfaces()) +} + +// GetChassisHealth pass through library function +func (c *Client) GetChassisHealth(ctx context.Context) (health []*devices.ChassisHealth, err error) { + return bmc.GetChassisHealthFromInterfaces(ctx, c.Registry.GetDriverInterfaces()) +} From c136613e80d2994eb0abef0ffe2ee7c02b5b5f8d Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 12:46:31 +0200 Subject: [PATCH 6/9] examples/sensors: add example for power draw, temperature, fan speeds and chassis health --- examples/v1/sensors/main.go | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/v1/sensors/main.go diff --git a/examples/v1/sensors/main.go b/examples/v1/sensors/main.go new file mode 100644 index 00000000..bb955e70 --- /dev/null +++ b/examples/v1/sensors/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/bmc-toolbox/bmclib" + "github.com/bombsimon/logrusr" + "github.com/sirupsen/logrus" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + host := "" + port := "" + user := "" + pass := "" + + l := logrus.New() + l.Level = logrus.DebugLevel + logger := logrusr.NewLogger(l) + + var err error + + cl := bmclib.NewClient(host, port, user, pass, bmclib.WithLogger(logger)) + cl.Registry.Drivers = cl.Registry.Using("redfish") + err = cl.Open(ctx) + if err != nil { + log.Fatal(err, "bmc login failed") + } + + p, err := cl.GetPowerSensors(ctx) + if err != nil { + fmt.Println(err, "unable to retrieve power sensor data") + } + + fmt.Printf("%+v\n", p) + + t, err := cl.GetTemperatureSensors(ctx) + if err != nil { + fmt.Println(err, "unable to retrieve temperature sensor data") + } + + fmt.Printf("%+v\n", t) + + f, err := cl.GetFanSensors(ctx) + if err != nil { + fmt.Println(err, "unable to retrieve fan sensor data") + } + + fmt.Printf("%+v\n", f) + + c, err := cl.GetChassisHealth(ctx) + if err != nil { + fmt.Println(err, "unable to retrieve chassis health data") + } + + fmt.Printf("%+v\n", c) + +} From 1bc9af009c3c21b8bf9d4335a0152b4e19f4127a Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 20:35:26 +0200 Subject: [PATCH 7/9] devices/sensors: include Name field --- devices/sensors.go | 5 ++++- providers/redfish/sensors.go | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/devices/sensors.go b/devices/sensors.go index e4de96bd..d23d17d0 100644 --- a/devices/sensors.go +++ b/devices/sensors.go @@ -1,8 +1,8 @@ package devices type PowerSensor struct { - Name string ID string + Name string InputWatts float32 OutputWatts float32 LastOutputWatts float32 @@ -10,18 +10,21 @@ type PowerSensor struct { type TemperatureSensor struct { ID string + Name string ReadingCelsius float32 PhysicalContext string } type FanSensor struct { ID string + Name string Reading float32 PhysicalContext string } type ChassisHealth struct { ID string + Name string State string Health string } diff --git a/providers/redfish/sensors.go b/providers/redfish/sensors.go index c1fe5680..f26cc0d0 100644 --- a/providers/redfish/sensors.go +++ b/providers/redfish/sensors.go @@ -106,6 +106,7 @@ func (c *Conn) TemperatureSensors(ctx context.Context) ([]*devices.TemperatureSe t := &devices.TemperatureSensor{ ID: id, + Name: s.Name, PhysicalContext: s.PhysicalContext, ReadingCelsius: s.ReadingCelsius, } @@ -155,6 +156,7 @@ func (c *Conn) FanSensors(ctx context.Context) ([]*devices.FanSensor, error) { t := &devices.FanSensor{ ID: id, + Name: s.Name, PhysicalContext: s.PhysicalContext, Reading: s.Reading, } @@ -192,6 +194,8 @@ func (c *Conn) ChassisHealth(ctx context.Context) ([]*devices.ChassisHealth, err compatible++ h := &devices.ChassisHealth{ + ID: ch.ID, + Name: ch.Name, State: string(ch.Status.State), Health: string(ch.Status.Health), } From 000f06fe47e8eab002556fad1807ba145f4c29ad Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Jul 2021 20:36:12 +0200 Subject: [PATCH 8/9] providers/redfish: return ErrLoginFailed on login failure --- bmc/connection.go | 4 ++++ providers/redfish/redfish.go | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bmc/connection.go b/bmc/connection.go index b1bf9489..a038b186 100644 --- a/bmc/connection.go +++ b/bmc/connection.go @@ -8,6 +8,10 @@ import ( "github.com/hashicorp/go-multierror" ) +var ( + ErrOpenConnection = errors.New("error opening connection") +) + // Opener interface for opening a connection to a BMC type Opener interface { Open(ctx context.Context) error diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index 2bf4cd1d..a4500c13 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -6,7 +6,9 @@ import ( "strings" "time" + bmcErrs "github.com/bmc-toolbox/bmclib/errors" "github.com/bmc-toolbox/bmclib/providers" + "github.com/go-logr/logr" "github.com/jacobweinstock/registrar" "github.com/pkg/errors" @@ -68,7 +70,7 @@ func (c *Conn) Open(ctx context.Context) (err error) { c.conn, err = gofish.ConnectContext(ctx, config) if err != nil { - return err + return errors.Wrap(bmcErrs.ErrLoginFailed, err.Error()) } return nil } From 3c3eec6ee4a3da968c1868910b2945088c0880fd Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 27 Jul 2021 11:28:57 +0200 Subject: [PATCH 9/9] providers/redfish: fix features typo --- providers/providers.go | 2 +- providers/redfish/redfish.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/providers.go b/providers/providers.go index e668ac15..46864146 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -30,7 +30,7 @@ const ( // FeatureBiosFirmwareUpdate means an implementation that updates the BIOS firmware FeatureBiosFirmwareUpdate registrar.Feature = "biosfirwareupdate" // FeaturePowersensors indicates an implementation that returns Power sensor information - FeaturePowersensors registrar.Feature = "powersensors" + FeaturePowerSensors registrar.Feature = "powersensors" // FeatureTemperatureSensors indicates an implementation that returns Temperature sensor information FeatureTemperatureSensors registrar.Feature = "temperaturesensors" // FeatureFanSensors indicates an implementation that returns Fan sensor information diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index a4500c13..7e29aafa 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -28,7 +28,7 @@ var ( Features = registrar.Features{ providers.FeaturePowerSet, providers.FeaturePowerState, - providers.FeaturePowersensors, + providers.FeaturePowerSensors, providers.FeatureTemperatureSensors, providers.FeatureFanSensors, providers.FeatureChassisHealth,