Add pipeline run coordinator
This commit is contained in:
131
internal/app/run_coordinator.go
Normal file
131
internal/app/run_coordinator.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PipelineRunID string
|
||||
|
||||
type PipelineRunStatus string
|
||||
|
||||
const (
|
||||
PipelineRunRunning PipelineRunStatus = "running"
|
||||
PipelineRunSucceeded PipelineRunStatus = "succeeded"
|
||||
PipelineRunFailed PipelineRunStatus = "failed"
|
||||
)
|
||||
|
||||
type PipelineRunRecord struct {
|
||||
ID PipelineRunID `json:"id"`
|
||||
PipelineID string `json:"pipeline_id"`
|
||||
Status PipelineRunStatus `json:"status"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
FinishedAt *time.Time `json:"finished_at,omitempty"`
|
||||
Report RunReport `json:"report,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type DuplicatePipelineRunError struct {
|
||||
PipelineID string
|
||||
RunID PipelineRunID
|
||||
}
|
||||
|
||||
func (err DuplicatePipelineRunError) Error() string {
|
||||
if err.RunID == "" {
|
||||
return fmt.Sprintf("pipeline %q already has an active run", err.PipelineID)
|
||||
}
|
||||
return fmt.Sprintf("pipeline %q already has active run %s", err.PipelineID, err.RunID)
|
||||
}
|
||||
|
||||
func IsDuplicatePipelineRun(err error) bool {
|
||||
var duplicate DuplicatePipelineRunError
|
||||
return errors.As(err, &duplicate)
|
||||
}
|
||||
|
||||
type PipelineRunCoordinator struct {
|
||||
ctx context.Context
|
||||
run pipelineRunFunc
|
||||
now func() time.Time
|
||||
mu sync.Mutex
|
||||
nextID uint64
|
||||
active map[string]PipelineRunRecord
|
||||
}
|
||||
|
||||
type pipelineRunFunc func(context.Context, RunPipelineOptions) (RunReport, error)
|
||||
|
||||
func NewPipelineRunCoordinator(ctx context.Context) *PipelineRunCoordinator {
|
||||
return newPipelineRunCoordinator(ctx, RunPipeline)
|
||||
}
|
||||
|
||||
func newPipelineRunCoordinator(ctx context.Context, run pipelineRunFunc) *PipelineRunCoordinator {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return &PipelineRunCoordinator{
|
||||
ctx: ctx,
|
||||
run: run,
|
||||
now: time.Now,
|
||||
active: map[string]PipelineRunRecord{},
|
||||
}
|
||||
}
|
||||
|
||||
func (coordinator *PipelineRunCoordinator) RunPipeline(ctx context.Context, options RunPipelineOptions) (PipelineRunRecord, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return PipelineRunRecord{}, err
|
||||
}
|
||||
record, err := coordinator.admit(options.PipelineID)
|
||||
if err != nil {
|
||||
return PipelineRunRecord{}, err
|
||||
}
|
||||
defer coordinator.clear(options.PipelineID)
|
||||
|
||||
report, runErr := coordinator.run(coordinator.ctx, options)
|
||||
record.Report = report
|
||||
finishedAt := coordinator.now().UTC()
|
||||
record.FinishedAt = &finishedAt
|
||||
if runErr != nil {
|
||||
record.Status = PipelineRunFailed
|
||||
record.Error = runErr.Error()
|
||||
return record, runErr
|
||||
}
|
||||
record.Status = PipelineRunSucceeded
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (coordinator *PipelineRunCoordinator) admit(pipelineID string) (PipelineRunRecord, error) {
|
||||
coordinator.mu.Lock()
|
||||
defer coordinator.mu.Unlock()
|
||||
if active, ok := coordinator.active[pipelineID]; ok {
|
||||
return PipelineRunRecord{}, DuplicatePipelineRunError{
|
||||
PipelineID: pipelineID,
|
||||
RunID: active.ID,
|
||||
}
|
||||
}
|
||||
coordinator.nextID++
|
||||
record := PipelineRunRecord{
|
||||
ID: PipelineRunID(fmt.Sprintf("run-%016d", coordinator.nextID)),
|
||||
PipelineID: pipelineID,
|
||||
Status: PipelineRunRunning,
|
||||
StartedAt: coordinator.now().UTC(),
|
||||
}
|
||||
coordinator.active[pipelineID] = record
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (coordinator *PipelineRunCoordinator) clear(pipelineID string) {
|
||||
coordinator.mu.Lock()
|
||||
defer coordinator.mu.Unlock()
|
||||
delete(coordinator.active, pipelineID)
|
||||
}
|
||||
|
||||
func (coordinator *PipelineRunCoordinator) activeCount() int {
|
||||
coordinator.mu.Lock()
|
||||
defer coordinator.mu.Unlock()
|
||||
return len(coordinator.active)
|
||||
}
|
||||
Reference in New Issue
Block a user