Implement the full architectural plan: unified ingest.Service entry point for archive and Redfish payloads, modular redfishprofile package with composable profiles (generic, ami-family, msi, supermicro, dell, hgx-topology), score-based profile matching with fallback expansion mode, and profile-driven acquisition/analysis plans. Vendor-specific logic moved out of common executors and into profile hooks. GPU chassis lookup strategies and known storage recovery collections (IntelVROC/HA-RAID/MRVL) now live in ResolvedAnalysisPlan, populated by profiles at analysis time. Replay helpers read from the plan; no hardcoded path lists remain in generic code. Also splits redfish_replay.go into domain modules (gpu, storage, inventory, fru, profiles) and adds full fixture/matcher/directive test coverage including Dell, AMI, unknown-vendor fallback, and deterministic ordering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package server
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.mchus.pro/mchus/logpile/internal/collector"
|
||
"git.mchus.pro/mchus/logpile/internal/models"
|
||
)
|
||
|
||
type mockConnector struct {
|
||
protocol string
|
||
}
|
||
|
||
func (c *mockConnector) Protocol() string {
|
||
return c.protocol
|
||
}
|
||
|
||
func (c *mockConnector) Probe(ctx context.Context, req collector.Request) (*collector.ProbeResult, error) {
|
||
if strings.Contains(strings.ToLower(req.Host), "fail") {
|
||
return nil, context.DeadlineExceeded
|
||
}
|
||
return &collector.ProbeResult{
|
||
Reachable: true,
|
||
Protocol: c.protocol,
|
||
HostPowerState: map[bool]string{true: "On", false: "Off"}[!strings.Contains(strings.ToLower(req.Host), "off")],
|
||
HostPoweredOn: !strings.Contains(strings.ToLower(req.Host), "off"),
|
||
PowerControlAvailable: true,
|
||
SystemPath: "/redfish/v1/Systems/1",
|
||
}, nil
|
||
}
|
||
|
||
func (c *mockConnector) Collect(ctx context.Context, req collector.Request, emit collector.ProgressFn) (*models.AnalysisResult, error) {
|
||
steps := []collector.Progress{
|
||
{
|
||
Status: CollectStatusRunning,
|
||
Progress: 10,
|
||
Message: "Подбор модулей Redfish...",
|
||
ActiveModules: []collector.ModuleActivation{
|
||
{Name: "supermicro", Score: 80},
|
||
{Name: "generic", Score: 10},
|
||
},
|
||
ModuleScores: []collector.ModuleScore{
|
||
{Name: "supermicro", Score: 80, Active: true, Priority: 20},
|
||
{Name: "generic", Score: 10, Active: true, Priority: 100},
|
||
{Name: "hgx-topology", Score: 0, Active: false, Priority: 30},
|
||
},
|
||
DebugInfo: &collector.CollectDebugInfo{
|
||
AdaptiveThrottled: false,
|
||
SnapshotWorkers: 6,
|
||
PrefetchWorkers: 4,
|
||
PhaseTelemetry: []collector.PhaseTelemetry{
|
||
{Phase: "discovery", Requests: 6, Errors: 0, ErrorRate: 0, AvgMS: 120, P95MS: 180},
|
||
},
|
||
},
|
||
},
|
||
{Status: CollectStatusRunning, Progress: 20, Message: "Подключение..."},
|
||
{Status: CollectStatusRunning, Progress: 50, Message: "Сбор инвентаря..."},
|
||
{Status: CollectStatusRunning, Progress: 80, Message: "Нормализация..."},
|
||
}
|
||
for _, step := range steps {
|
||
if !collectorSleep(ctx, 100*time.Millisecond) {
|
||
return nil, ctx.Err()
|
||
}
|
||
if emit != nil {
|
||
emit(step)
|
||
}
|
||
}
|
||
|
||
if strings.Contains(strings.ToLower(req.Host), "fail") {
|
||
return nil, context.DeadlineExceeded
|
||
}
|
||
|
||
return &models.AnalysisResult{
|
||
Events: make([]models.Event, 0),
|
||
FRU: make([]models.FRUInfo, 0),
|
||
Sensors: make([]models.SensorReading, 0),
|
||
Hardware: &models.HardwareConfig{},
|
||
}, nil
|
||
}
|
||
|
||
func testCollectorRegistry() *collector.Registry {
|
||
r := collector.NewRegistry()
|
||
r.Register(&mockConnector{protocol: "redfish"})
|
||
r.Register(&mockConnector{protocol: "ipmi"})
|
||
return r
|
||
}
|
||
|
||
func collectorSleep(ctx context.Context, d time.Duration) bool {
|
||
timer := time.NewTimer(d)
|
||
defer timer.Stop()
|
||
select {
|
||
case <-ctx.Done():
|
||
return false
|
||
case <-timer.C:
|
||
return true
|
||
}
|
||
}
|