diff --git a/.golangci.yml b/.golangci.yml index 6382dad..d807212 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,10 @@ run: tests: true +issues: + exclude-dirs: + - pkg/oem/ + linters: enable-all: true disable: diff --git a/pkg/drivers/redfish/metrics/metrics.go b/pkg/drivers/redfish/metrics/metrics.go index f0031be..b807d7b 100644 --- a/pkg/drivers/redfish/metrics/metrics.go +++ b/pkg/drivers/redfish/metrics/metrics.go @@ -38,6 +38,8 @@ func (m *Metrics) Collect() error { } for _, system := range systems { + m.vendorSpecifics(system) + m.WithRedfishHealthMetric(convertHealthStatus(system.Status.Health, false), map[string]string{ "system_id": system.ID, }) diff --git a/pkg/drivers/redfish/metrics/vendor_specifics.go b/pkg/drivers/redfish/metrics/vendor_specifics.go new file mode 100644 index 0000000..b5319cf --- /dev/null +++ b/pkg/drivers/redfish/metrics/vendor_specifics.go @@ -0,0 +1,52 @@ +package metrics + +import ( + "log" + + "github.com/g-portal/redfish_exporter/pkg/oem/hpe" + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +// vendorSpecifics is a helper function to collect vendor specific metrics. +func (m *Metrics) vendorSpecifics(system *redfish.ComputerSystem) { + switch system.Manufacturer { + case hpe.Manufacturer: + //nolint: nestif + if hpeStorage, err := hpe.GetSmartStorage(system); err == nil { + if arrayControllers, err := hpeStorage.ArrayControllers(); err == nil { + for _, controller := range arrayControllers { + m.WithRedfishStorageHealthMetric(convertHealthStatus(hpeStorage.Status.Health, + hpeStorage.Status.State == common.EnabledState), map[string]string{ + "system_id": system.ID, + "storage_id": controller.ID, + }) + + if drives, err := controller.PhysicalDrives(); err == nil { + for _, drive := range drives { + m.WithRedfishDriveHealthMetric(convertHealthStatus(drive.Status.Health, + drive.Status.State == common.EnabledState), map[string]string{ + "system_id": system.ID, + "storage_id": controller.ID, + "drive_id": drive.ID, + }) + m.WithRedfishDriveCapacityMetric(float64(drive.CapacityMiB), map[string]string{ + "system_id": system.ID, + "storage_id": controller.ID, + "drive_id": drive.ID, + }) + } + } else { + log.Printf("error getting drives: %s", err) + } + } + } else { + log.Printf("error getting array controllers: %s", err) + } + } else { + log.Printf("error getting SmartStorage: %s", err) + } + default: + // no vendor specific metrics to collect + } +} diff --git a/pkg/oem/hpe/array_controller.go b/pkg/oem/hpe/array_controller.go new file mode 100644 index 0000000..e1bb702 --- /dev/null +++ b/pkg/oem/hpe/array_controller.go @@ -0,0 +1,116 @@ +package hpe + +import ( + "encoding/json" + "fmt" + "github.com/stmcginnis/gofish/common" + "io" +) + +type ArrayController struct { + common.Entity + + OdataContext string `json:"@odata.context"` + OdataID string `json:"@odata.id"` + OdataType string `json:"@odata.type"` + AdapterType string `json:"AdapterType"` + BackupPowerSourceStatus string `json:"BackupPowerSourceStatus"` + CurrentOperatingMode string `json:"CurrentOperatingMode"` + Description string `json:"Description"` + EncryptionCryptoOfficerPasswordSet bool `json:"EncryptionCryptoOfficerPasswordSet"` + EncryptionEnabled bool `json:"EncryptionEnabled"` + EncryptionFwLocked bool `json:"EncryptionFwLocked"` + EncryptionHasLockedVolumesMissingBootPassword bool `json:"EncryptionHasLockedVolumesMissingBootPassword"` + EncryptionMixedVolumesEnabled bool `json:"EncryptionMixedVolumesEnabled"` + EncryptionStandaloneModeEnabled bool `json:"EncryptionStandaloneModeEnabled"` + ExternalPortCount int `json:"ExternalPortCount"` + FirmwareVersion struct { + Current struct { + VersionString string `json:"VersionString"` + } `json:"Current"` + } `json:"FirmwareVersion"` + HardwareRevision string `json:"HardwareRevision"` + ID string `json:"Id"` + InternalPortCount int `json:"InternalPortCount"` + Location string `json:"Location"` + LocationFormat string `json:"LocationFormat"` + Model string `json:"Model"` + Name string `json:"Name"` + SerialNumber string `json:"SerialNumber"` + Status common.Status `json:"Status"` + Type string `json:"Type"` + Links struct { + LogicalDrives struct { + Href string `json:"href"` + } `json:"LogicalDrives"` + PhysicalDrives struct { + Href string `json:"href"` + } `json:"PhysicalDrives"` + StorageEnclosures struct { + Href string `json:"href"` + } `json:"StorageEnclosures"` + UnconfiguredDrives struct { + Href string `json:"href"` + } `json:"UnconfiguredDrives"` + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} + +func (s *ArrayController) PhysicalDrives() ([]*Drive, error) { + result := make([]*Drive, 0) + + drives, err := s.diskDrivesList() + if err != nil { + return nil, fmt.Errorf("error getting ArrayControllers: %w", err) + } + + for _, member := range drives.Members { + data, err := s.GetClient().Get(member.OdataID) + if err != nil { + return nil, fmt.Errorf("error getting ArrayController data: %w", err) + } + + var drive *Drive + jsonPayload, err := io.ReadAll(data.Body) + if err != nil { + return nil, fmt.Errorf("error reading SmartStorage data: %w", err) + } + + err = json.Unmarshal(jsonPayload, &drive) + if err != nil { + return nil, fmt.Errorf("error unmarshalling SmartStorage data: %w", err) + } + + drive.SetClient(s.GetClient()) + + result = append(result, drive) + } + + return result, nil + +} + +func (s *ArrayController) diskDrivesList() (*DiskDrives, error) { + var result *DiskDrives + + data, err := s.GetClient().Get(s.Links.PhysicalDrives.Href) + if err != nil { + return nil, fmt.Errorf("error getting physical drives: %w", err) + } + + jsonPayload, err := io.ReadAll(data.Body) + if err != nil { + return nil, fmt.Errorf("error reading SmartStorage data: %w", err) + } + + err = json.Unmarshal(jsonPayload, &result) + if err != nil { + return nil, fmt.Errorf("error unmarshalling SmartStorage data: %w", err) + } + + result.SetClient(s.GetClient()) + + return result, nil +} diff --git a/pkg/oem/hpe/array_controllers.go b/pkg/oem/hpe/array_controllers.go new file mode 100644 index 0000000..f5299a5 --- /dev/null +++ b/pkg/oem/hpe/array_controllers.go @@ -0,0 +1,89 @@ +package hpe + +import ( + "encoding/json" + "fmt" + "github.com/stmcginnis/gofish/common" + "io" +) + +type ArrayControllers struct { + common.Entity + + OdataContext string `json:"@odata.context"` + OdataID string `json:"@odata.id"` + OdataType string `json:"@odata.type"` + Description string `json:"Description"` + MemberType string `json:"MemberType"` + Members []struct { + OdataID string `json:"@odata.id"` + } `json:"Members"` + MembersOdataCount int `json:"Members@odata.count"` + Name string `json:"Name"` + Total int `json:"Total"` + Type string `json:"Type"` + Links struct { + Member []struct { + Href string `json:"href"` + } `json:"Member"` + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} + +func (s *SmartStorage) ArrayControllers() ([]*ArrayController, error) { + result := make([]*ArrayController, 0) + + controllers, err := s.arrayControllerList() + if err != nil { + return nil, fmt.Errorf("error getting ArrayControllers: %w", err) + } + + for _, member := range controllers.Members { + data, err := s.GetClient().Get(member.OdataID) + if err != nil { + return nil, fmt.Errorf("error getting ArrayController data: %w", err) + } + + var controller *ArrayController + jsonPayload, err := io.ReadAll(data.Body) + if err != nil { + return nil, fmt.Errorf("error reading SmartStorage data: %w", err) + } + + err = json.Unmarshal(jsonPayload, &controller) + if err != nil { + return nil, fmt.Errorf("error unmarshalling SmartStorage data: %w", err) + } + + controller.SetClient(s.GetClient()) + + result = append(result, controller) + } + + return result, nil +} + +func (s *SmartStorage) arrayControllerList() (*ArrayControllers, error) { + var result *ArrayControllers + + data, err := s.GetClient().Get(s.Links.ArrayControllers.Href) + if err != nil { + return nil, fmt.Errorf("error getting ArrayControllers: %w", err) + } + + jsonPayload, err := io.ReadAll(data.Body) + if err != nil { + return nil, fmt.Errorf("error reading SmartStorage data: %w", err) + } + + err = json.Unmarshal(jsonPayload, &result) + if err != nil { + return nil, fmt.Errorf("error unmarshalling SmartStorage data: %w", err) + } + + result.SetClient(s.GetClient()) + + return result, nil +} diff --git a/pkg/oem/hpe/drive.go b/pkg/oem/hpe/drive.go new file mode 100644 index 0000000..158a044 --- /dev/null +++ b/pkg/oem/hpe/drive.go @@ -0,0 +1,46 @@ +package hpe + +import "github.com/stmcginnis/gofish/common" + +type Drive struct { + common.Entity + + OdataContext string `json:"@odata.context"` + OdataID string `json:"@odata.id"` + OdataType string `json:"@odata.type"` + BlockSizeBytes int `json:"BlockSizeBytes"` + CapacityGB int `json:"CapacityGB"` + CapacityLogicalBlocks int64 `json:"CapacityLogicalBlocks"` + CapacityMiB int `json:"CapacityMiB"` + CarrierApplicationVersion string `json:"CarrierApplicationVersion"` + CarrierAuthenticationStatus string `json:"CarrierAuthenticationStatus"` + CurrentTemperatureCelsius int `json:"CurrentTemperatureCelsius"` + Description string `json:"Description"` + DiskDriveStatusReasons []string `json:"DiskDriveStatusReasons"` + EncryptedDrive bool `json:"EncryptedDrive"` + FirmwareVersion struct { + Current struct { + VersionString string `json:"VersionString"` + } `json:"Current"` + } `json:"FirmwareVersion"` + ID string `json:"Id"` + InterfaceSpeedMbps int `json:"InterfaceSpeedMbps"` + InterfaceType string `json:"InterfaceType"` + Location string `json:"Location"` + LocationFormat string `json:"LocationFormat"` + MaximumTemperatureCelsius int `json:"MaximumTemperatureCelsius"` + MediaType string `json:"MediaType"` + Model string `json:"Model"` + Name string `json:"Name"` + PowerOnHours interface{} `json:"PowerOnHours"` + RotationalSpeedRpm int `json:"RotationalSpeedRpm"` + SSDEnduranceUtilizationPercentage interface{} `json:"SSDEnduranceUtilizationPercentage"` + SerialNumber string `json:"SerialNumber"` + Status common.Status `json:"Status"` + Type string `json:"Type"` + Links struct { + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} diff --git a/pkg/oem/hpe/drives.go b/pkg/oem/hpe/drives.go new file mode 100644 index 0000000..09b0838 --- /dev/null +++ b/pkg/oem/hpe/drives.go @@ -0,0 +1,28 @@ +package hpe + +import "github.com/stmcginnis/gofish/common" + +type DiskDrives struct { + common.Entity + + OdataContext string `json:"@odata.context"` + OdataID string `json:"@odata.id"` + OdataType string `json:"@odata.type"` + Description string `json:"Description"` + MemberType string `json:"MemberType"` + Members []struct { + OdataID string `json:"@odata.id"` + } `json:"Members"` + MembersOdataCount int `json:"Members@odata.count"` + Name string `json:"Name"` + Total int `json:"Total"` + Type string `json:"Type"` + Links struct { + Member []struct { + Href string `json:"href"` + } `json:"Member"` + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} diff --git a/pkg/oem/hpe/generic.go b/pkg/oem/hpe/generic.go new file mode 100644 index 0000000..15d217f --- /dev/null +++ b/pkg/oem/hpe/generic.go @@ -0,0 +1,3 @@ +package hpe + +const Manufacturer = "HPE" diff --git a/pkg/oem/hpe/smart_storage.go b/pkg/oem/hpe/smart_storage.go new file mode 100644 index 0000000..4ccd19a --- /dev/null +++ b/pkg/oem/hpe/smart_storage.go @@ -0,0 +1,57 @@ +package hpe + +import ( + "encoding/json" + "fmt" + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" + "io" + "strings" +) + +type SmartStorage struct { + common.Entity + + OdataContext string `json:"@odata.context"` + OdataID string `json:"@odata.id"` + OdataType string `json:"@odata.type"` + Description string `json:"Description"` + ID string `json:"Id"` + Name string `json:"Name"` + Status common.Status `json:"Status"` + Type string `json:"Type"` + Links struct { + ArrayControllers struct { + Href string `json:"href"` + } `json:"ArrayControllers"` + HostBusAdapters struct { + Href string `json:"href"` + } `json:"HostBusAdapters"` + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} + +func GetSmartStorage(system *redfish.ComputerSystem) (*SmartStorage, error) { + var result *SmartStorage + url := fmt.Sprintf("%s/SmartStorage", strings.TrimSuffix(system.ODataID, "/")) + data, err := system.GetClient().Get(url) + if err != nil { + return nil, fmt.Errorf("error getting SmartStorage data: %w", err) + } + + jsonPayload, err := io.ReadAll(data.Body) + if err != nil { + return nil, fmt.Errorf("error reading SmartStorage data: %w", err) + } + + err = json.Unmarshal(jsonPayload, &result) + if err != nil { + return nil, fmt.Errorf("error unmarshalling SmartStorage data: %w", err) + } + + result.SetClient(system.GetClient()) + + return result, nil +}