Skip to content

Commit 1f56404

Browse files
author
Sunil Thaha
committed
refactor(redfish): enhance power reading with PowerSubsystem API support
This refactoring improves reliability by supporting both new and legacy Redfish implementations. This commit adds support for PowerSubsystem API and uses deprecated/legacy Power API as fallback for old BMC Ref: https://dmtf.org/sites/default/files/standards/documents/DSP2046_2025.2.html#power-173-deprecated Key Changes: - Add PowerSubsystem API support as primary power reading method - Fallback to deprecated Power API when needed - Move client lifecycle management from Service to PowerReader - Strategy pattern for determining optimal power reading approach - redfish Init() fails if it can't create a gofish client that connects to BMC Test changes: - Reorganize test utilities into dedicated testutil package - Add fallback scenario test - JSON fixtures for PowerSubsystem and PowerSupplies responses - Mock server handles configurable response scenarios - Add validation helpers for power data verification Signed-off-by: Sunil Thaha <[email protected]>
1 parent 905238a commit 1f56404

40 files changed

+2090
-2205
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ require (
1414
github.com/prometheus/client_model v0.6.1
1515
github.com/prometheus/exporter-toolkit v0.14.0
1616
github.com/prometheus/procfs v0.15.1
17-
github.com/stmcginnis/gofish v0.15.0
17+
github.com/stmcginnis/gofish v0.20.0
1818
github.com/stretchr/testify v1.10.0
1919
go.uber.org/zap v1.26.0
2020
golang.org/x/sync v0.12.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
135135
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
136136
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
137137
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
138-
github.com/stmcginnis/gofish v0.15.0 h1:8TG41+lvJk/0Nf8CIIYErxbMlQUy80W0JFRZP3Ld82A=
139-
github.com/stmcginnis/gofish v0.15.0/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI=
138+
github.com/stmcginnis/gofish v0.20.0 h1:hH2V2Qe898F2wWT1loApnkDUrXXiLKqbSlMaH3Y1n08=
139+
github.com/stmcginnis/gofish v0.20.0/go.mod h1:PzF5i8ecRG9A2ol8XT64npKUunyraJ+7t0kYMpQAtqU=
140140
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
141141
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
142142
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=

hack/gen-metric-docs/main.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,27 @@ func (m *MockRedfishService) Power() (*redfish.PowerReading, error) {
6666
ID: "System.Embedded.1",
6767
Readings: []redfish.Reading{
6868
{
69-
ControlID: "PC1",
70-
Name: "System Power Control",
71-
Power: 245.0 * device.Watt, // Dell 245W scenario
69+
SourceID: "PC1",
70+
SourceName: "System Power Control",
71+
SourceType: redfish.PowerControlSource,
72+
Power: 245.0 * device.Watt, // Dell 245W scenario
7273
},
7374
},
7475
},
7576
{
7677
ID: "Enclosure.Internal.0-1",
7778
Readings: []redfish.Reading{
7879
{
79-
ControlID: "PC1",
80-
Name: "Enclosure Power Control",
81-
Power: 189.5 * device.Watt, // HPE 189W scenario
80+
SourceID: "PC1",
81+
SourceName: "Enclosure Power Control",
82+
SourceType: redfish.PowerControlSource,
83+
Power: 189.5 * device.Watt, // HPE 189W scenario
8284
},
8385
{
84-
ControlID: "PC2",
85-
Name: "CPU Sub-system Power",
86-
Power: 167.8 * device.Watt, // Lenovo 167W scenario
86+
SourceID: "PC2",
87+
SourceName: "CPU Sub-system Power",
88+
SourceType: redfish.PowerControlSource,
89+
Power: 167.8 * device.Watt, // Lenovo 167W scenario
8790
},
8891
},
8992
},

internal/exporter/prometheus/collector/platform_collector.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ func NewRedfishCollector(redfish RedfishDataProvider, logger *slog.Logger) *Plat
5353
bmcID: redfish.BMCID(),
5454
wattsDesc: prometheus.NewDesc(
5555
prometheus.BuildFQName(keplerNS, platformSubsystem, "watts"),
56-
"Current platform power consumption in watts from BMC PowerControl entries",
57-
[]string{"source", "node_name", "bmc_id", "chassis_id", "power_control_id", "power_control_name"},
56+
"Current platform power in watts from BMC (PowerSubsystem or deprecated Power API)",
57+
[]string{"source", "node_name", "bmc_id", "chassis_id", "source_id", "source_name", "source_type"},
5858
nil,
5959
),
6060
}
@@ -80,13 +80,13 @@ func (c *PlatformCollector) Collect(ch chan<- prometheus.Metric) {
8080
return
8181
}
8282

