Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
127
internal/fleet/crucible.go
Normal file
127
internal/fleet/crucible.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BatchJob tracks a crucible batch dispatch.
|
||||
type BatchJob struct {
|
||||
ID string `json:"id"`
|
||||
Command string `json:"command"`
|
||||
HostIDs []string `json:"host_ids"`
|
||||
Results []BatchResult `json:"results"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// BatchResult is one host outcome in a batch job.
|
||||
type BatchResult struct {
|
||||
HostID string `json:"host_id"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
CommandID string `json:"command_id,omitempty"`
|
||||
}
|
||||
|
||||
// CrucibleStore holds in-memory batch job history.
|
||||
type CrucibleStore struct {
|
||||
mu sync.RWMutex
|
||||
jobs map[string]*BatchJob
|
||||
max int
|
||||
}
|
||||
|
||||
func NewCrucibleStore(maxHistory int) *CrucibleStore {
|
||||
if maxHistory <= 0 {
|
||||
maxHistory = 100
|
||||
}
|
||||
return &CrucibleStore{
|
||||
jobs: make(map[string]*BatchJob),
|
||||
max: maxHistory,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) Create(command string, hostIDs []string) *BatchJob {
|
||||
job := &BatchJob{
|
||||
ID: uuid.NewString(),
|
||||
Command: command,
|
||||
HostIDs: append([]string(nil), hostIDs...),
|
||||
Results: make([]BatchResult, 0, len(hostIDs)),
|
||||
Status: "running",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.jobs[job.ID] = job
|
||||
c.trimLocked()
|
||||
c.mu.Unlock()
|
||||
return job
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) Get(id string) (*BatchJob, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
job, ok := c.jobs[id]
|
||||
return job, ok
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) AddResult(jobID string, result BatchResult) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
job, ok := c.jobs[jobID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
job.Results = append(job.Results, result)
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) Complete(jobID, status string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if job, ok := c.jobs[jobID]; ok {
|
||||
job.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) History(limit int) []*BatchJob {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
jobs := make([]*BatchJob, 0, len(c.jobs))
|
||||
for _, j := range c.jobs {
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
// Sort by created_at desc (simple bubble for small sets)
|
||||
for i := 0; i < len(jobs); i++ {
|
||||
for j := i + 1; j < len(jobs); j++ {
|
||||
if jobs[j].CreatedAt.After(jobs[i].CreatedAt) {
|
||||
jobs[i], jobs[j] = jobs[j], jobs[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(jobs) > limit {
|
||||
jobs = jobs[:limit]
|
||||
}
|
||||
return jobs
|
||||
}
|
||||
|
||||
func (c *CrucibleStore) trimLocked() {
|
||||
if len(c.jobs) <= c.max {
|
||||
return
|
||||
}
|
||||
oldest := ""
|
||||
var oldestTime time.Time
|
||||
for id, j := range c.jobs {
|
||||
if oldest == "" || j.CreatedAt.Before(oldestTime) {
|
||||
oldest = id
|
||||
oldestTime = j.CreatedAt
|
||||
}
|
||||
}
|
||||
if oldest != "" {
|
||||
delete(c.jobs, oldest)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user