Expand Redfish best-effort snapshot crawling

This commit is contained in:
Mikhail Chusavitin
2026-02-25 12:24:06 +03:00
parent 693b7346ab
commit b1dde592ae
2 changed files with 129 additions and 0 deletions

2
.gitignore vendored
View File

@@ -64,6 +64,8 @@ go.work.sum
dist/
# Release artifacts
release/
releases/
releases/**/SHA256SUMS.txt
releases/**/*.tar.gz
releases/**/*.zip

View File

@@ -678,6 +678,16 @@ func (c *RedfishConnector) collectRawRedfishTree(ctx context.Context, client *ht
c.debugSnapshotf("snapshot nvme bay probe hit path=%s", bayPath)
}
}
// Some BMCs under-report collection Members for sensors/PSU subresources but still serve
// direct numeric child endpoints. Probe common collections to maximize raw snapshot fidelity.
for path := range out {
for childPath, doc := range c.probeDirectRedfishCollectionChildren(ctx, client, req, baseURL, path) {
if _, exists := out[childPath]; exists {
continue
}
out[childPath] = doc
}
}
if emit != nil {
emit(Progress{
@@ -738,6 +748,106 @@ func directDiskBayCandidates(drivesCollectionPath string) []string {
return out
}
func (c *RedfishConnector) probeDirectRedfishCollectionChildren(ctx context.Context, client *http.Client, req Request, baseURL, collectionPath string) map[string]map[string]interface{} {
normalized := normalizeRedfishPath(collectionPath)
maxItems, startIndex, missBudget := directNumericProbePlan(normalized)
if maxItems <= 0 {
return nil
}
out := make(map[string]map[string]interface{})
consecutiveMisses := 0
for i := startIndex; i <= maxItems; i++ {
path := fmt.Sprintf("%s/%d", normalized, i)
doc, err := c.getJSON(ctx, client, req, baseURL, path)
if err != nil {
consecutiveMisses++
if consecutiveMisses >= missBudget {
break
}
continue
}
consecutiveMisses = 0
if !looksLikeRedfishResource(doc) {
continue
}
out[normalizeRedfishPath(path)] = doc
}
return out
}
func directNumericProbePlan(collectionPath string) (maxItems, startIndex, missBudget int) {
switch {
case strings.HasSuffix(collectionPath, "/Systems"):
return 32, 1, 8
case strings.HasSuffix(collectionPath, "/Chassis"):
return 64, 1, 12
case strings.HasSuffix(collectionPath, "/Managers"):
return 16, 1, 6
case strings.HasSuffix(collectionPath, "/Processors"):
return 32, 1, 12
case strings.HasSuffix(collectionPath, "/Memory"):
return 512, 1, 48
case strings.HasSuffix(collectionPath, "/Storage"):
return 128, 1, 24
case strings.HasSuffix(collectionPath, "/Drives"):
return 256, 0, 24
case strings.HasSuffix(collectionPath, "/Volumes"):
return 128, 1, 16
case strings.HasSuffix(collectionPath, "/PCIeDevices"):
return 256, 1, 24
case strings.HasSuffix(collectionPath, "/PCIeFunctions"):
return 512, 1, 32
case strings.HasSuffix(collectionPath, "/NetworkAdapters"):
return 128, 1, 20
case strings.HasSuffix(collectionPath, "/NetworkPorts"):
return 256, 1, 24
case strings.HasSuffix(collectionPath, "/Ports"):
return 256, 1, 24
case strings.HasSuffix(collectionPath, "/EthernetInterfaces"):
return 256, 1, 24
case strings.HasSuffix(collectionPath, "/Certificates"):
return 256, 1, 24
case strings.HasSuffix(collectionPath, "/Accounts"):
return 128, 1, 16
case strings.HasSuffix(collectionPath, "/LogServices"):
return 32, 1, 8
case strings.HasSuffix(collectionPath, "/Sensors"):
return 512, 1, 48
case strings.HasSuffix(collectionPath, "/Temperatures"):
return 256, 1, 32
case strings.HasSuffix(collectionPath, "/Fans"):
return 256, 1, 32
case strings.HasSuffix(collectionPath, "/Voltages"):
return 256, 1, 32
case strings.HasSuffix(collectionPath, "/PowerSupplies"):
return 64, 1, 16
default:
return 0, 0, 0
}
}
func looksLikeRedfishResource(doc map[string]interface{}) bool {
if len(doc) == 0 {
return false
}
if asString(doc["@odata.id"]) != "" {
return true
}
if asString(doc["Id"]) != "" || asString(doc["Name"]) != "" {
return true
}
if _, ok := doc["Status"]; ok {
return true
}
if _, ok := doc["Reading"]; ok {
return true
}
if _, ok := doc["ReadingCelsius"]; ok {
return true
}
return false
}
func redfishLinkRefs(doc map[string]interface{}, topKey, nestedKey string) []string {
top, ok := doc[topKey].(map[string]interface{})
if !ok {
@@ -1856,27 +1966,44 @@ func redfishSnapshotPrioritySeeds(systemPaths, chassisPaths, managerPaths []stri
add(joinPath(p, "/SecureBoot"))
add(joinPath(p, "/Processors"))
add(joinPath(p, "/Memory"))
add(joinPath(p, "/EthernetInterfaces"))
add(joinPath(p, "/NetworkInterfaces"))
add(joinPath(p, "/BootOptions"))
add(joinPath(p, "/Certificates"))
add(joinPath(p, "/PCIeDevices"))
add(joinPath(p, "/PCIeFunctions"))
add(joinPath(p, "/Accelerators"))
add(joinPath(p, "/Storage"))
add(joinPath(p, "/SimpleStorage"))
add(joinPath(p, "/Storage/IntelVROC"))
add(joinPath(p, "/Storage/IntelVROC/Drives"))
add(joinPath(p, "/Storage/IntelVROC/Volumes"))
}
for _, p := range chassisPaths {
add(p)
add(joinPath(p, "/Sensors"))
add(joinPath(p, "/Thermal"))
add(joinPath(p, "/EnvironmentMetrics"))
add(joinPath(p, "/PCIeDevices"))
add(joinPath(p, "/PCIeSlots"))
add(joinPath(p, "/NetworkAdapters"))
add(joinPath(p, "/Drives"))
add(joinPath(p, "/Temperatures"))
add(joinPath(p, "/Fans"))
add(joinPath(p, "/Voltages"))
add(joinPath(p, "/PowerSubsystem"))
add(joinPath(p, "/PowerSubsystem/PowerSupplies"))
add(joinPath(p, "/PowerSubsystem/Voltages"))
add(joinPath(p, "/ThermalSubsystem"))
add(joinPath(p, "/ThermalSubsystem/Fans"))
add(joinPath(p, "/ThermalSubsystem/Temperatures"))
add(joinPath(p, "/Power"))
}
for _, p := range managerPaths {
add(p)
add(joinPath(p, "/EthernetInterfaces"))
add(joinPath(p, "/NetworkProtocol/HTTPS/Certificates"))
add(joinPath(p, "/LogServices"))
add(joinPath(p, "/NetworkProtocol"))
}
return out