83-
// Emit metrics for each PowerControl reading in each chassis
83+
// Emit metrics for each power reading in each chassis (PowerSupply or PowerControl)
8484
for _, chassis := range powerReading.Chassis {
8585
for _, reading := range chassis.Readings {
86-
// Label order must match the descriptor: source, node_name, bmc_id, chassis_id, power_control_id, power_control_name
87-
labels := []string{"redfish", c.nodeName, c.bmcID, chassis.ID, reading.ControlID, reading.Name}
86+
// Label order must match the descriptor: source, node_name, bmc_id, chassis_id, source_id, source_name, source_type
87+
labels := []string{"redfish", c.nodeName, c.bmcID, chassis.ID, reading.SourceID, reading.SourceName, string(reading.SourceType)}
8888

89-
// Emit current power consumption metric (power-only approach)
89+
// Emit current power metric (output from PowerSupply or consumption from PowerControl)
9090
ch <- prometheus.MustNewConstMetric(
9191
c.wattsDesc,
9292
prometheus.GaugeValue,
@@ -98,8 +98,9 @@ func (c *PlatformCollector) Collect(ch chan<- prometheus.Metric) {
9898
"node.name", c.nodeName,
9999
"bmc.id", c.bmcID,
100100
"chassis.id", chassis.ID,
101-
"power_control.id", reading.ControlID,
102-
"power_control.name", reading.Name,
101+
"source.id", reading.SourceID,
102+
"source.name", reading.SourceName,
103+
"source.type", reading.SourceType,
103104
"power.watts", reading.Power,
104105
"age", time.Since(powerReading.Timestamp).Seconds())
105106
}

internal/exporter/prometheus/collector/platform_collector_test.go

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -169,24 +169,24 @@ func TestPlatformCollector_Collect_Success(t *testing.T) {
169169
ID: "System.Embedded.1",
170170
Readings: []redfish.Reading{
171171
{
172-
ControlID: "PC1",
173-
Name: "Server Power Control",
174-
Power: 450.5 * device.Watt,
172+
SourceID: "PC1",
173+
SourceName: "Server Power Control",
174+
Power: 450.5 * device.Watt,
175175
},
176176
{
177-
ControlID: "PC2",
178-
Name: "CPU Sub-system Power",
179-
Power: 85.2 * device.Watt,
177+
SourceID: "PC2",
178+
SourceName: "CPU Sub-system Power",
179+
Power: 85.2 * device.Watt,
180180
},
181181
},
182182
},
183183
{
184184
ID: "Enclosure.Internal.0-1",
185185
Readings: []redfish.Reading{
186186
{
187-
ControlID: "PC1",
188-
Name: "Enclosure Power Control",
189-
Power: 125.3 * device.Watt,
187+
SourceID: "PC1",
188+
SourceName: "Enclosure Power Control",
189+
Power: 125.3 * device.Watt,
190190
},
191191
},
192192
},
@@ -220,34 +220,34 @@ func TestPlatformCollector_Collect_Success(t *testing.T) {
220220

221221
// Verify first chassis, first PowerControl metric
222222
chassis1PC1Value := findMetricValue(t, platformMetric, map[string]string{
223-
"source": "redfish",
224-
"node_name": "worker-1",
225-
"bmc_id": "bmc-1",
226-
"chassis_id": "System.Embedded.1",
227-
"power_control_id": "PC1",
228-
"power_control_name": "Server Power Control",
223+
"source": "redfish",
224+
"node_name": "worker-1",
225+
"bmc_id": "bmc-1",
226+
"chassis_id": "System.Embedded.1",
227+
"source_id": "PC1",
228+
"source_name": "Server Power Control",
229229
})
230230
assert.Equal(t, 450.5, chassis1PC1Value)
231231

232232
// Verify first chassis, second PowerControl metric
233233
chassis1PC2Value := findMetricValue(t, platformMetric, map[string]string{
234-
"source": "redfish",
235-
"node_name": "worker-1",
236-
"bmc_id": "bmc-1",
237-
"chassis_id": "System.Embedded.1",
238-
"power_control_id": "PC2",
239-
"power_control_name": "CPU Sub-system Power",
234+
"source": "redfish",
235+
"node_name": "worker-1",
236+
"bmc_id": "bmc-1",
237+
"chassis_id": "System.Embedded.1",
238+
"source_id": "PC2",
239+
"source_name": "CPU Sub-system Power",
240240
})
241241
assert.Equal(t, 85.2, chassis1PC2Value)
242242

243243
// Verify second chassis metric
244244
chassis2Value := findMetricValue(t, platformMetric, map[string]string{
245-
"source": "redfish",
246-
"node_name": "worker-1",
247-
"bmc_id": "bmc-1",
248-
"chassis_id": "Enclosure.Internal.0-1",
249-
"power_control_id": "PC1",
250-
"power_control_name": "Enclosure Power Control",
245+
"source": "redfish",
246+
"node_name": "worker-1",
247+
"bmc_id": "bmc-1",
248+
"chassis_id": "Enclosure.Internal.0-1",
249+
"source_id": "PC1",
250+
"source_name": "Enclosure Power Control",
251251
})
252252
assert.Equal(t, 125.3, chassis2Value)
253253
}
@@ -338,9 +338,9 @@ func TestPlatformCollector_Collect_SingleChassis(t *testing.T) {
338338
ID: "System.Embedded.1",
339339
Readings: []redfish.Reading{
340340
{
341-
ControlID: "PC1",
342-
Name: "Server Power Control",
343-
Power: 300.0 * device.Watt,
341+
SourceID: "PC1",
342+
SourceName: "Server Power Control",
343+
Power: 300.0 * device.Watt,
344344
},
345345
},
346346
},
@@ -389,9 +389,9 @@ func TestPlatformCollector_Collect_ParallelCollection(t *testing.T) {
389389
ID: "System.Embedded.1",
390390
Readings: []redfish.Reading{
391391
{
392-
ControlID: "PC1",
393-
Name: "Server Power Control",
394-
Power: 200.0 * device.Watt,
392+
SourceID: "PC1",
393+
SourceName: "Server Power Control",
394+
Power: 200.0 * device.Watt,
395395
},
396396
},
397397
},
@@ -479,9 +479,9 @@ func TestPlatformCollector_Collect_MetricLabelsValidation(t *testing.T) {
479479
ID: tc.chassisID,
480480
Readings: []redfish.Reading{
481481
{
482-
ControlID: "PC1",
483-
Name: "Server Power Control",
484-
Power: tc.power,
482+
SourceID: "PC1",
483+
SourceName: "Server Power Control",
484+
Power: tc.power,
485485
},
486486
},
487487
},

0 commit comments

Comments
 (0)