github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/scheduler/buildstarter_test.go (about) 1 package scheduler_test 2 3 import ( 4 "errors" 5 "fmt" 6 7 "code.cloudfoundry.org/lager/lagertest" 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/db/dbfakes" 11 "github.com/pf-qiu/concourse/v6/atc/scheduler" 12 "github.com/pf-qiu/concourse/v6/atc/scheduler/schedulerfakes" 13 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 ) 17 18 var _ = Describe("BuildStarter", func() { 19 var ( 20 fakePipeline *dbfakes.FakePipeline 21 fakePlanner *schedulerfakes.FakeBuildPlanner 22 pendingBuilds []db.Build 23 fakeAlgorithm *schedulerfakes.FakeAlgorithm 24 25 buildStarter scheduler.BuildStarter 26 27 jobInputs db.InputConfigs 28 29 disaster error 30 ) 31 32 BeforeEach(func() { 33 fakePipeline = new(dbfakes.FakePipeline) 34 fakePlanner = new(schedulerfakes.FakeBuildPlanner) 35 fakeAlgorithm = new(schedulerfakes.FakeAlgorithm) 36 37 buildStarter = scheduler.NewBuildStarter(fakePlanner, fakeAlgorithm) 38 39 disaster = errors.New("bad thing") 40 }) 41 42 Describe("TryStartPendingBuildsForJob", func() { 43 var tryStartErr error 44 var needsReschedule bool 45 var createdBuild *dbfakes.FakeBuild 46 var job *dbfakes.FakeJob 47 var resources db.SchedulerResources 48 var versionedResourceTypes atc.VersionedResourceTypes 49 50 BeforeEach(func() { 51 versionedResourceTypes = atc.VersionedResourceTypes{ 52 { 53 ResourceType: atc.ResourceType{Name: "some-resource-type"}, 54 Version: atc.Version{"some": "version"}, 55 }, 56 } 57 58 resources = db.SchedulerResources{ 59 { 60 Name: "some-resource", 61 }, 62 } 63 }) 64 65 Context("when pending builds are successfully fetched", func() { 66 BeforeEach(func() { 67 createdBuild = new(dbfakes.FakeBuild) 68 createdBuild.IDReturns(66) 69 createdBuild.NameReturns("some-build") 70 71 pendingBuilds = []db.Build{createdBuild} 72 73 job = new(dbfakes.FakeJob) 74 job.GetPendingBuildsReturns(pendingBuilds, nil) 75 job.NameReturns("some-job") 76 job.IDReturns(1) 77 job.ConfigReturns(atc.JobConfig{ 78 PlanSequence: []atc.Step{ 79 { 80 Config: &atc.GetStep{ 81 Name: "input-1", 82 Resource: "some-resource", 83 }, 84 }, { 85 Config: &atc.GetStep{ 86 Name: "input-2", 87 Resource: "some-resource", 88 }, 89 }, 90 }, 91 }, nil) 92 93 jobInputs = db.InputConfigs{ 94 { 95 Name: "input-1", 96 ResourceID: 1, 97 }, 98 { 99 Name: "input-2", 100 ResourceID: 1, 101 }, 102 } 103 }) 104 105 Context("when one pending build is aborted before start", func() { 106 var abortedBuild *dbfakes.FakeBuild 107 108 BeforeEach(func() { 109 abortedBuild = new(dbfakes.FakeBuild) 110 abortedBuild.IDReturns(42) 111 abortedBuild.IsAbortedReturns(true) 112 abortedBuild.FinishReturns(nil) 113 }) 114 115 JustBeforeEach(func() { 116 needsReschedule, tryStartErr = buildStarter.TryStartPendingBuildsForJob( 117 lagertest.NewTestLogger("test"), 118 db.SchedulerJob{ 119 Job: job, 120 Resources: resources, 121 ResourceTypes: versionedResourceTypes, 122 }, 123 jobInputs, 124 ) 125 }) 126 127 Context("when there is one aborted build", func() { 128 BeforeEach(func() { 129 pendingBuilds = []db.Build{abortedBuild} 130 job.GetPendingBuildsReturns(pendingBuilds, nil) 131 }) 132 133 It("won't try to start the aborted pending build", func() { 134 Expect(abortedBuild.FinishCallCount()).To(Equal(1)) 135 }) 136 137 It("returns without error", func() { 138 Expect(tryStartErr).NotTo(HaveOccurred()) 139 Expect(needsReschedule).To(BeFalse()) 140 }) 141 142 Context("when finishing the aborted build fails", func() { 143 BeforeEach(func() { 144 abortedBuild.FinishReturns(disaster) 145 }) 146 147 It("returns an error", func() { 148 Expect(tryStartErr).To(Equal(fmt.Errorf("finish aborted build: %w", disaster))) 149 Expect(needsReschedule).To(BeFalse()) 150 }) 151 }) 152 }) 153 154 Context("when there is multiple pending builds after the aborted build", func() { 155 BeforeEach(func() { 156 // make sure pending build can be started after another pending build is aborted 157 pendingBuilds = append([]db.Build{abortedBuild}, pendingBuilds...) 158 job.GetPendingBuildsReturns(pendingBuilds, nil) 159 }) 160 161 It("will try to start the next non aborted pending build", func() { 162 Expect(job.ScheduleBuildCallCount()).To(Equal(1)) 163 actualBuild := job.ScheduleBuildArgsForCall(0) 164 Expect(actualBuild.Name()).To(Equal(createdBuild.Name())) 165 }) 166 }) 167 }) 168 169 Context("when manually triggered", func() { 170 BeforeEach(func() { 171 createdBuild.IsManuallyTriggeredReturns(true) 172 173 resources = db.SchedulerResources{ 174 { 175 Name: "some-resource", 176 }, 177 } 178 }) 179 180 JustBeforeEach(func() { 181 needsReschedule, tryStartErr = buildStarter.TryStartPendingBuildsForJob( 182 lagertest.NewTestLogger("test"), 183 db.SchedulerJob{ 184 Job: job, 185 Resources: resources, 186 }, 187 jobInputs, 188 ) 189 }) 190 191 It("tries to schedule the build", func() { 192 Expect(job.ScheduleBuildCallCount()).To(Equal(1)) 193 actualBuild := job.ScheduleBuildArgsForCall(0) 194 Expect(actualBuild.Name()).To(Equal(createdBuild.Name())) 195 }) 196 197 Context("when the build not scheduled", func() { 198 BeforeEach(func() { 199 job.ScheduleBuildReturns(false, nil) 200 }) 201 202 It("does not start the build and needs to be rescheduled", func() { 203 Expect(createdBuild.StartCallCount()).To(BeZero()) 204 Expect(tryStartErr).ToNot(HaveOccurred()) 205 Expect(needsReschedule).To(BeTrue()) 206 }) 207 }) 208 209 Context("when scheduling the build fails", func() { 210 BeforeEach(func() { 211 job.ScheduleBuildReturns(false, disaster) 212 }) 213 214 It("returns the error", func() { 215 Expect(tryStartErr).To(Equal(fmt.Errorf("schedule build: %w", disaster))) 216 Expect(needsReschedule).To(BeFalse()) 217 }) 218 }) 219 220 Context("when the build is successfully scheduled", func() { 221 BeforeEach(func() { 222 job.ScheduleBuildReturns(true, nil) 223 }) 224 225 Context("when checking if resources have been checked fails", func() { 226 BeforeEach(func() { 227 createdBuild.ResourcesCheckedReturns(false, disaster) 228 }) 229 230 It("returns the error", func() { 231 Expect(tryStartErr).To(Equal(fmt.Errorf("ready to determine inputs: %w", disaster))) 232 Expect(needsReschedule).To(BeFalse()) 233 }) 234 }) 235 236 Context("when some of the resources are checked before build create time", func() { 237 BeforeEach(func() { 238 createdBuild.ResourcesCheckedReturns(false, nil) 239 }) 240 241 It("does not save the next input mapping", func() { 242 Expect(fakeAlgorithm.ComputeCallCount()).To(BeZero()) 243 }) 244 245 It("does not start the build", func() { 246 Expect(createdBuild.StartCallCount()).To(BeZero()) 247 }) 248 249 It("returns without error", func() { 250 Expect(tryStartErr).NotTo(HaveOccurred()) 251 }) 252 253 It("retries to schedule", func() { 254 Expect(needsReschedule).To(BeTrue()) 255 }) 256 }) 257 258 Context("when all resources are checked after build create time or pinned", func() { 259 BeforeEach(func() { 260 createdBuild.ResourcesCheckedReturns(true, nil) 261 }) 262 263 It("computes a new set of versions for inputs to the build", func() { 264 Expect(fakeAlgorithm.ComputeCallCount()).To(Equal(1)) 265 }) 266 267 Context("when computing the next inputs fails", func() { 268 BeforeEach(func() { 269 fakeAlgorithm.ComputeReturns(nil, false, false, disaster) 270 }) 271 272 It("computes the next inputs for the right job and versions", func() { 273 Expect(fakeAlgorithm.ComputeCallCount()).To(Equal(1)) 274 _, actualJob, actualInputs := fakeAlgorithm.ComputeArgsForCall(0) 275 Expect(actualJob).To(Equal( 276 db.SchedulerJob{ 277 Job: job, 278 Resources: resources, 279 })) 280 Expect(actualInputs).To(Equal(jobInputs)) 281 }) 282 283 It("returns the error and retries to schedule", func() { 284 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("compute inputs: %w", disaster)))) 285 Expect(needsReschedule).To(BeFalse()) 286 }) 287 }) 288 289 Context("when computing the next inputs succeeds", func() { 290 var expectedInputMapping db.InputMapping 291 292 BeforeEach(func() { 293 expectedInputMapping = map[string]db.InputResult{ 294 "input-1": db.InputResult{ 295 Input: &db.AlgorithmInput{ 296 AlgorithmVersion: db.AlgorithmVersion{ 297 ResourceID: 1, 298 Version: db.ResourceVersion("1"), 299 }, 300 FirstOccurrence: true, 301 }, 302 }, 303 } 304 305 fakeAlgorithm.ComputeReturns(expectedInputMapping, true, false, nil) 306 }) 307 308 Context("when the algorithm can run again", func() { 309 BeforeEach(func() { 310 fakeAlgorithm.ComputeReturns(expectedInputMapping, true, true, nil) 311 }) 312 313 It("requests schedule on the job", func() { 314 Expect(job.RequestScheduleCallCount()).To(Equal(1)) 315 }) 316 317 Context("when requesting schedule fails", func() { 318 BeforeEach(func() { 319 job.RequestScheduleReturns(disaster) 320 }) 321 322 It("returns the error and retries to schedule", func() { 323 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("request schedule: %w", disaster)))) 324 Expect(needsReschedule).To(BeFalse()) 325 }) 326 }) 327 }) 328 329 Context("when the algorithm can not run again", func() { 330 BeforeEach(func() { 331 fakeAlgorithm.ComputeReturns(expectedInputMapping, true, false, nil) 332 }) 333 334 It("does not requests schedule on the job", func() { 335 Expect(job.RequestScheduleCallCount()).To(Equal(0)) 336 }) 337 }) 338 339 It("saves the next input mapping", func() { 340 Expect(job.SaveNextInputMappingCallCount()).To(Equal(1)) 341 }) 342 343 Context("when saving the next input mapping fails", func() { 344 BeforeEach(func() { 345 job.SaveNextInputMappingReturns(disaster) 346 }) 347 348 It("saves the next input mapping with the right inputs", func() { 349 actualInputMapping, resolved := job.SaveNextInputMappingArgsForCall(0) 350 Expect(actualInputMapping).To(Equal(expectedInputMapping)) 351 Expect(resolved).To(BeTrue()) 352 }) 353 354 It("returns the error and retries to schedule", func() { 355 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("save next input mapping: %w", disaster)))) 356 Expect(needsReschedule).To(BeFalse()) 357 }) 358 }) 359 360 Context("when saving the next input mapping succeeds", func() { 361 BeforeEach(func() { 362 job.SaveNextInputMappingReturns(nil) 363 }) 364 365 It("saved the next input mapping and adopts the inputs and pipes", func() { 366 Expect(createdBuild.AdoptInputsAndPipesCallCount()).To(Equal(1)) 367 Expect(tryStartErr).NotTo(HaveOccurred()) 368 }) 369 }) 370 371 Context("when adopting inputs and pipes succeeds", func() { 372 BeforeEach(func() { 373 createdBuild.AdoptInputsAndPipesReturns([]db.BuildInput{}, true, nil) 374 }) 375 376 It("tries to fetch the job config", func() { 377 Expect(job.ConfigCallCount()).To(Equal(1)) 378 }) 379 }) 380 381 Context("when adopting inputs and pipes fails", func() { 382 BeforeEach(func() { 383 createdBuild.AdoptInputsAndPipesReturns(nil, false, errors.New("error")) 384 }) 385 386 It("returns an error and retries to schedule", func() { 387 Expect(tryStartErr).To(HaveOccurred()) 388 Expect(needsReschedule).To(BeFalse()) 389 }) 390 }) 391 392 Context("when adopting inputs and pipes has no satisfiable inputs", func() { 393 BeforeEach(func() { 394 createdBuild.AdoptInputsAndPipesReturns(nil, false, nil) 395 }) 396 397 It("does not return an error and does not try to reschedule", func() { 398 Expect(tryStartErr).ToNot(HaveOccurred()) 399 Expect(needsReschedule).To(BeFalse()) 400 }) 401 }) 402 }) 403 }) 404 }) 405 }) 406 407 Context("when not manually triggered", func() { 408 var pendingBuild1 *dbfakes.FakeBuild 409 var pendingBuild2 *dbfakes.FakeBuild 410 var rerunBuild *dbfakes.FakeBuild 411 412 var jobConfig = atc.JobConfig{ 413 Name: "some-job", 414 PlanSequence: []atc.Step{ 415 { 416 Config: &atc.GetStep{ 417 Name: "some-input", 418 }, 419 }, 420 }, 421 } 422 423 var plannedPlan = atc.Plan{ 424 Get: &atc.GetPlan{ 425 Name: "some-input", 426 Resource: "some-input", 427 }, 428 } 429 430 BeforeEach(func() { 431 job.NameReturns("some-job") 432 job.IDReturns(1) 433 job.ConfigReturns(jobConfig, nil) 434 createdBuild.IsManuallyTriggeredReturns(false) 435 436 jobInputs = db.InputConfigs{} 437 }) 438 439 JustBeforeEach(func() { 440 needsReschedule, tryStartErr = buildStarter.TryStartPendingBuildsForJob( 441 lagertest.NewTestLogger("test"), 442 db.SchedulerJob{ 443 Job: job, 444 Resources: resources, 445 ResourceTypes: atc.VersionedResourceTypes{ 446 { 447 ResourceType: atc.ResourceType{ 448 Name: "some-resource-type", 449 }, 450 Version: atc.Version{"some": "version"}, 451 }, 452 }, 453 }, 454 jobInputs, 455 ) 456 }) 457 458 It("doesn't compute the algorithm", func() { 459 Expect(fakeAlgorithm.ComputeCallCount()).To(Equal(0)) 460 }) 461 462 itScheduledAllBuilds := func() { 463 It("scheduled all the pending builds", func() { 464 Expect(job.ScheduleBuildCallCount()).To(Equal(3)) 465 actualBuild := job.ScheduleBuildArgsForCall(0) 466 Expect(actualBuild.ID()).To(Equal(pendingBuild1.ID())) 467 468 actualBuild = job.ScheduleBuildArgsForCall(1) 469 Expect(actualBuild.ID()).To(Equal(rerunBuild.ID())) 470 471 actualBuild = job.ScheduleBuildArgsForCall(2) 472 Expect(actualBuild.ID()).To(Equal(pendingBuild2.ID())) 473 }) 474 } 475 476 Context("when the stars align", func() { 477 BeforeEach(func() { 478 job.PausedReturns(false) 479 job.ScheduleBuildReturns(true, nil) 480 fakePipeline.PausedReturns(false) 481 }) 482 483 Context("when adopting inputs and pipes for a rerun build fails", func() { 484 BeforeEach(func() { 485 pendingBuild1 = new(dbfakes.FakeBuild) 486 pendingBuild1.IDReturns(99) 487 pendingBuild1.RerunOfReturns(1) 488 pendingBuild1.AdoptRerunInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, false, disaster) 489 job.GetPendingBuildsReturns([]db.Build{pendingBuild1}, nil) 490 }) 491 492 It("returns the error and retries to schedule", func() { 493 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("adopt rerun inputs and pipes: %w", disaster)))) 494 Expect(needsReschedule).To(BeFalse()) 495 }) 496 }) 497 498 Context("when adopting inputs and pipes for a rerun build has no satisfiable inputs", func() { 499 BeforeEach(func() { 500 pendingBuild1 = new(dbfakes.FakeBuild) 501 pendingBuild1.IDReturns(99) 502 pendingBuild1.RerunOfReturns(1) 503 pendingBuild1.AdoptRerunInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, false, nil) 504 job.GetPendingBuildsReturns([]db.Build{pendingBuild1}, nil) 505 }) 506 507 It("returns the error and does not retry to schedule", func() { 508 Expect(tryStartErr).ToNot(HaveOccurred()) 509 Expect(needsReschedule).To(BeFalse()) 510 }) 511 }) 512 513 Context("when adopting inputs and pipes for a normal scheduler build fails", func() { 514 BeforeEach(func() { 515 pendingBuild1 = new(dbfakes.FakeBuild) 516 pendingBuild1.IDReturns(99) 517 pendingBuild1.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, false, disaster) 518 job.GetPendingBuildsReturns([]db.Build{pendingBuild1}, nil) 519 }) 520 521 It("returns the error and retries to schedule", func() { 522 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("adopt inputs and pipes: %w", disaster)))) 523 Expect(needsReschedule).To(BeFalse()) 524 }) 525 }) 526 527 Context("when adopting inputs and pipes for a normal scheduler build has no satisfiable inputs", func() { 528 BeforeEach(func() { 529 pendingBuild1 = new(dbfakes.FakeBuild) 530 pendingBuild1.IDReturns(99) 531 pendingBuild1.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, false, nil) 532 job.GetPendingBuildsReturns([]db.Build{pendingBuild1}, nil) 533 }) 534 535 It("returns the error and does not retry to schedule", func() { 536 Expect(tryStartErr).ToNot(HaveOccurred()) 537 Expect(needsReschedule).To(BeFalse()) 538 }) 539 }) 540 541 Context("when there are several pending builds consisting of both retrigger and normal scheduler builds", func() { 542 BeforeEach(func() { 543 pendingBuild1 = new(dbfakes.FakeBuild) 544 pendingBuild1.IDReturns(99) 545 pendingBuild1.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, true, nil) 546 job.ScheduleBuildReturnsOnCall(0, true, nil) 547 pendingBuild2 = new(dbfakes.FakeBuild) 548 pendingBuild2.IDReturns(999) 549 pendingBuild2.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, true, nil) 550 job.ScheduleBuildReturnsOnCall(1, true, nil) 551 rerunBuild = new(dbfakes.FakeBuild) 552 rerunBuild.IDReturns(555) 553 rerunBuild.RerunOfReturns(pendingBuild1.ID()) 554 rerunBuild.AdoptRerunInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, true, nil) 555 job.ScheduleBuildReturnsOnCall(2, true, nil) 556 pendingBuilds = []db.Build{pendingBuild1, rerunBuild, pendingBuild2} 557 job.GetPendingBuildsReturns(pendingBuilds, nil) 558 }) 559 560 Context("when marking the build as scheduled fails", func() { 561 BeforeEach(func() { 562 job.ScheduleBuildReturnsOnCall(0, false, disaster) 563 }) 564 565 It("returns the error", func() { 566 Expect(tryStartErr).To(Equal(fmt.Errorf("schedule build: %w", disaster))) 567 }) 568 569 It("only tried to schedule one pending build", func() { 570 Expect(job.ScheduleBuildCallCount()).To(Equal(1)) 571 }) 572 }) 573 574 Context("when the build was not able to be scheduled", func() { 575 BeforeEach(func() { 576 job.ScheduleBuildReturnsOnCall(0, false, nil) 577 }) 578 579 It("doesn't return an error", func() { 580 Expect(tryStartErr).NotTo(HaveOccurred()) 581 }) 582 583 It("doesn't try adopt build inputs and pipes for that pending build and doesn't try scheduling the next ones", func() { 584 Expect(pendingBuild1.AdoptInputsAndPipesCallCount()).To(BeZero()) 585 Expect(pendingBuild2.AdoptInputsAndPipesCallCount()).To(BeZero()) 586 Expect(rerunBuild.AdoptRerunInputsAndPipesCallCount()).To(BeZero()) 587 }) 588 }) 589 590 Context("when the build was scheduled successfully", func() { 591 Context("when the resource types are successfully fetched", func() { 592 Context("when creating the build plan fails for the rerun build and the scheduler builds", func() { 593 BeforeEach(func() { 594 fakePlanner.CreateReturns(atc.Plan{}, disaster) 595 }) 596 597 It("keeps going after failing to create", func() { 598 Expect(fakePlanner.CreateCallCount()).To(Equal(3)) 599 600 Expect(rerunBuild.FinishCallCount()).To(Equal(1)) 601 Expect(pendingBuild1.FinishCallCount()).To(Equal(1)) 602 Expect(pendingBuild2.FinishCallCount()).To(Equal(1)) 603 }) 604 605 Context("when marking the build as errored fails", func() { 606 BeforeEach(func() { 607 pendingBuild1.FinishReturns(disaster) 608 }) 609 610 It("returns an error", func() { 611 Expect(tryStartErr).To(Equal(fmt.Errorf("finish build: %w", disaster))) 612 Expect(needsReschedule).To(BeFalse()) 613 }) 614 615 It("does not start the other pending build", func() { 616 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 617 }) 618 619 It("marked the right build as errored", func() { 620 Expect(pendingBuild1.FinishCallCount()).To(Equal(1)) 621 actualStatus := pendingBuild1.FinishArgsForCall(0) 622 Expect(actualStatus).To(Equal(db.BuildStatusErrored)) 623 }) 624 }) 625 626 Context("when marking the build as errored succeeds", func() { 627 BeforeEach(func() { 628 pendingBuild1.FinishReturns(nil) 629 }) 630 631 It("does not start the other builds", func() { 632 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 633 }) 634 635 It("doesn't return an error", func() { 636 Expect(tryStartErr).NotTo(HaveOccurred()) 637 Expect(needsReschedule).To(BeFalse()) 638 }) 639 }) 640 }) 641 642 Context("when creating the build plan succeeds", func() { 643 BeforeEach(func() { 644 fakePlanner.CreateReturns(plannedPlan, nil) 645 pendingBuild1.StartReturns(true, nil) 646 pendingBuild2.StartReturns(true, nil) 647 rerunBuild.StartReturns(true, nil) 648 }) 649 650 It("adopts the build inputs and pipes", func() { 651 Expect(pendingBuild1.AdoptInputsAndPipesCallCount()).To(Equal(1)) 652 Expect(pendingBuild1.AdoptRerunInputsAndPipesCallCount()).To(BeZero()) 653 654 Expect(pendingBuild2.AdoptInputsAndPipesCallCount()).To(Equal(1)) 655 Expect(pendingBuild2.AdoptRerunInputsAndPipesCallCount()).To(BeZero()) 656 657 Expect(rerunBuild.AdoptInputsAndPipesCallCount()).To(BeZero()) 658 Expect(rerunBuild.AdoptRerunInputsAndPipesCallCount()).To(Equal(1)) 659 }) 660 661 It("creates build plans for all builds", func() { 662 Expect(fakePlanner.CreateCallCount()).To(Equal(3)) 663 664 actualPlanConfig, actualResourceConfigs, actualResourceTypes, actualBuildInputs := fakePlanner.CreateArgsForCall(0) 665 Expect(actualPlanConfig).To(Equal(&atc.DoStep{Steps: jobConfig.PlanSequence})) 666 Expect(actualResourceConfigs).To(Equal(db.SchedulerResources{{Name: "some-resource"}})) 667 Expect(actualResourceTypes).To(Equal(versionedResourceTypes)) 668 Expect(actualBuildInputs).To(Equal([]db.BuildInput{{Name: "some-input"}})) 669 670 actualPlanConfig, actualResourceConfigs, actualResourceTypes, actualBuildInputs = fakePlanner.CreateArgsForCall(1) 671 Expect(actualPlanConfig).To(Equal(&atc.DoStep{Steps: jobConfig.PlanSequence})) 672 Expect(actualResourceConfigs).To(Equal(db.SchedulerResources{{Name: "some-resource"}})) 673 Expect(actualResourceTypes).To(Equal(versionedResourceTypes)) 674 Expect(actualBuildInputs).To(Equal([]db.BuildInput{{Name: "some-input"}})) 675 676 actualPlanConfig, actualResourceConfigs, actualResourceTypes, actualBuildInputs = fakePlanner.CreateArgsForCall(2) 677 Expect(actualPlanConfig).To(Equal(&atc.DoStep{Steps: jobConfig.PlanSequence})) 678 Expect(actualResourceConfigs).To(Equal(db.SchedulerResources{{Name: "some-resource"}})) 679 Expect(actualResourceTypes).To(Equal(versionedResourceTypes)) 680 Expect(actualBuildInputs).To(Equal([]db.BuildInput{{Name: "some-input"}})) 681 }) 682 683 Context("when starting the build fails", func() { 684 BeforeEach(func() { 685 pendingBuild1.StartReturns(false, disaster) 686 }) 687 688 It("returns the error", func() { 689 Expect(tryStartErr).To(Equal(fmt.Errorf("start build: %w", disaster))) 690 Expect(needsReschedule).To(BeFalse()) 691 }) 692 693 It("does not start the other builds", func() { 694 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 695 }) 696 }) 697 698 Context("when starting the build returns false", func() { 699 BeforeEach(func() { 700 pendingBuild1.StartReturns(false, nil) 701 }) 702 703 It("doesn't return an error", func() { 704 Expect(tryStartErr).NotTo(HaveOccurred()) 705 Expect(needsReschedule).To(BeFalse()) 706 }) 707 708 It("starts the other builds", func() { 709 Expect(pendingBuild2.StartCallCount()).To(Equal(1)) 710 }) 711 712 It("finishes the build with aborted status", func() { 713 Expect(pendingBuild1.FinishCallCount()).To(Equal(1)) 714 Expect(pendingBuild1.FinishArgsForCall(0)).To(Equal(db.BuildStatusAborted)) 715 }) 716 717 Context("when marking the build as errored fails", func() { 718 BeforeEach(func() { 719 pendingBuild1.FinishReturns(disaster) 720 }) 721 722 It("returns an error", func() { 723 Expect(tryStartErr).To(Equal(fmt.Errorf("finish build: %w", disaster))) 724 Expect(needsReschedule).To(BeFalse()) 725 }) 726 727 It("does not start the other builds", func() { 728 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 729 }) 730 731 It("marked the right build as errored", func() { 732 Expect(pendingBuild1.FinishCallCount()).To(Equal(1)) 733 actualStatus := pendingBuild1.FinishArgsForCall(0) 734 Expect(actualStatus).To(Equal(db.BuildStatusAborted)) 735 }) 736 }) 737 738 Context("when marking the build as errored succeeds", func() { 739 BeforeEach(func() { 740 pendingBuild1.FinishReturns(nil) 741 }) 742 743 It("doesn't return an error", func() { 744 Expect(tryStartErr).NotTo(HaveOccurred()) 745 Expect(needsReschedule).To(BeFalse()) 746 }) 747 748 It("starts the other builds", func() { 749 Expect(pendingBuild2.StartCallCount()).To(Equal(1)) 750 }) 751 }) 752 }) 753 754 Context("when starting the builds returns true", func() { 755 BeforeEach(func() { 756 pendingBuild1.StartReturns(true, nil) 757 pendingBuild2.StartReturns(true, nil) 758 rerunBuild.StartReturns(true, nil) 759 }) 760 761 It("doesn't return an error", func() { 762 Expect(tryStartErr).NotTo(HaveOccurred()) 763 Expect(needsReschedule).To(BeFalse()) 764 }) 765 766 itScheduledAllBuilds() 767 768 It("starts the build with the right plan", func() { 769 Expect(pendingBuild1.StartCallCount()).To(Equal(1)) 770 Expect(pendingBuild1.StartArgsForCall(0)).To(Equal(plannedPlan)) 771 772 Expect(pendingBuild2.StartCallCount()).To(Equal(1)) 773 Expect(pendingBuild2.StartArgsForCall(0)).To(Equal(plannedPlan)) 774 775 Expect(rerunBuild.StartCallCount()).To(Equal(1)) 776 Expect(rerunBuild.StartArgsForCall(0)).To(Equal(plannedPlan)) 777 }) 778 }) 779 }) 780 }) 781 }) 782 783 Context("when adopting the inputs and pipes fails", func() { 784 BeforeEach(func() { 785 pendingBuild1.AdoptInputsAndPipesReturns(nil, false, disaster) 786 }) 787 788 It("returns the error", func() { 789 Expect(tryStartErr).To(Equal(fmt.Errorf("get build inputs: %w", fmt.Errorf("adopt inputs and pipes: %w", disaster)))) 790 Expect(needsReschedule).To(BeFalse()) 791 }) 792 }) 793 794 Context("when there are no next build inputs", func() { 795 BeforeEach(func() { 796 pendingBuild1.AdoptInputsAndPipesReturns(nil, false, nil) 797 }) 798 799 It("doesn't return an error", func() { 800 Expect(tryStartErr).NotTo(HaveOccurred()) 801 Expect(needsReschedule).To(BeFalse()) 802 }) 803 804 It("does not start the build", func() { 805 Expect(createdBuild.StartCallCount()).To(BeZero()) 806 }) 807 }) 808 809 Context("when fetching pending builds fail", func() { 810 BeforeEach(func() { 811 job.GetPendingBuildsReturns(nil, disaster) 812 }) 813 814 It("returns the error", func() { 815 Expect(tryStartErr).To(Equal(fmt.Errorf("get pending builds: %w", disaster))) 816 }) 817 818 It("does not need to be rescheduled", func() { 819 Expect(needsReschedule).To(BeFalse()) 820 }) 821 }) 822 }) 823 824 Context("when there are several pending builds with one failing to start rerun build", func() { 825 BeforeEach(func() { 826 pendingBuild1 = new(dbfakes.FakeBuild) 827 pendingBuild1.IDReturns(99) 828 pendingBuild1.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, true, nil) 829 pendingBuild1.StartReturns(true, nil) 830 job.ScheduleBuildReturnsOnCall(0, true, nil) 831 pendingBuild2 = new(dbfakes.FakeBuild) 832 pendingBuild2.IDReturns(999) 833 pendingBuild2.AdoptInputsAndPipesReturns([]db.BuildInput{{Name: "some-input"}}, true, nil) 834 pendingBuild2.StartReturns(true, nil) 835 job.ScheduleBuildReturnsOnCall(2, true, nil) 836 }) 837 838 Context("when the rerun build is failing to adopt inputs and outputs", func() { 839 BeforeEach(func() { 840 rerunBuild = new(dbfakes.FakeBuild) 841 rerunBuild.IDReturns(555) 842 rerunBuild.RerunOfReturns(pendingBuild1.ID()) 843 rerunBuild.AdoptRerunInputsAndPipesReturns(nil, false, errors.New("error")) 844 job.ScheduleBuildReturnsOnCall(1, true, nil) 845 pendingBuilds = []db.Build{pendingBuild1, rerunBuild, pendingBuild2} 846 job.GetPendingBuildsReturns(pendingBuilds, nil) 847 }) 848 849 It("does not schedule the next build", func() { 850 Expect(tryStartErr).To(HaveOccurred()) 851 Expect(pendingBuild1.StartCallCount()).To(Equal(1)) 852 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 853 }) 854 }) 855 856 Context("when the rerun build is not started because it has no inputs or versions", func() { 857 BeforeEach(func() { 858 rerunBuild = new(dbfakes.FakeBuild) 859 rerunBuild.IDReturns(555) 860 rerunBuild.RerunOfReturns(pendingBuild1.ID()) 861 rerunBuild.AdoptRerunInputsAndPipesReturns(nil, false, nil) 862 job.ScheduleBuildReturnsOnCall(1, true, nil) 863 pendingBuilds = []db.Build{pendingBuild1, rerunBuild, pendingBuild2} 864 job.GetPendingBuildsReturns(pendingBuilds, nil) 865 }) 866 867 It("tries to schedule the 2 other pending builds", func() { 868 Expect(tryStartErr).ToNot(HaveOccurred()) 869 Expect(needsReschedule).To(BeFalse()) 870 Expect(pendingBuild1.StartCallCount()).To(Equal(1)) 871 Expect(pendingBuild2.StartCallCount()).To(Equal(1)) 872 }) 873 }) 874 875 Context("when the rerun build needs to retry a new scheduler tick", func() { 876 BeforeEach(func() { 877 rerunBuild = new(dbfakes.FakeBuild) 878 rerunBuild.IDReturns(555) 879 rerunBuild.RerunOfReturns(pendingBuild1.ID()) 880 job.ScheduleBuildReturnsOnCall(1, false, nil) 881 pendingBuilds = []db.Build{pendingBuild1, rerunBuild, pendingBuild2} 882 job.GetPendingBuildsReturns(pendingBuilds, nil) 883 }) 884 885 It("does not try to schedule the other pending build", func() { 886 Expect(tryStartErr).ToNot(HaveOccurred()) 887 Expect(needsReschedule).To(BeTrue()) 888 Expect(pendingBuild1.StartCallCount()).To(Equal(1)) 889 Expect(pendingBuild2.StartCallCount()).To(Equal(0)) 890 }) 891 }) 892 }) 893 }) 894 }) 895 }) 896 }) 897 })