Skip to content

Commit f237cc1

Browse files
committed
Implement polling-based BMC metrics collection
Closes #813
1 parent e1eb6a0 commit f237cc1

14 files changed

Lines changed: 653 additions & 349 deletions

File tree

api/v1alpha1/bmc_types.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,6 @@ type BMCStatus struct {
206206
// +optional
207207
LastResetTime *metav1.Time `json:"lastResetTime,omitempty"`
208208

209-
// MetricsReportSubscriptionLink is the link to the metrics report subscription of the bmc.
210-
// +optional
211-
MetricsReportSubscriptionLink string `json:"metricsReportSubscriptionLink,omitempty"`
212-
213-
// EventsSubscriptionLink is the link to the events subscription of the bmc.
214-
// +optional
215-
EventsSubscriptionLink string `json:"eventsSubscriptionLink,omitempty"`
216-
217209
// Conditions represents the latest available observations of the BMC's current state.
218210
// +patchStrategy=merge
219211
// +patchMergeKey=type

bmc/bmc.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ type BMC interface {
138138
// CheckBMCPendingComponentUpgrade checks if there are pending/staged firmware upgrades
139139
// for the given component type.
140140
CheckBMCPendingComponentUpgrade(ctx context.Context, componentType ComponentType) (bool, error)
141+
142+
// GetMetricReport retrieves the current metric report from the BMC.
143+
GetMetricReport(ctx context.Context) (MetricsReport, error)
144+
145+
// GetEventLog retrieves recent events from the BMC's System Event Log.
146+
GetEventLog(ctx context.Context) ([]Event, error)
141147
}
142148

143149
type Entity struct {
@@ -297,3 +303,32 @@ type Manager struct {
297303
MACAddress string
298304
OemLinks json.RawMessage
299305
}
306+
307+
// MetricsReport represents a Redfish telemetry metric report.
308+
type MetricsReport struct {
309+
ODataID string `json:"@odata.id,omitempty"`
310+
ODataType string `json:"@odata.type,omitempty"`
311+
ID string `json:"Id,omitempty"`
312+
Name string `json:"Name,omitempty"`
313+
MetricValues []MetricValue `json:"MetricValues,omitempty"`
314+
}
315+
316+
// MetricValue represents a single metric value in a metric report.
317+
type MetricValue struct {
318+
MetricID string `json:"MetricId"`
319+
MetricProperty string `json:"MetricProperty"`
320+
MetricValue string `json:"MetricValue"`
321+
Units string `json:"Units"`
322+
MetricValueKind string `json:"MetricValueKind"`
323+
Timestamp string `json:"Timestamp"`
324+
Oem any `json:"Oem"`
325+
}
326+
327+
// Event represents a Redfish event or alert.
328+
type Event struct {
329+
EventID string `json:"EventId"`
330+
Message string `json:"Message"`
331+
Severity string `json:"Severity"`
332+
EventTimestamp string `json:"EventTimestamp"`
333+
OriginOfCondition string `json:"OriginOfCondition"`
334+
}

bmc/mock/server/data/index.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@
6060
},
6161
"@odata.id": "/redfish/v1/",
6262
"@Redfish.Copyright": "Copyright 2014-2023 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
63-
}
63+
}

bmc/redfish.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,3 +1140,106 @@ func (r *RedfishBaseBMC) DeleteEventSubscription(ctx context.Context, uri string
11401140
}
11411141
return nil
11421142
}
1143+
1144+
// GetMetricReport retrieves the current metric report from the BMC via TelemetryService.
1145+
// If TelemetryService is not available, returns an empty report (fallback to events).
1146+
func (r *RedfishBaseBMC) GetMetricReport(ctx context.Context) (MetricsReport, error) {
1147+
service := r.client.GetService()
1148+
1149+
// Try TelemetryService first (Redfish 2018.3+)
1150+
telemetry, err := service.TelemetryService()
1151+
if err == nil && telemetry != nil {
1152+
// Get metric reports
1153+
reports, err := telemetry.MetricReports()
1154+
if err == nil && len(reports) > 0 {
1155+
// Use the first available report and convert to our format
1156+
report := reports[0]
1157+
metricValues := make([]MetricValue, 0)
1158+
1159+
// Try to extract metric values - schema may vary by vendor
1160+
// The report.MetricValues is a slice of report-specific values
1161+
// We'll create a simple representation
1162+
for i := 0; i < len(report.MetricValues); i++ {
1163+
metricValues = append(metricValues, MetricValue{
1164+
MetricID: fmt.Sprintf("Metric%d", i),
1165+
MetricProperty: report.ODataID,
1166+
MetricValue: fmt.Sprintf("%v", report.MetricValues[i]),
1167+
MetricValueKind: "Gauge",
1168+
Timestamp: time.Now().Format(time.RFC3339),
1169+
})
1170+
}
1171+
1172+
return MetricsReport{
1173+
ODataID: report.ODataID,
1174+
ODataType: report.ODataType,
1175+
ID: report.ID,
1176+
Name: report.Name,
1177+
MetricValues: metricValues,
1178+
}, nil
1179+
}
1180+
}
1181+
1182+
// Fallback: Return empty report - polling will rely on event subscriptions or vendor-specific implementations
1183+
return MetricsReport{
1184+
ID: "EmptyMetrics",
1185+
Name: "No TelemetryService available",
1186+
MetricValues: []MetricValue{},
1187+
}, nil
1188+
}
1189+
1190+
// GetEventLog retrieves recent events from the BMC's System Event Log (SEL).
1191+
// Returns events from the last 10 minutes by default.
1192+
func (r *RedfishBaseBMC) GetEventLog(ctx context.Context) ([]Event, error) {
1193+
service := r.client.GetService()
1194+
systems, err := service.Systems()
1195+
if err != nil {
1196+
return nil, fmt.Errorf("failed to get systems: %w", err)
1197+
}
1198+
if len(systems) == 0 {
1199+
return nil, fmt.Errorf("no systems found")
1200+
}
1201+
1202+
system := systems[0]
1203+
1204+
// Get log services
1205+
logServices, err := system.LogServices()
1206+
if err != nil {
1207+
return nil, fmt.Errorf("failed to get log services: %w", err)
1208+
}
1209+
1210+
events := []Event{}
1211+
1212+
// Query each log service
1213+
for _, logService := range logServices {
1214+
entries, err := logService.Entries()
1215+
if err != nil {
1216+
continue // Skip log services that fail
1217+
}
1218+
1219+
// Filter to recent entries (last 10 minutes)
1220+
cutoff := time.Now().Add(-10 * time.Minute)
1221+
1222+
for _, entry := range entries {
1223+
// Parse timestamp
1224+
var entryTime time.Time
1225+
if entry.Created != "" {
1226+
entryTime, _ = time.Parse(time.RFC3339, entry.Created)
1227+
}
1228+
1229+
// Skip old entries
1230+
if !entryTime.IsZero() && entryTime.Before(cutoff) {
1231+
continue
1232+
}
1233+
1234+
events = append(events, Event{
1235+
EventID: entry.ID,
1236+
Message: entry.Message,
1237+
Severity: string(entry.Severity),
1238+
EventTimestamp: entry.Created,
1239+
OriginOfCondition: entry.ODataID,
1240+
})
1241+
}
1242+
}
1243+
1244+
return events, nil
1245+
}

0 commit comments

Comments
 (0)