github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/scheduler/buildstarter.go (about) 1 package scheduler 2 3 import ( 4 "context" 5 "fmt" 6 7 "code.cloudfoundry.org/lager" 8 "github.com/pf-qiu/concourse/v6/atc" 9 "github.com/pf-qiu/concourse/v6/atc/db" 10 "github.com/pf-qiu/concourse/v6/atc/metric" 11 ) 12 13 //go:generate counterfeiter . BuildStarter 14 15 type BuildStarter interface { 16 TryStartPendingBuildsForJob( 17 logger lager.Logger, 18 job db.SchedulerJob, 19 inputs db.InputConfigs, 20 ) (bool, error) 21 } 22 23 //go:generate counterfeiter . BuildPlanner 24 25 type BuildPlanner interface { 26 Create(atc.StepConfig, db.SchedulerResources, atc.VersionedResourceTypes, []db.BuildInput) (atc.Plan, error) 27 } 28 29 type Build interface { 30 db.Build 31 32 IsReadyToDetermineInputs(lager.Logger) (bool, error) 33 BuildInputs(context.Context) ([]db.BuildInput, bool, error) 34 } 35 36 func NewBuildStarter( 37 planner BuildPlanner, 38 algorithm Algorithm, 39 ) BuildStarter { 40 return &buildStarter{ 41 planner: planner, 42 algorithm: algorithm, 43 } 44 } 45 46 type buildStarter struct { 47 planner BuildPlanner 48 algorithm Algorithm 49 } 50 51 func (s *buildStarter) TryStartPendingBuildsForJob( 52 logger lager.Logger, 53 job db.SchedulerJob, 54 jobInputs db.InputConfigs, 55 ) (bool, error) { 56 nextPendingBuilds, err := job.GetPendingBuilds() 57 if err != nil { 58 return false, fmt.Errorf("get pending builds: %w", err) 59 } 60 61 buildsToSchedule := s.constructBuilds(job, jobInputs, nextPendingBuilds) 62 63 var needsRetry bool 64 for _, nextSchedulableBuild := range buildsToSchedule { 65 results, err := s.tryStartNextPendingBuild(logger, nextSchedulableBuild, job) 66 if err != nil { 67 return false, err 68 } 69 70 if results.finished { 71 // If the build is successfully aborted, errored or started, continue 72 // onto the next pending build 73 continue 74 } 75 76 if !results.scheduled || !results.readyToDetermineInputs { 77 // If max in flight is reached or a manually triggered build has not 78 // checked all resources, stop scheduling and retry later 79 needsRetry = true 80 break 81 } 82 83 if !results.inputsDetermined { 84 if nextSchedulableBuild.RerunOf() != 0 { 85 // If it is a rerun build, continue on to next build. We don't want to 86 // stop scheduling other builds because of a rerun build cannot 87 // determine inputs 88 continue 89 } else { 90 // If it is a regular scheduler build, stop scheduling because it is 91 // failing to determine inputs 92 break 93 } 94 } 95 } 96 97 return needsRetry, nil 98 } 99 100 func (s *buildStarter) constructBuilds(job db.Job, jobInputs db.InputConfigs, builds []db.Build) []Build { 101 var buildsToSchedule []Build 102 103 for _, nextPendingBuild := range builds { 104 if nextPendingBuild.IsManuallyTriggered() { 105 buildsToSchedule = append(buildsToSchedule, &manualTriggerBuild{ 106 Build: nextPendingBuild, 107 algorithm: s.algorithm, 108 job: job, 109 jobInputs: jobInputs, 110 }) 111 } else if nextPendingBuild.RerunOf() != 0 { 112 buildsToSchedule = append(buildsToSchedule, &rerunBuild{ 113 Build: nextPendingBuild, 114 }) 115 } else { 116 buildsToSchedule = append(buildsToSchedule, &schedulerBuild{ 117 Build: nextPendingBuild, 118 }) 119 } 120 } 121 122 return buildsToSchedule 123 } 124 125 type startResults struct { 126 finished bool 127 scheduled bool 128 readyToDetermineInputs bool 129 inputsDetermined bool 130 } 131 132 func (s *buildStarter) tryStartNextPendingBuild( 133 logger lager.Logger, 134 nextPendingBuild Build, 135 job db.SchedulerJob, 136 ) (startResults, error) { 137 logger = logger.Session("try-start-next-pending-build", lager.Data{ 138 "build-id": nextPendingBuild.ID(), 139 "build-name": nextPendingBuild.Name(), 140 }) 141 142 if nextPendingBuild.IsAborted() { 143 logger.Debug("cancel-aborted-pending-build") 144 145 err := nextPendingBuild.Finish(db.BuildStatusAborted) 146 if err != nil { 147 return startResults{}, fmt.Errorf("finish aborted build: %w", err) 148 } 149 150 return startResults{ 151 finished: true, 152 }, nil 153 } 154 155 scheduled, err := job.ScheduleBuild(nextPendingBuild) 156 if err != nil { 157 return startResults{}, fmt.Errorf("schedule build: %w", err) 158 } 159 160 if !scheduled { 161 logger.Debug("build-not-scheduled") 162 return startResults{ 163 scheduled: scheduled, 164 }, nil 165 } 166 167 readyToDetermineInputs, err := nextPendingBuild.IsReadyToDetermineInputs(logger) 168 if err != nil { 169 return startResults{}, fmt.Errorf("ready to determine inputs: %w", err) 170 } 171 172 if !readyToDetermineInputs { 173 return startResults{ 174 scheduled: scheduled, 175 readyToDetermineInputs: readyToDetermineInputs, 176 }, nil 177 } 178 179 buildInputs, inputsDetermined, err := nextPendingBuild.BuildInputs(context.TODO()) 180 if err != nil { 181 return startResults{}, fmt.Errorf("get build inputs: %w", err) 182 } 183 184 if !inputsDetermined { 185 logger.Debug("build-inputs-not-found") 186 187 // don't retry when build inputs are not found because this is due to the 188 // inputs being unsatisfiable 189 return startResults{ 190 scheduled: scheduled, 191 readyToDetermineInputs: readyToDetermineInputs, 192 inputsDetermined: inputsDetermined, 193 }, nil 194 } 195 196 config, err := job.Config() 197 if err != nil { 198 return startResults{}, fmt.Errorf("config: %w", err) 199 } 200 201 plan, err := s.planner.Create(config.StepConfig(), job.Resources, job.ResourceTypes, buildInputs) 202 if err != nil { 203 logger.Error("failed-to-create-build-plan", err) 204 205 // Don't use ErrorBuild because it logs a build event, and this build hasn't started 206 if err = nextPendingBuild.Finish(db.BuildStatusErrored); err != nil { 207 logger.Error("failed-to-mark-build-as-errored", err) 208 return startResults{}, fmt.Errorf("finish build: %w", err) 209 } 210 211 return startResults{ 212 finished: true, 213 }, nil 214 } 215 216 started, err := nextPendingBuild.Start(plan) 217 if err != nil { 218 logger.Error("failed-to-mark-build-as-started", err) 219 return startResults{}, fmt.Errorf("start build: %w", err) 220 } 221 222 if !started { 223 if err = nextPendingBuild.Finish(db.BuildStatusAborted); err != nil { 224 logger.Error("failed-to-mark-build-as-finished", err) 225 return startResults{}, fmt.Errorf("finish build: %w", err) 226 } 227 228 return startResults{ 229 finished: true, 230 }, nil 231 } 232 233 metric.Metrics.BuildsStarted.Inc() 234 235 return startResults{ 236 finished: true, 237 }, nil 238 }