diff --git a/internal/services/runservice/scheduler/command/command.go b/internal/services/runservice/scheduler/command/command.go index 5512612..839767c 100644 --- a/internal/services/runservice/scheduler/command/command.go +++ b/internal/services/runservice/scheduler/command/command.go @@ -216,6 +216,16 @@ func (s *CommandHandler) recreateRun(ctx context.Context, req *RunCreateRequest) return nil, errors.Wrapf(err, "run %q doens't exist", req.RunID) } + if req.FromStart { + if canRestart, reason := run.CanRestartFromScratch(); !canRestart { + return nil, errors.Errorf("run cannot be restarted: %s", reason) + } + } else { + if canRestart, reason := run.CanRestartFromFailedTasks(); !canRestart { + return nil, errors.Errorf("run cannot be restarted: %s", reason) + } + } + // update the run ID run.ID = id // reset run revision @@ -231,7 +241,6 @@ func (s *CommandHandler) recreateRun(ctx context.Context, req *RunCreateRequest) tasksToAdd := []*types.RunTask{} tasksToDelete := []string{} - s.log.Infof("fromStart: %t", req.FromStart) for _, rt := range run.RunTasks { if req.FromStart || rt.Status != types.RunTaskStatusSuccess { rct := rc.Tasks[rt.ID] diff --git a/internal/services/runservice/types/types.go b/internal/services/runservice/types/types.go index a84884d..4eed09b 100644 --- a/internal/services/runservice/types/types.go +++ b/internal/services/runservice/types/types.go @@ -17,6 +17,7 @@ package types import ( "encoding/base64" "encoding/json" + "fmt" "time" "github.com/sorintlab/agola/internal/util" @@ -131,6 +132,36 @@ func (r *Run) TasksWaitingApproval() []string { return runTasksIDs } +// CanRestartFromScratch reports if the run can be restarted from scratch +func (r *Run) CanRestartFromScratch() (bool, string) { + // can restart only if the run phase is finished or cancelled + if !r.Phase.IsFinished() { + return false, fmt.Sprintf("run is not finished, phase: %q", r.Phase) + } + return true, "" +} + +// CanRestartFromFailedTasks reports if the run can be restarted from failed tasks +func (r *Run) CanRestartFromFailedTasks() (bool, string) { + // can restart only if the run phase is finished or cancelled + if !r.Phase.IsFinished() { + return false, fmt.Sprintf("run is not finished, phase: %q", r.Phase) + } + // can restart from failed tasks only if there're some failed tasks + if r.Result == RunResultSuccess { + return false, fmt.Sprintf("run %q has success result, cannot restart from failed tasks", r.ID) + } + // can restart only if the successful tasks are fully archived + for _, rt := range r.RunTasks { + if rt.Status == RunTaskStatusSuccess { + if !rt.LogsFetchFinished() || !rt.ArchivesFetchFinished() { + return false, fmt.Sprintf("run %q task %q not fully archived", r.ID, rt.ID) + } + } + } + return true, "" +} + type RunTaskStatus string const (