feat(backend): add in-memory collect job manager and mock executor

This commit is contained in:
Mikhail Chusavitin
2026-02-04 10:01:51 +03:00
parent aa3c82d9ba
commit d38d0c9d30
7 changed files with 555 additions and 47 deletions

View File

@@ -1,6 +1,7 @@
package server
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
@@ -572,28 +573,12 @@ func (s *Server) handleCollectStart(w http.ResponseWriter, r *http.Request) {
return
}
jobID := generateJobID()
now := time.Now().UTC()
progress := 0
s.collectMu.Lock()
s.collectJobs[jobID] = &CollectJobStatusResponse{
JobID: jobID,
Status: "queued",
Progress: &progress,
Logs: []string{"Job queued"},
UpdatedAt: now,
}
s.collectMu.Unlock()
job := s.jobManager.CreateJob(req)
s.startMockCollectionJob(job.ID, req)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(CollectJobResponse{
JobID: jobID,
Status: "queued",
Message: "Collection job accepted",
CreatedAt: now,
})
_ = json.NewEncoder(w).Encode(job.toJobResponse("Collection job accepted"))
}
func (s *Server) handleCollectStatus(w http.ResponseWriter, r *http.Request) {
@@ -603,17 +588,13 @@ func (s *Server) handleCollectStatus(w http.ResponseWriter, r *http.Request) {
return
}
s.collectMu.RLock()
job, ok := s.collectJobs[jobID]
if !ok || job == nil {
s.collectMu.RUnlock()
job, ok := s.jobManager.GetJob(jobID)
if !ok {
jsonError(w, "Collect job not found", http.StatusNotFound)
return
}
resp := *job
s.collectMu.RUnlock()
jsonResponse(w, resp)
jsonResponse(w, job.toStatusResponse())
}
func (s *Server) handleCollectCancel(w http.ResponseWriter, r *http.Request) {
@@ -623,25 +604,76 @@ func (s *Server) handleCollectCancel(w http.ResponseWriter, r *http.Request) {
return
}
s.collectMu.Lock()
job, ok := s.collectJobs[jobID]
if !ok || job == nil {
s.collectMu.Unlock()
job, ok := s.jobManager.CancelJob(jobID)
if !ok {
jsonError(w, "Collect job not found", http.StatusNotFound)
return
}
now := time.Now().UTC()
progress := 0
job.Status = "canceled"
job.Progress = &progress
job.Logs = append(job.Logs, "Job canceled by user")
job.Error = ""
job.UpdatedAt = now
resp := *job
s.collectMu.Unlock()
jsonResponse(w, job.toStatusResponse())
}
jsonResponse(w, resp)
func (s *Server) startMockCollectionJob(jobID string, req CollectRequest) {
ctx, cancel := context.WithCancel(context.Background())
if attached := s.jobManager.AttachJobCancel(jobID, cancel); !attached {
cancel()
return
}
go func() {
steps := []struct {
delay time.Duration
status string
progress int
log string
}{
{delay: 250 * time.Millisecond, status: CollectStatusRunning, progress: 20, log: "Подключение..."},
{delay: 250 * time.Millisecond, status: CollectStatusRunning, progress: 50, log: "Сбор инвентаря..."},
{delay: 250 * time.Millisecond, status: CollectStatusRunning, progress: 80, log: "Нормализация..."},
}
for _, step := range steps {
if !waitWithCancel(ctx, step.delay) {
return
}
if job, ok := s.jobManager.GetJob(jobID); !ok || isTerminalCollectStatus(job.Status) {
return
}
s.jobManager.UpdateJobStatus(jobID, step.status, step.progress, "")
s.jobManager.AppendJobLog(jobID, step.log)
}
if !waitWithCancel(ctx, 250*time.Millisecond) {
return
}
if job, ok := s.jobManager.GetJob(jobID); !ok || isTerminalCollectStatus(job.Status) {
return
}
if strings.Contains(strings.ToLower(req.Host), "fail") {
s.jobManager.UpdateJobStatus(jobID, CollectStatusFailed, 100, "Mock: не удалось завершить сбор")
s.jobManager.AppendJobLog(jobID, "Сбор завершен с ошибкой")
return
}
s.jobManager.UpdateJobStatus(jobID, CollectStatusSuccess, 100, "")
s.jobManager.AppendJobLog(jobID, "Сбор завершен")
}()
}
func waitWithCancel(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
}
}
func validateCollectRequest(req CollectRequest) error {