Add raw export reanalyze flow for Redfish snapshots

This commit is contained in:
Mikhail Chusavitin
2026-02-24 17:23:26 +03:00
parent 5d9e9d73de
commit 810c4b5ff9
7 changed files with 783 additions and 23 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
@@ -70,15 +71,33 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
)
if looksLikeJSONSnapshot(header.Filename, payload) {
snapshotResult, snapshotErr := parseUploadedSnapshot(payload)
if snapshotErr != nil {
jsonError(w, "Failed to parse snapshot: "+snapshotErr.Error(), http.StatusBadRequest)
if rawPkg, ok, err := parseRawExportPackage(payload); err != nil {
jsonError(w, "Failed to parse raw export package: "+err.Error(), http.StatusBadRequest)
return
}
result = snapshotResult
vendor = strings.TrimSpace(snapshotResult.Protocol)
if vendor == "" {
vendor = "snapshot"
} else if ok {
replayed, replayVendor, replayErr := s.reanalyzeRawExportPackage(rawPkg)
if replayErr != nil {
jsonError(w, "Failed to reanalyze raw export package: "+replayErr.Error(), http.StatusBadRequest)
return
}
result = replayed
vendor = replayVendor
if strings.TrimSpace(vendor) == "" {
vendor = "snapshot"
}
s.SetRawExport(rawPkg)
} else {
snapshotResult, snapshotErr := parseUploadedSnapshot(payload)
if snapshotErr != nil {
jsonError(w, "Failed to parse snapshot: "+snapshotErr.Error(), http.StatusBadRequest)
return
}
result = snapshotResult
vendor = strings.TrimSpace(snapshotResult.Protocol)
if vendor == "" {
vendor = "snapshot"
}
s.SetRawExport(newRawExportFromUploadedFile(header.Filename, header.Header.Get("Content-Type"), payload, result))
}
} else {
// Parse archive
@@ -90,6 +109,7 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
result = p.Result()
applyArchiveSourceMetadata(result)
vendor = p.DetectedVendor()
s.SetRawExport(newRawExportFromUploadedFile(header.Filename, header.Header.Get("Content-Type"), payload, result))
}
s.SetResult(result)
@@ -108,6 +128,77 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
})
}
func (s *Server) reanalyzeRawExportPackage(pkg *RawExportPackage) (*models.AnalysisResult, string, error) {
if pkg == nil {
return nil, "", fmt.Errorf("empty package")
}
switch pkg.Source.Kind {
case "file_bytes":
if strings.TrimSpace(pkg.Source.Encoding) != "base64" {
return nil, "", fmt.Errorf("unsupported file_bytes encoding: %s", pkg.Source.Encoding)
}
data, err := base64.StdEncoding.DecodeString(pkg.Source.Data)
if err != nil {
return nil, "", fmt.Errorf("decode source.data: %w", err)
}
return s.parseUploadedPayload(pkg.Source.Filename, data)
case "live_redfish":
if !strings.EqualFold(strings.TrimSpace(pkg.Source.Protocol), "redfish") {
return nil, "", fmt.Errorf("unsupported live protocol: %s", pkg.Source.Protocol)
}
result, err := collector.ReplayRedfishFromRawPayloads(pkg.Source.RawPayloads, nil)
if err != nil {
return nil, "", err
}
if result != nil {
if strings.TrimSpace(result.Protocol) == "" {
result.Protocol = "redfish"
}
if strings.TrimSpace(result.SourceType) == "" {
result.SourceType = models.SourceTypeAPI
}
if strings.TrimSpace(result.TargetHost) == "" {
result.TargetHost = strings.TrimSpace(pkg.Source.TargetHost)
}
if result.CollectedAt.IsZero() {
result.CollectedAt = time.Now().UTC()
}
if strings.TrimSpace(result.Filename) == "" {
target := result.TargetHost
if target == "" {
target = "snapshot"
}
result.Filename = "redfish://" + target
}
}
return result, "redfish", nil
default:
return nil, "", fmt.Errorf("unsupported raw export source kind: %s", pkg.Source.Kind)
}
}
func (s *Server) parseUploadedPayload(filename string, payload []byte) (*models.AnalysisResult, string, error) {
if looksLikeJSONSnapshot(filename, payload) {
snapshotResult, err := parseUploadedSnapshot(payload)
if err != nil {
return nil, "", err
}
vendor := strings.TrimSpace(snapshotResult.Protocol)
if vendor == "" {
vendor = "snapshot"
}
return snapshotResult, vendor, nil
}
p := parser.NewBMCParser()
if err := p.ParseFromReader(bytes.NewReader(payload), filename); err != nil {
return nil, "", err
}
result := p.Result()
applyArchiveSourceMetadata(result)
return result, p.DetectedVendor(), nil
}
func (s *Server) handleGetParsers(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, map[string]interface{}{
"parsers": parser.ListParsersInfo(),
@@ -667,8 +758,19 @@ func (s *Server) handleExportJSON(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", exportFilename(result, "json")))
if rawPkg := s.GetRawExport(); rawPkg != nil {
rawPkg.ExportedAt = time.Now().UTC()
rawPkg.Analysis = nil
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
if err := encoder.Encode(rawPkg); err != nil {
return
}
return
}
exp := exporter.New(result)
exp.ExportJSON(w)
_ = exp.ExportJSON(w)
}
func (s *Server) handleExportReanimator(w http.ResponseWriter, r *http.Request) {
@@ -702,6 +804,7 @@ func (s *Server) handleExportReanimator(w http.ResponseWriter, r *http.Request)
func (s *Server) handleClear(w http.ResponseWriter, r *http.Request) {
s.SetResult(nil)
s.SetDetectedVendor("")
s.SetRawExport(nil)
jsonResponse(w, map[string]string{
"status": "ok",
"message": "Data cleared",
@@ -827,6 +930,9 @@ func (s *Server) startCollectionJob(jobID string, req CollectRequest) {
s.jobManager.AppendJobLog(jobID, "Сбор завершен")
s.SetResult(result)
s.SetDetectedVendor(req.Protocol)
if job, ok := s.jobManager.GetJob(jobID); ok {
s.SetRawExport(newRawExportFromLiveCollect(result, req, job.Logs))
}
}()
}