github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/client_test.go (about) 1 package worker_test 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "path" 9 "time" 10 11 "code.cloudfoundry.org/garden" 12 "code.cloudfoundry.org/garden/gardenfakes" 13 "code.cloudfoundry.org/lager" 14 "github.com/pf-qiu/concourse/v6/atc" 15 "github.com/pf-qiu/concourse/v6/atc/compression/compressionfakes" 16 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 17 "github.com/pf-qiu/concourse/v6/atc/db/lock/lockfakes" 18 "github.com/pf-qiu/concourse/v6/atc/resource/resourcefakes" 19 "github.com/pf-qiu/concourse/v6/atc/runtime" 20 "github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes" 21 "github.com/onsi/gomega/gbytes" 22 23 "code.cloudfoundry.org/lager/lagertest" 24 "github.com/concourse/baggageclaim" 25 "github.com/pf-qiu/concourse/v6/atc/db" 26 "github.com/pf-qiu/concourse/v6/atc/worker" 27 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 28 29 . "github.com/onsi/ginkgo" 30 . "github.com/onsi/gomega" 31 ) 32 33 var _ = Describe("Client", func() { 34 var ( 35 logger *lagertest.TestLogger 36 fakePool *workerfakes.FakePool 37 fakeProvider *workerfakes.FakeWorkerProvider 38 client worker.Client 39 fakeLock *lockfakes.FakeLock 40 fakeLockFactory *lockfakes.FakeLockFactory 41 fakeCompression *compressionfakes.FakeCompression 42 ) 43 44 BeforeEach(func() { 45 logger = lagertest.NewTestLogger("test") 46 fakePool = new(workerfakes.FakePool) 47 fakeProvider = new(workerfakes.FakeWorkerProvider) 48 fakeCompression = new(compressionfakes.FakeCompression) 49 workerPolling := 1 * time.Second 50 workerStatus := 2 * time.Second 51 52 client = worker.NewClient(fakePool, fakeProvider, fakeCompression, workerPolling, workerStatus, false, 15*time.Minute) 53 }) 54 55 Describe("FindContainer", func() { 56 var ( 57 foundContainer worker.Container 58 found bool 59 findErr error 60 ) 61 62 JustBeforeEach(func() { 63 foundContainer, found, findErr = client.FindContainer( 64 logger, 65 4567, 66 "some-handle", 67 ) 68 }) 69 70 Context("when looking up the worker errors", func() { 71 BeforeEach(func() { 72 fakeProvider.FindWorkerForContainerReturns(nil, false, errors.New("nope")) 73 }) 74 75 It("errors", func() { 76 Expect(findErr).To(HaveOccurred()) 77 }) 78 }) 79 80 Context("when worker is not found", func() { 81 BeforeEach(func() { 82 fakeProvider.FindWorkerForContainerReturns(nil, false, nil) 83 }) 84 85 It("returns not found", func() { 86 Expect(findErr).NotTo(HaveOccurred()) 87 Expect(found).To(BeFalse()) 88 }) 89 }) 90 91 Context("when a worker is found with the container", func() { 92 var fakeWorker *workerfakes.FakeWorker 93 var fakeContainer *workerfakes.FakeContainer 94 95 BeforeEach(func() { 96 fakeWorker = new(workerfakes.FakeWorker) 97 fakeProvider.FindWorkerForContainerReturns(fakeWorker, true, nil) 98 99 fakeContainer = new(workerfakes.FakeContainer) 100 fakeWorker.FindContainerByHandleReturns(fakeContainer, true, nil) 101 }) 102 103 It("succeeds", func() { 104 Expect(found).To(BeTrue()) 105 Expect(findErr).NotTo(HaveOccurred()) 106 }) 107 108 It("returns the created container", func() { 109 Expect(foundContainer).To(Equal(fakeContainer)) 110 }) 111 }) 112 }) 113 114 Describe("FindVolume", func() { 115 var ( 116 foundVolume worker.Volume 117 found bool 118 findErr error 119 ) 120 121 JustBeforeEach(func() { 122 foundVolume, found, findErr = client.FindVolume( 123 logger, 124 4567, 125 "some-handle", 126 ) 127 }) 128 129 Context("when looking up the worker errors", func() { 130 BeforeEach(func() { 131 fakeProvider.FindWorkerForVolumeReturns(nil, false, errors.New("nope")) 132 }) 133 134 It("errors", func() { 135 Expect(findErr).To(HaveOccurred()) 136 }) 137 }) 138 139 Context("when worker is not found", func() { 140 BeforeEach(func() { 141 fakeProvider.FindWorkerForVolumeReturns(nil, false, nil) 142 }) 143 144 It("returns not found", func() { 145 Expect(findErr).NotTo(HaveOccurred()) 146 Expect(found).To(BeFalse()) 147 }) 148 }) 149 150 Context("when a worker is found with the volume", func() { 151 var fakeWorker *workerfakes.FakeWorker 152 var fakeVolume *workerfakes.FakeVolume 153 154 BeforeEach(func() { 155 fakeWorker = new(workerfakes.FakeWorker) 156 fakeProvider.FindWorkerForVolumeReturns(fakeWorker, true, nil) 157 158 fakeVolume = new(workerfakes.FakeVolume) 159 fakeWorker.LookupVolumeReturns(fakeVolume, true, nil) 160 }) 161 162 It("succeeds", func() { 163 Expect(found).To(BeTrue()) 164 Expect(findErr).NotTo(HaveOccurred()) 165 }) 166 167 It("returns the volume", func() { 168 Expect(foundVolume).To(Equal(fakeVolume)) 169 }) 170 }) 171 }) 172 173 Describe("CreateVolume", func() { 174 var ( 175 fakeWorker *workerfakes.FakeWorker 176 volumeSpec worker.VolumeSpec 177 workerSpec worker.WorkerSpec 178 volumeType db.VolumeType 179 err error 180 ) 181 182 BeforeEach(func() { 183 volumeSpec = worker.VolumeSpec{ 184 Strategy: baggageclaim.EmptyStrategy{}, 185 } 186 187 workerSpec = worker.WorkerSpec{ 188 TeamID: 1, 189 } 190 191 volumeType = db.VolumeTypeArtifact 192 }) 193 194 JustBeforeEach(func() { 195 _, err = client.CreateVolume(logger, volumeSpec, workerSpec, volumeType) 196 }) 197 198 Context("when no workers can be found", func() { 199 BeforeEach(func() { 200 fakePool.FindOrChooseWorkerReturns(nil, errors.New("nope")) 201 }) 202 203 It("returns an error", func() { 204 Expect(err).To(HaveOccurred()) 205 }) 206 }) 207 208 Context("when the worker can be found", func() { 209 BeforeEach(func() { 210 fakeWorker = new(workerfakes.FakeWorker) 211 fakePool.FindOrChooseWorkerReturns(fakeWorker, nil) 212 }) 213 214 It("creates the volume on the worker", func() { 215 Expect(err).ToNot(HaveOccurred()) 216 Expect(fakeWorker.CreateVolumeCallCount()).To(Equal(1)) 217 l, spec, id, t := fakeWorker.CreateVolumeArgsForCall(0) 218 Expect(l).To(Equal(logger)) 219 Expect(spec).To(Equal(volumeSpec)) 220 Expect(id).To(Equal(1)) 221 Expect(t).To(Equal(volumeType)) 222 }) 223 }) 224 }) 225 226 Describe("RunCheckStep", func() { 227 228 var ( 229 containerSpec worker.ContainerSpec 230 workerSpec worker.WorkerSpec 231 result worker.CheckResult 232 err, expectedErr error 233 fakeResource *resourcefakes.FakeResource 234 fakeEventDelegate *runtimefakes.FakeStartingEventDelegate 235 fakeProcessSpec runtime.ProcessSpec 236 ) 237 238 BeforeEach(func() { 239 fakeResource = new(resourcefakes.FakeResource) 240 fakeEventDelegate = new(runtimefakes.FakeStartingEventDelegate) 241 stdout := new(gbytes.Buffer) 242 stderr := new(gbytes.Buffer) 243 containerSpec = worker.ContainerSpec{ 244 TeamID: 123, 245 ImageSpec: worker.ImageSpec{ 246 ResourceType: "some-base-type", 247 Privileged: false, 248 }, 249 Dir: "some-artifact-root", 250 } 251 workerSpec = worker.WorkerSpec{ 252 Platform: "some-platform", 253 Tags: []string{"step", "tags"}, 254 } 255 fakeProcessSpec = runtime.ProcessSpec{ 256 Path: "/opt/resource/out", 257 StdoutWriter: stdout, 258 StderrWriter: stderr, 259 } 260 }) 261 262 JustBeforeEach(func() { 263 owner := new(dbfakes.FakeContainerOwner) 264 fakeStrategy := new(workerfakes.FakeContainerPlacementStrategy) 265 266 result, err = client.RunCheckStep( 267 context.Background(), 268 logger, 269 owner, 270 containerSpec, 271 workerSpec, 272 fakeStrategy, 273 metadata, 274 fakeProcessSpec, 275 fakeEventDelegate, 276 fakeResource, 277 1*time.Nanosecond, 278 ) 279 }) 280 281 Context("faling to find worker for container", func() { 282 BeforeEach(func() { 283 expectedErr = errors.New("find-worker-err") 284 285 fakePool.FindOrChooseWorkerForContainerReturns(nil, expectedErr) 286 }) 287 288 It("errors", func() { 289 Expect(err).To(HaveOccurred()) 290 Expect(errors.Is(err, expectedErr)).To(BeTrue()) 291 }) 292 }) 293 294 Context("having found a worker", func() { 295 var fakeWorker *workerfakes.FakeWorker 296 297 BeforeEach(func() { 298 fakeWorker = new(workerfakes.FakeWorker) 299 fakeWorker.NameReturns("some-worker") 300 fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil) 301 }) 302 303 Describe("with an image artifact", func() { 304 var fakeArtifact *runtimefakes.FakeArtifact 305 var fakeVolumeWorker *workerfakes.FakeWorker 306 var fakeVolume *workerfakes.FakeVolume 307 308 BeforeEach(func() { 309 fakeArtifact = new(runtimefakes.FakeArtifact) 310 311 containerSpec.ImageSpec = worker.ImageSpec{ 312 ImageArtifact: fakeArtifact, 313 } 314 315 fakeVolumeWorker = new(workerfakes.FakeWorker) 316 fakeProvider.FindWorkerForVolumeReturns(fakeVolumeWorker, true, nil) 317 318 fakeVolume = new(workerfakes.FakeVolume) 319 fakeVolumeWorker.LookupVolumeReturns(fakeVolume, true, nil) 320 }) 321 322 It("locates the volume and assigns it as an ImageArtifactSource", func() { 323 _, _, _, _, containerSpec := fakeWorker.FindOrCreateContainerArgsForCall(0) 324 imageSpec := containerSpec.ImageSpec 325 Expect(imageSpec.ImageArtifactSource).To(Equal(worker.NewStreamableArtifactSource(fakeArtifact, fakeVolume, fakeCompression, false, 15*time.Minute))) 326 }) 327 }) 328 329 Context("failing to find or create container in the worker", func() { 330 BeforeEach(func() { 331 expectedErr = errors.New("find-or-create-container-err") 332 fakeWorker.FindOrCreateContainerReturns(nil, expectedErr) 333 }) 334 335 It("errors", func() { 336 Expect(errors.Is(err, expectedErr)).To(BeTrue()) 337 }) 338 }) 339 340 Context("having found a container", func() { 341 var fakeContainer *workerfakes.FakeContainer 342 343 BeforeEach(func() { 344 fakeContainer = new(workerfakes.FakeContainer) 345 fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil) 346 }) 347 348 It("emits a selected worker event", func() { 349 Expect(fakeEventDelegate.SelectedWorkerCallCount()).To(Equal(1)) 350 _, name := fakeEventDelegate.SelectedWorkerArgsForCall(0) 351 Expect(name).To(Equal("some-worker")) 352 }) 353 354 It("runs check w/ timeout", func() { 355 ctx, _, _ := fakeResource.CheckArgsForCall(0) 356 _, hasDeadline := ctx.Deadline() 357 358 Expect(hasDeadline).To(BeTrue()) 359 }) 360 361 It("uses the right executable path in the proc spec", func() { 362 _, processSpec, _ := fakeResource.CheckArgsForCall(0) 363 364 Expect(processSpec).To(Equal(fakeProcessSpec)) 365 }) 366 367 It("uses the container as the runner", func() { 368 _, _, container := fakeResource.CheckArgsForCall(0) 369 370 Expect(container).To(Equal(fakeContainer)) 371 }) 372 373 Context("prior to running the check", func() { 374 BeforeEach(func() { 375 fakeEventDelegate.StartingStub = func(lager.Logger) { 376 Expect(fakeResource.CheckCallCount()).To(Equal(0)) 377 return 378 } 379 }) 380 381 It("invokes the Starting Event on the delegate", func() { 382 Expect(fakeEventDelegate.StartingCallCount()).Should((Equal(1))) 383 }) 384 }) 385 386 Context("succeeding", func() { 387 BeforeEach(func() { 388 fakeResource.CheckReturns([]atc.Version{ 389 {"version": "1"}, 390 }, nil) 391 }) 392 393 It("returns the versions", func() { 394 Expect(result.Versions).To(HaveLen(1)) 395 Expect(result.Versions[0]).To(Equal(atc.Version{"version": "1"})) 396 }) 397 }) 398 399 Context("check erroring", func() { 400 BeforeEach(func() { 401 expectedErr = errors.New("check-err") 402 fakeResource.CheckReturns(nil, expectedErr) 403 }) 404 405 It("errors", func() { 406 Expect(errors.Is(err, expectedErr)).To(BeTrue()) 407 }) 408 }) 409 }) 410 }) 411 }) 412 413 Describe("RunGetStep", func() { 414 415 var ( 416 ctx context.Context 417 owner db.ContainerOwner 418 containerSpec worker.ContainerSpec 419 workerSpec worker.WorkerSpec 420 metadata db.ContainerMetadata 421 fakeChosenWorker *workerfakes.FakeWorker 422 fakeStrategy *workerfakes.FakeContainerPlacementStrategy 423 fakeEventDelegate *runtimefakes.FakeStartingEventDelegate 424 fakeContainer *workerfakes.FakeContainer 425 fakeProcessSpec runtime.ProcessSpec 426 fakeResource *resourcefakes.FakeResource 427 fakeUsedResourceCache *dbfakes.FakeUsedResourceCache 428 429 err error 430 431 disasterErr error 432 433 result worker.GetResult 434 ) 435 436 BeforeEach(func() { 437 ctx, _ = context.WithCancel(context.Background()) 438 owner = new(dbfakes.FakeContainerOwner) 439 containerSpec = worker.ContainerSpec{ 440 TeamID: 123, 441 ImageSpec: worker.ImageSpec{ 442 ResourceType: "some-base-type", 443 Privileged: false, 444 }, 445 Dir: "some-artifact-root", 446 } 447 fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy) 448 workerSpec = worker.WorkerSpec{ 449 Platform: "some-platform", 450 Tags: []string{"step", "tags"}, 451 } 452 fakeChosenWorker = new(workerfakes.FakeWorker) 453 fakeEventDelegate = new(runtimefakes.FakeStartingEventDelegate) 454 455 fakeResource = new(resourcefakes.FakeResource) 456 fakeContainer = new(workerfakes.FakeContainer) 457 disasterErr = errors.New("oh no") 458 stdout := new(gbytes.Buffer) 459 stderr := new(gbytes.Buffer) 460 fakeProcessSpec = runtime.ProcessSpec{ 461 Path: "/opt/resource/out", 462 StdoutWriter: stdout, 463 StderrWriter: stderr, 464 } 465 fakeUsedResourceCache = new(dbfakes.FakeUsedResourceCache) 466 467 fakeChosenWorker = new(workerfakes.FakeWorker) 468 fakeChosenWorker.NameReturns("some-worker") 469 fakeChosenWorker.SatisfiesReturns(true) 470 fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil) 471 fakePool.FindOrChooseWorkerForContainerReturns(fakeChosenWorker, nil) 472 473 }) 474 475 JustBeforeEach(func() { 476 result, err = client.RunGetStep( 477 ctx, 478 logger, 479 owner, 480 containerSpec, 481 workerSpec, 482 fakeStrategy, 483 metadata, 484 fakeProcessSpec, 485 fakeEventDelegate, 486 fakeUsedResourceCache, 487 fakeResource, 488 ) 489 }) 490 491 It("finds/chooses a worker", func() { 492 Expect(err).ToNot(HaveOccurred()) 493 494 Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1)) 495 496 _, _, actualOwner, actualContainerSpec, actualWorkerSpec, actualStrategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0) 497 Expect(actualOwner).To(Equal(owner)) 498 Expect(actualContainerSpec).To(Equal(containerSpec)) 499 Expect(actualWorkerSpec).To(Equal(workerSpec)) 500 Expect(actualStrategy).To(Equal(fakeStrategy)) 501 }) 502 503 It("invokes the SelectedWorker Event on the delegate", func() { 504 Expect(fakeEventDelegate.SelectedWorkerCallCount()).Should((Equal(1))) 505 }) 506 507 Context("worker is chosen", func() { 508 BeforeEach(func() { 509 fakePool.FindOrChooseWorkerReturns(fakeChosenWorker, nil) 510 }) 511 512 It("invokes the Starting Event on the delegate", func() { 513 Expect(fakeEventDelegate.StartingCallCount()).Should((Equal(1))) 514 }) 515 516 It("calls Fetch on the worker", func() { 517 Expect(fakeChosenWorker.FetchCallCount()).To(Equal(1)) 518 _, _, actualMetadata, actualChosenWorker, actualContainerSpec, actualProcessSpec, actualResource, actualOwner, actualResourceCache, actualLockName := fakeChosenWorker.FetchArgsForCall(0) 519 520 Expect(actualMetadata).To(Equal(metadata)) 521 Expect(actualChosenWorker).To(Equal(fakeChosenWorker)) 522 Expect(actualContainerSpec).To(Equal(containerSpec)) 523 Expect(actualProcessSpec).To(Equal(fakeProcessSpec)) 524 Expect(actualResource).To(Equal(fakeResource)) 525 Expect(actualOwner).To(Equal(owner)) 526 Expect(actualResourceCache).To(Equal(fakeUsedResourceCache)) 527 // Computed SHA 528 Expect(actualLockName).To(Equal("18c3de3f8ea112ba52e01f279b6cc62335b4bec2f359b9be7636a5ad7bf98f8c")) 529 }) 530 531 Describe("with an image artifact", func() { 532 var fakeArtifact *runtimefakes.FakeArtifact 533 var fakeVolumeWorker *workerfakes.FakeWorker 534 var fakeVolume *workerfakes.FakeVolume 535 536 BeforeEach(func() { 537 fakeArtifact = new(runtimefakes.FakeArtifact) 538 539 containerSpec.ImageSpec = worker.ImageSpec{ 540 ImageArtifact: fakeArtifact, 541 } 542 543 fakeVolumeWorker = new(workerfakes.FakeWorker) 544 fakeProvider.FindWorkerForVolumeReturns(fakeVolumeWorker, true, nil) 545 546 fakeVolume = new(workerfakes.FakeVolume) 547 fakeVolumeWorker.LookupVolumeReturns(fakeVolume, true, nil) 548 }) 549 550 It("locates the volume and assigns it as an ImageArtifactSource", func() { 551 Expect(fakeChosenWorker.FetchCallCount()).To(Equal(1)) 552 _, _, _, _, actualContainerSpec, _, _, _, _, _ := fakeChosenWorker.FetchArgsForCall(0) 553 imageSpec := actualContainerSpec.ImageSpec 554 Expect(imageSpec.ImageArtifactSource).To(Equal(worker.NewStreamableArtifactSource(fakeArtifact, fakeVolume, fakeCompression, false, 15*time.Minute))) 555 }) 556 }) 557 }) 558 559 Context("Worker selection returns an error", func() { 560 BeforeEach(func() { 561 fakePool.FindOrChooseWorkerForContainerReturns(nil, disasterErr) 562 }) 563 564 It("Returns the error", func() { 565 Expect(err).To(HaveOccurred()) 566 Expect(err).To(Equal(disasterErr)) 567 568 Expect(result).To(Equal(worker.GetResult{})) 569 }) 570 }) 571 572 Context("Calling chosenWorker.Fetch", func() { 573 var ( 574 someError error 575 someGetResult worker.GetResult 576 fakeVolume *workerfakes.FakeVolume 577 ) 578 BeforeEach(func() { 579 someGetResult = worker.GetResult{ 580 ExitStatus: 0, 581 VersionResult: runtime.VersionResult{ 582 Version: atc.Version{"some-version": "some-value"}, 583 Metadata: []atc.MetadataField{{Name: "foo", Value: "bar"}}, 584 }, 585 } 586 someError = errors.New("some-foo-error") 587 fakeVolume = new(workerfakes.FakeVolume) 588 fakeChosenWorker.FetchReturns(someGetResult, fakeVolume, someError) 589 }) 590 It("returns getResult & err", func() { 591 Expect(result).To(Equal(someGetResult)) 592 Expect(err).To(Equal(someError)) 593 }) 594 }) 595 }) 596 597 Describe("RunTaskStep", func() { 598 var ( 599 status int 600 volumeMounts []worker.VolumeMount 601 inputSources []worker.InputSource 602 taskResult worker.TaskResult 603 err error 604 605 fakeWorker *workerfakes.FakeWorker 606 fakeContainerOwner db.ContainerOwner 607 fakeWorkerSpec worker.WorkerSpec 608 fakeContainerSpec worker.ContainerSpec 609 fakeStrategy *workerfakes.FakeContainerPlacementStrategy 610 fakeMetadata db.ContainerMetadata 611 fakeTaskProcessSpec runtime.ProcessSpec 612 fakeContainer *workerfakes.FakeContainer 613 fakeEventDelegate *runtimefakes.FakeStartingEventDelegate 614 615 ctx context.Context 616 cancel func() 617 ) 618 619 BeforeEach(func() { 620 cpu := uint64(1024) 621 memory := uint64(1024) 622 623 buildId := 1234 624 planId := atc.PlanID("42") 625 teamId := 123 626 fakeContainerOwner = db.NewBuildStepContainerOwner( 627 buildId, 628 planId, 629 teamId, 630 ) 631 fakeWorkerSpec = worker.WorkerSpec{ 632 Platform: "some-platform", 633 Tags: []string{"step", "tags"}, 634 } 635 fakeContainerSpec = worker.ContainerSpec{ 636 TeamID: 123, 637 ImageSpec: worker.ImageSpec{ 638 ImageArtifactSource: new(workerfakes.FakeStreamableArtifactSource), 639 Privileged: false, 640 }, 641 Limits: worker.ContainerLimits{ 642 CPU: &cpu, 643 Memory: &memory, 644 }, 645 Dir: "some-artifact-root", 646 Env: []string{"SECURE=secret-task-param"}, 647 ArtifactByPath: map[string]runtime.Artifact{}, 648 Inputs: inputSources, 649 Outputs: worker.OutputPaths{}, 650 } 651 fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy) 652 fakeMetadata = db.ContainerMetadata{ 653 WorkingDirectory: "some-artifact-root", 654 Type: db.ContainerTypeTask, 655 StepName: "some-step", 656 } 657 fakeTaskProcessSpec = runtime.ProcessSpec{ 658 Path: "/some/path", 659 Args: []string{"some", "args"}, 660 Dir: "/some/dir", 661 StdoutWriter: new(bytes.Buffer), 662 StderrWriter: new(bytes.Buffer), 663 } 664 fakeContainer = new(workerfakes.FakeContainer) 665 fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil) 666 667 fakeWorker = new(workerfakes.FakeWorker) 668 fakeWorker.NameReturns("some-worker") 669 fakeWorker.SatisfiesReturns(true) 670 fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil) 671 672 fakeWorker.IncreaseActiveTasksStub = func() error { 673 fakeWorker.ActiveTasksReturns(1, nil) 674 return nil 675 } 676 677 fakeWorker.DecreaseActiveTasksStub = func() error { 678 fakeWorker.ActiveTasksReturns(0, nil) 679 return nil 680 } 681 682 fakeEventDelegate = new(runtimefakes.FakeStartingEventDelegate) 683 684 fakeLockFactory = new(lockfakes.FakeLockFactory) 685 fakeLock = new(lockfakes.FakeLock) 686 fakeLockFactory.AcquireReturns(fakeLock, true, nil) 687 688 fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil) 689 ctx, cancel = context.WithCancel(context.Background()) 690 }) 691 692 JustBeforeEach(func() { 693 taskResult, err = client.RunTaskStep( 694 ctx, 695 logger, 696 fakeContainerOwner, 697 fakeContainerSpec, 698 fakeWorkerSpec, 699 fakeStrategy, 700 fakeMetadata, 701 fakeTaskProcessSpec, 702 fakeEventDelegate, 703 fakeLockFactory, 704 ) 705 status = taskResult.ExitStatus 706 volumeMounts = taskResult.VolumeMounts 707 }) 708 709 Context("choosing a worker", func() { 710 BeforeEach(func() { 711 // later fakes are uninitialized 712 fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "3"}, nil) 713 }) 714 715 It("chooses a worker", func() { 716 Expect(err).ToNot(HaveOccurred()) 717 Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1)) 718 _, actualWorkerName := fakeEventDelegate.SelectedWorkerArgsForCall(0) 719 Expect(actualWorkerName).To(Equal(fakeWorker.Name())) 720 }) 721 722 It("invokes the SelectedWorker Event on the delegate", func() { 723 Expect(fakeEventDelegate.SelectedWorkerCallCount()).Should((Equal(1))) 724 }) 725 726 Context("when 'limit-active-tasks' strategy is chosen", func() { 727 BeforeEach(func() { 728 fakeStrategy.ModifiesActiveTasksReturns(true) 729 }) 730 Context("when a worker is found", func() { 731 BeforeEach(func() { 732 fakeWorker.NameReturns("some-worker") 733 fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil) 734 735 fakeContainer := new(workerfakes.FakeContainer) 736 fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil) 737 fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil) 738 }) 739 It("increase the active tasks on the worker", func() { 740 Expect(fakeWorker.IncreaseActiveTasksCallCount()).To(Equal(1)) 741 }) 742 743 Context("when the container is already present on the worker", func() { 744 BeforeEach(func() { 745 fakePool.ContainerInWorkerReturns(true, nil) 746 }) 747 It("does not increase the active tasks on the worker", func() { 748 Expect(fakeWorker.IncreaseActiveTasksCallCount()).To(Equal(0)) 749 }) 750 }) 751 }) 752 753 Context("when the task is aborted waiting for an available worker", func() { 754 BeforeEach(func() { 755 cancel() 756 }) 757 It("exits releasing the lock", func() { 758 Expect(err.Error()).To(ContainSubstring(context.Canceled.Error())) 759 Expect(fakeLock.ReleaseCallCount()).To(Equal(fakeLockFactory.AcquireCallCount())) 760 }) 761 }) 762 763 Context("when a container in worker returns an error", func() { 764 BeforeEach(func() { 765 fakePool.ContainerInWorkerReturns(false, errors.New("nope")) 766 }) 767 It("release the task-step lock every time it acquires it", func() { 768 Expect(fakeLock.ReleaseCallCount()).To(Equal(fakeLockFactory.AcquireCallCount())) 769 }) 770 }) 771 }) 772 773 Context("when finding or choosing the worker errors", func() { 774 workerDisaster := errors.New("worker selection errored") 775 776 BeforeEach(func() { 777 fakePool.FindOrChooseWorkerForContainerReturns(nil, workerDisaster) 778 }) 779 780 It("returns the error", func() { 781 Expect(err).To(Equal(workerDisaster)) 782 }) 783 784 It("should not invokes the SelectedWorker Event on the delegate", func() { 785 Expect(fakeEventDelegate.SelectedWorkerCallCount()).Should((Equal(0))) 786 }) 787 }) 788 789 }) 790 791 It("finds or creates a container", func() { 792 Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1)) 793 _, _, owner, createdMetadata, containerSpec := fakeWorker.FindOrCreateContainerArgsForCall(0) 794 Expect(containerSpec.Inputs).To(Equal(fakeContainerSpec.Inputs)) 795 Expect(containerSpec).To(Equal(fakeContainerSpec)) 796 Expect(owner).To(Equal(fakeContainerOwner)) 797 Expect(createdMetadata).To(Equal(db.ContainerMetadata{ 798 WorkingDirectory: "some-artifact-root", 799 Type: db.ContainerTypeTask, 800 StepName: "some-step", 801 })) 802 }) 803 804 Describe("with an image artifact", func() { 805 var fakeArtifact *runtimefakes.FakeArtifact 806 var fakeVolumeWorker *workerfakes.FakeWorker 807 var fakeVolume *workerfakes.FakeVolume 808 809 BeforeEach(func() { 810 fakeArtifact = new(runtimefakes.FakeArtifact) 811 812 fakeContainerSpec.ImageSpec = worker.ImageSpec{ 813 ImageArtifact: fakeArtifact, 814 } 815 816 fakeVolumeWorker = new(workerfakes.FakeWorker) 817 fakeProvider.FindWorkerForVolumeReturns(fakeVolumeWorker, true, nil) 818 819 fakeVolume = new(workerfakes.FakeVolume) 820 fakeVolumeWorker.LookupVolumeReturns(fakeVolume, true, nil) 821 }) 822 823 It("locates the volume and assigns it as an ImageArtifactSource", func() { 824 _, _, _, _, containerSpec := fakeWorker.FindOrCreateContainerArgsForCall(0) 825 imageSpec := containerSpec.ImageSpec 826 Expect(imageSpec.ImageArtifactSource).To(Equal(worker.NewStreamableArtifactSource(fakeArtifact, fakeVolume, fakeCompression, false, 15*time.Minute))) 827 }) 828 }) 829 830 Context("found a container that has already exited", func() { 831 BeforeEach(func() { 832 fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "8"}, nil) 833 }) 834 835 It("does not attach to any process", func() { 836 Expect(fakeContainer.AttachCallCount()).To(BeZero()) 837 }) 838 839 It("returns result of container process", func() { 840 Expect(err).ToNot(HaveOccurred()) 841 Expect(status).To(Equal(8)) 842 }) 843 844 Context("when 'limit-active-tasks' strategy is chosen", func() { 845 BeforeEach(func() { 846 fakeStrategy.ModifiesActiveTasksReturns(true) 847 }) 848 849 It("decrements the active tasks counter on the worker", func() { 850 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 851 }) 852 }) 853 854 Context("when volumes are configured and present on the container", func() { 855 var ( 856 fakeMountPath1 = "some-artifact-root/some-output-configured-path/" 857 fakeMountPath2 = "some-artifact-root/some-other-output/" 858 fakeMountPath3 = "some-artifact-root/some-output-configured-path-with-trailing-slash/" 859 860 fakeVolume1 *workerfakes.FakeVolume 861 fakeVolume2 *workerfakes.FakeVolume 862 fakeVolume3 *workerfakes.FakeVolume 863 ) 864 865 BeforeEach(func() { 866 fakeVolume1 = new(workerfakes.FakeVolume) 867 fakeVolume1.HandleReturns("some-handle-1") 868 fakeVolume2 = new(workerfakes.FakeVolume) 869 fakeVolume2.HandleReturns("some-handle-2") 870 fakeVolume3 = new(workerfakes.FakeVolume) 871 fakeVolume3.HandleReturns("some-handle-3") 872 873 fakeContainer.VolumeMountsReturns([]worker.VolumeMount{ 874 { 875 Volume: fakeVolume1, 876 MountPath: fakeMountPath1, 877 }, 878 { 879 Volume: fakeVolume2, 880 MountPath: fakeMountPath2, 881 }, 882 { 883 Volume: fakeVolume3, 884 MountPath: fakeMountPath3, 885 }, 886 }) 887 }) 888 889 It("returns all the volume mounts", func() { 890 Expect(volumeMounts).To(ConsistOf( 891 worker.VolumeMount{ 892 Volume: fakeVolume1, 893 MountPath: fakeMountPath1, 894 }, 895 worker.VolumeMount{ 896 Volume: fakeVolume2, 897 MountPath: fakeMountPath2, 898 }, 899 worker.VolumeMount{ 900 Volume: fakeVolume3, 901 MountPath: fakeMountPath3, 902 }, 903 )) 904 }) 905 }) 906 }) 907 908 Context("container has not already exited", func() { 909 var ( 910 fakeProcess *gardenfakes.FakeProcess 911 fakeProcessExitCode int 912 913 fakeMountPath1 = "some-artifact-root/some-output-configured-path/" 914 fakeMountPath2 = "some-artifact-root/some-other-output/" 915 fakeMountPath3 = "some-artifact-root/some-output-configured-path-with-trailing-slash/" 916 917 fakeVolume1 *workerfakes.FakeVolume 918 fakeVolume2 *workerfakes.FakeVolume 919 fakeVolume3 *workerfakes.FakeVolume 920 921 stdoutBuf *gbytes.Buffer 922 stderrBuf *gbytes.Buffer 923 ) 924 925 BeforeEach(func() { 926 fakeProcess = new(gardenfakes.FakeProcess) 927 fakeContainer.PropertiesReturns(garden.Properties{}, nil) 928 929 // for testing volume mounts being returned 930 fakeVolume1 = new(workerfakes.FakeVolume) 931 fakeVolume1.HandleReturns("some-handle-1") 932 fakeVolume2 = new(workerfakes.FakeVolume) 933 fakeVolume2.HandleReturns("some-handle-2") 934 fakeVolume3 = new(workerfakes.FakeVolume) 935 fakeVolume3.HandleReturns("some-handle-3") 936 937 fakeContainer.VolumeMountsReturns([]worker.VolumeMount{ 938 { 939 Volume: fakeVolume1, 940 MountPath: fakeMountPath1, 941 }, 942 { 943 Volume: fakeVolume2, 944 MountPath: fakeMountPath2, 945 }, 946 { 947 Volume: fakeVolume3, 948 MountPath: fakeMountPath3, 949 }, 950 }) 951 }) 952 953 Context("found container that is already running", func() { 954 BeforeEach(func() { 955 fakeContainer.AttachReturns(fakeProcess, nil) 956 957 stdoutBuf = new(gbytes.Buffer) 958 stderrBuf = new(gbytes.Buffer) 959 fakeTaskProcessSpec = runtime.ProcessSpec{ 960 StdoutWriter: stdoutBuf, 961 StderrWriter: stderrBuf, 962 } 963 }) 964 965 It("does not create a new container", func() { 966 Expect(fakeContainer.RunCallCount()).To(BeZero()) 967 }) 968 969 It("attaches to the running process", func() { 970 Expect(err).ToNot(HaveOccurred()) 971 Expect(fakeContainer.AttachCallCount()).To(Equal(1)) 972 Expect(fakeContainer.RunCallCount()).To(Equal(0)) 973 _, _, actualProcessIO := fakeContainer.AttachArgsForCall(0) 974 Expect(actualProcessIO.Stdout).To(Equal(stdoutBuf)) 975 Expect(actualProcessIO.Stderr).To(Equal(stderrBuf)) 976 }) 977 978 Context("when the process is interrupted", func() { 979 var stopped chan struct{} 980 BeforeEach(func() { 981 stopped = make(chan struct{}) 982 983 fakeProcess.WaitStub = func() (int, error) { 984 defer GinkgoRecover() 985 986 <-stopped 987 return 128 + 15, nil 988 } 989 990 fakeContainer.StopStub = func(bool) error { 991 close(stopped) 992 return nil 993 } 994 995 cancel() 996 }) 997 998 It("stops the container", func() { 999 Expect(fakeContainer.StopCallCount()).To(Equal(1)) 1000 Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse()) 1001 Expect(err).To(Equal(context.Canceled)) 1002 }) 1003 1004 Context("when container.stop returns an error", func() { 1005 var disaster error 1006 1007 BeforeEach(func() { 1008 disaster = errors.New("gotta get away") 1009 1010 fakeContainer.StopStub = func(bool) error { 1011 close(stopped) 1012 return disaster 1013 } 1014 }) 1015 1016 It("doesn't return the error", func() { 1017 Expect(err).To(Equal(context.Canceled)) 1018 }) 1019 }) 1020 1021 Context("when 'limit-active-tasks' strategy is chosen", func() { 1022 BeforeEach(func() { 1023 fakeStrategy.ModifiesActiveTasksReturns(true) 1024 }) 1025 1026 It("decrements the active tasks counter on the worker", func() { 1027 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1028 }) 1029 }) 1030 }) 1031 1032 Context("when the process exits successfully", func() { 1033 BeforeEach(func() { 1034 fakeProcessExitCode = 0 1035 fakeProcess.WaitReturns(fakeProcessExitCode, nil) 1036 }) 1037 It("returns a successful result", func() { 1038 Expect(status).To(BeZero()) 1039 Expect(err).ToNot(HaveOccurred()) 1040 }) 1041 1042 It("returns all the volume mounts", func() { 1043 Expect(volumeMounts).To(ConsistOf( 1044 worker.VolumeMount{ 1045 Volume: fakeVolume1, 1046 MountPath: fakeMountPath1, 1047 }, 1048 worker.VolumeMount{ 1049 Volume: fakeVolume2, 1050 MountPath: fakeMountPath2, 1051 }, 1052 worker.VolumeMount{ 1053 Volume: fakeVolume3, 1054 MountPath: fakeMountPath3, 1055 }, 1056 )) 1057 }) 1058 1059 Context("when 'limit-active-tasks' strategy is chosen", func() { 1060 BeforeEach(func() { 1061 fakeStrategy.ModifiesActiveTasksReturns(true) 1062 }) 1063 1064 It("decrements the active tasks counter on the worker", func() { 1065 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1066 }) 1067 }) 1068 }) 1069 1070 Context("when the process exits with an error", func() { 1071 disaster := errors.New("process failed") 1072 BeforeEach(func() { 1073 fakeProcessExitCode = 128 + 15 1074 fakeProcess.WaitReturns(fakeProcessExitCode, disaster) 1075 }) 1076 It("returns an unsuccessful result", func() { 1077 Expect(status).To(Equal(fakeProcessExitCode)) 1078 Expect(err).To(HaveOccurred()) 1079 Expect(err).To(Equal(disaster)) 1080 }) 1081 1082 It("returns no volume mounts", func() { 1083 Expect(volumeMounts).To(BeEmpty()) 1084 }) 1085 1086 Context("when 'limit-active-tasks' strategy is chosen", func() { 1087 BeforeEach(func() { 1088 fakeStrategy.ModifiesActiveTasksReturns(true) 1089 }) 1090 1091 It("decrements the active tasks counter on the worker", func() { 1092 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1093 }) 1094 }) 1095 }) 1096 }) 1097 1098 Context("created a new container", func() { 1099 BeforeEach(func() { 1100 fakeContainer.AttachReturns(nil, errors.New("container not running")) 1101 fakeContainer.RunReturns(fakeProcess, nil) 1102 1103 stdoutBuf = new(gbytes.Buffer) 1104 stderrBuf = new(gbytes.Buffer) 1105 fakeTaskProcessSpec = runtime.ProcessSpec{ 1106 StdoutWriter: stdoutBuf, 1107 StderrWriter: stderrBuf, 1108 } 1109 }) 1110 1111 It("runs a new process in the container", func() { 1112 Eventually(fakeContainer.RunCallCount()).Should(Equal(1)) 1113 1114 _, gardenProcessSpec, actualProcessIO := fakeContainer.RunArgsForCall(0) 1115 Expect(gardenProcessSpec.ID).To(Equal("task")) 1116 Expect(gardenProcessSpec.Path).To(Equal(fakeTaskProcessSpec.Path)) 1117 Expect(gardenProcessSpec.Args).To(ConsistOf(fakeTaskProcessSpec.Args)) 1118 Expect(gardenProcessSpec.Dir).To(Equal(path.Join(fakeMetadata.WorkingDirectory, fakeTaskProcessSpec.Dir))) 1119 Expect(gardenProcessSpec.TTY).To(Equal(&garden.TTYSpec{WindowSize: &garden.WindowSize{Columns: 500, Rows: 500}})) 1120 Expect(actualProcessIO.Stdout).To(Equal(stdoutBuf)) 1121 Expect(actualProcessIO.Stderr).To(Equal(stderrBuf)) 1122 }) 1123 1124 It("invokes the Starting Event on the delegate", func() { 1125 Expect(fakeEventDelegate.StartingCallCount()).Should((Equal(1))) 1126 }) 1127 1128 Context("when the process is interrupted", func() { 1129 var stopped chan struct{} 1130 BeforeEach(func() { 1131 stopped = make(chan struct{}) 1132 1133 fakeProcess.WaitStub = func() (int, error) { 1134 defer GinkgoRecover() 1135 1136 <-stopped 1137 return 128 + 15, nil // wat? 1138 } 1139 1140 fakeContainer.StopStub = func(bool) error { 1141 close(stopped) 1142 return nil 1143 } 1144 1145 cancel() 1146 }) 1147 1148 It("stops the container", func() { 1149 Expect(fakeContainer.StopCallCount()).To(Equal(1)) 1150 Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse()) 1151 Expect(err).To(Equal(context.Canceled)) 1152 }) 1153 1154 Context("when container.stop returns an error", func() { 1155 var disaster error 1156 1157 BeforeEach(func() { 1158 disaster = errors.New("gotta get away") 1159 1160 fakeContainer.StopStub = func(bool) error { 1161 close(stopped) 1162 return disaster 1163 } 1164 }) 1165 1166 It("doesn't return the error", func() { 1167 Expect(err).To(Equal(context.Canceled)) 1168 }) 1169 }) 1170 }) 1171 1172 Context("when the process exits successfully", func() { 1173 It("returns a successful result", func() { 1174 Expect(status).To(BeZero()) 1175 Expect(err).ToNot(HaveOccurred()) 1176 }) 1177 1178 It("saves the exit status property", func() { 1179 Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1)) 1180 1181 name, value := fakeContainer.SetPropertyArgsForCall(0) 1182 Expect(name).To(Equal("concourse:exit-status")) 1183 Expect(value).To(Equal("0")) 1184 }) 1185 1186 Context("when saving the exit status succeeds", func() { 1187 BeforeEach(func() { 1188 fakeContainer.SetPropertyReturns(nil) 1189 }) 1190 1191 It("returns successfully", func() { 1192 Expect(err).ToNot(HaveOccurred()) 1193 }) 1194 }) 1195 1196 Context("when saving the exit status fails", func() { 1197 disaster := errors.New("nope") 1198 1199 BeforeEach(func() { 1200 fakeContainer.SetPropertyStub = func(name string, value string) error { 1201 defer GinkgoRecover() 1202 1203 if name == "concourse:exit-status" { 1204 return disaster 1205 } 1206 1207 return nil 1208 } 1209 }) 1210 1211 It("returns the error", func() { 1212 Expect(err).To(Equal(disaster)) 1213 }) 1214 }) 1215 1216 Context("when volumes are configured and present on the container", func() { 1217 var ( 1218 fakeMountPath1 = "some-artifact-root/some-output-configured-path/" 1219 fakeMountPath2 = "some-artifact-root/some-other-output/" 1220 fakeMountPath3 = "some-artifact-root/some-output-configured-path-with-trailing-slash/" 1221 1222 fakeVolume1 *workerfakes.FakeVolume 1223 fakeVolume2 *workerfakes.FakeVolume 1224 fakeVolume3 *workerfakes.FakeVolume 1225 ) 1226 1227 BeforeEach(func() { 1228 fakeVolume1 = new(workerfakes.FakeVolume) 1229 fakeVolume1.HandleReturns("some-handle-1") 1230 fakeVolume2 = new(workerfakes.FakeVolume) 1231 fakeVolume2.HandleReturns("some-handle-2") 1232 fakeVolume3 = new(workerfakes.FakeVolume) 1233 fakeVolume3.HandleReturns("some-handle-3") 1234 1235 fakeContainer.VolumeMountsReturns([]worker.VolumeMount{ 1236 { 1237 Volume: fakeVolume1, 1238 MountPath: fakeMountPath1, 1239 }, 1240 { 1241 Volume: fakeVolume2, 1242 MountPath: fakeMountPath2, 1243 }, 1244 { 1245 Volume: fakeVolume3, 1246 MountPath: fakeMountPath3, 1247 }, 1248 }) 1249 }) 1250 1251 It("returns all the volume mounts", func() { 1252 Expect(volumeMounts).To(ConsistOf( 1253 worker.VolumeMount{ 1254 Volume: fakeVolume1, 1255 MountPath: fakeMountPath1, 1256 }, 1257 worker.VolumeMount{ 1258 Volume: fakeVolume2, 1259 MountPath: fakeMountPath2, 1260 }, 1261 worker.VolumeMount{ 1262 Volume: fakeVolume3, 1263 MountPath: fakeMountPath3, 1264 }, 1265 )) 1266 }) 1267 1268 }) 1269 1270 Context("when 'limit-active-tasks' strategy is chosen", func() { 1271 BeforeEach(func() { 1272 fakeStrategy.ModifiesActiveTasksReturns(true) 1273 }) 1274 1275 It("decrements the active tasks counter on the worker", func() { 1276 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1277 }) 1278 }) 1279 }) 1280 1281 Context("when the process exits on failure", func() { 1282 BeforeEach(func() { 1283 fakeProcessExitCode = 128 + 15 1284 fakeProcess.WaitReturns(fakeProcessExitCode, nil) 1285 }) 1286 It("returns an unsuccessful result", func() { 1287 Expect(status).To(Equal(fakeProcessExitCode)) 1288 Expect(err).ToNot(HaveOccurred()) 1289 }) 1290 1291 It("saves the exit status property", func() { 1292 Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1)) 1293 1294 name, value := fakeContainer.SetPropertyArgsForCall(0) 1295 Expect(name).To(Equal("concourse:exit-status")) 1296 Expect(value).To(Equal(fmt.Sprint(fakeProcessExitCode))) 1297 }) 1298 1299 Context("when saving the exit status succeeds", func() { 1300 BeforeEach(func() { 1301 fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil) 1302 }) 1303 1304 It("returns successfully", func() { 1305 Expect(err).ToNot(HaveOccurred()) 1306 }) 1307 }) 1308 1309 Context("when saving the exit status fails", func() { 1310 disaster := errors.New("nope") 1311 1312 BeforeEach(func() { 1313 fakeContainer.SetPropertyStub = func(name string, value string) error { 1314 defer GinkgoRecover() 1315 1316 if name == "concourse:exit-status" { 1317 return disaster 1318 } 1319 1320 return nil 1321 } 1322 }) 1323 1324 It("returns the error", func() { 1325 Expect(err).To(Equal(disaster)) 1326 }) 1327 }) 1328 1329 It("returns all the volume mounts", func() { 1330 Expect(volumeMounts).To(ConsistOf( 1331 worker.VolumeMount{ 1332 Volume: fakeVolume1, 1333 MountPath: fakeMountPath1, 1334 }, 1335 worker.VolumeMount{ 1336 Volume: fakeVolume2, 1337 MountPath: fakeMountPath2, 1338 }, 1339 worker.VolumeMount{ 1340 Volume: fakeVolume3, 1341 MountPath: fakeMountPath3, 1342 }, 1343 )) 1344 }) 1345 1346 Context("when 'limit-active-tasks' strategy is chosen", func() { 1347 BeforeEach(func() { 1348 fakeStrategy.ModifiesActiveTasksReturns(true) 1349 }) 1350 1351 It("decrements the active tasks counter on the worker", func() { 1352 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1353 }) 1354 }) 1355 }) 1356 1357 Context("when running the container fails with an error", func() { 1358 disaster := errors.New("nope") 1359 1360 BeforeEach(func() { 1361 fakeContainer.RunReturns(nil, disaster) 1362 }) 1363 1364 It("returns the error", func() { 1365 Expect(err).To(Equal(disaster)) 1366 }) 1367 1368 Context("when 'limit-active-tasks' strategy is chosen", func() { 1369 BeforeEach(func() { 1370 fakeStrategy.ModifiesActiveTasksReturns(true) 1371 }) 1372 1373 It("decrements the active tasks counter on the worker", func() { 1374 Expect(fakeWorker.ActiveTasks()).To(Equal(0)) 1375 }) 1376 }) 1377 }) 1378 }) 1379 }) 1380 }) 1381 1382 Describe("RunPutStep", func() { 1383 1384 var ( 1385 ctx context.Context 1386 owner db.ContainerOwner 1387 containerSpec worker.ContainerSpec 1388 workerSpec worker.WorkerSpec 1389 metadata db.ContainerMetadata 1390 fakeChosenWorker *workerfakes.FakeWorker 1391 fakeStrategy *workerfakes.FakeContainerPlacementStrategy 1392 fakeEventDelegate *runtimefakes.FakeStartingEventDelegate 1393 fakeContainer *workerfakes.FakeContainer 1394 fakeProcessSpec runtime.ProcessSpec 1395 fakeResource *resourcefakes.FakeResource 1396 1397 versionResult runtime.VersionResult 1398 status int 1399 err error 1400 result worker.PutResult 1401 1402 disasterErr error 1403 ) 1404 1405 BeforeEach(func() { 1406 ctx = context.Background() 1407 owner = new(dbfakes.FakeContainerOwner) 1408 containerSpec = worker.ContainerSpec{ 1409 TeamID: 123, 1410 ImageSpec: worker.ImageSpec{ 1411 ResourceType: "some-base-type", 1412 Privileged: false, 1413 }, 1414 Dir: "some-artifact-root", 1415 } 1416 fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy) 1417 workerSpec = worker.WorkerSpec{ 1418 Platform: "some-platform", 1419 Tags: []string{"step", "tags"}, 1420 } 1421 fakeChosenWorker = new(workerfakes.FakeWorker) 1422 fakeEventDelegate = new(runtimefakes.FakeStartingEventDelegate) 1423 1424 fakeContainer = new(workerfakes.FakeContainer) 1425 disasterErr = errors.New("oh no") 1426 stdout := new(gbytes.Buffer) 1427 stderr := new(gbytes.Buffer) 1428 fakeProcessSpec = runtime.ProcessSpec{ 1429 Path: "/opt/resource/out", 1430 StdoutWriter: stdout, 1431 StderrWriter: stderr, 1432 } 1433 fakeResource = new(resourcefakes.FakeResource) 1434 1435 fakeChosenWorker = new(workerfakes.FakeWorker) 1436 fakeChosenWorker.NameReturns("some-worker") 1437 fakeChosenWorker.SatisfiesReturns(true) 1438 fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil) 1439 fakePool.FindOrChooseWorkerForContainerReturns(fakeChosenWorker, nil) 1440 1441 }) 1442 1443 JustBeforeEach(func() { 1444 result, err = client.RunPutStep( 1445 ctx, 1446 logger, 1447 owner, 1448 containerSpec, 1449 workerSpec, 1450 fakeStrategy, 1451 metadata, 1452 fakeProcessSpec, 1453 fakeEventDelegate, 1454 fakeResource, 1455 ) 1456 versionResult = result.VersionResult 1457 status = result.ExitStatus 1458 }) 1459 1460 It("finds/chooses a worker", func() { 1461 Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1)) 1462 1463 _, _, actualOwner, actualContainerSpec, actualWorkerSpec, strategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0) 1464 Expect(actualOwner).To(Equal(owner)) 1465 Expect(actualContainerSpec).To(Equal(containerSpec)) 1466 Expect(actualWorkerSpec).To(Equal(workerSpec)) 1467 Expect(strategy).To(Equal(fakeStrategy)) 1468 }) 1469 1470 It("invokes the SelectedWorker Event on the delegate", func() { 1471 Expect(fakeEventDelegate.SelectedWorkerCallCount()).Should((Equal(1))) 1472 _, actualWorkerName := fakeEventDelegate.SelectedWorkerArgsForCall(0) 1473 Expect(actualWorkerName).To(Equal(fakeChosenWorker.Name())) 1474 }) 1475 1476 Context("worker is chosen", func() { 1477 BeforeEach(func() { 1478 fakePool.FindOrChooseWorkerReturns(fakeChosenWorker, nil) 1479 }) 1480 It("finds or creates a put container on that worker", func() { 1481 Expect(fakeChosenWorker.FindOrCreateContainerCallCount()).To(Equal(1)) 1482 _, _, actualOwner, actualMetadata, actualContainerSpec := fakeChosenWorker.FindOrCreateContainerArgsForCall(0) 1483 1484 Expect(actualContainerSpec).To(Equal(containerSpec)) 1485 Expect(actualOwner).To(Equal(owner)) 1486 Expect(actualMetadata).To(Equal(metadata)) 1487 }) 1488 1489 Describe("with an image artifact", func() { 1490 var fakeArtifact *runtimefakes.FakeArtifact 1491 var fakeVolumeWorker *workerfakes.FakeWorker 1492 var fakeVolume *workerfakes.FakeVolume 1493 1494 BeforeEach(func() { 1495 fakeArtifact = new(runtimefakes.FakeArtifact) 1496 1497 containerSpec.ImageSpec = worker.ImageSpec{ 1498 ImageArtifact: fakeArtifact, 1499 } 1500 1501 fakeVolumeWorker = new(workerfakes.FakeWorker) 1502 fakeProvider.FindWorkerForVolumeReturns(fakeVolumeWorker, true, nil) 1503 1504 fakeVolume = new(workerfakes.FakeVolume) 1505 fakeVolumeWorker.LookupVolumeReturns(fakeVolume, true, nil) 1506 }) 1507 1508 It("locates the volume and assigns it as an ImageArtifactSource", func() { 1509 _, _, _, _, containerSpec := fakeChosenWorker.FindOrCreateContainerArgsForCall(0) 1510 imageSpec := containerSpec.ImageSpec 1511 Expect(imageSpec.ImageArtifactSource).To(Equal(worker.NewStreamableArtifactSource(fakeArtifact, fakeVolume, fakeCompression, false, 15*time.Minute))) 1512 }) 1513 }) 1514 }) 1515 1516 Context("worker selection returns an error", func() { 1517 BeforeEach(func() { 1518 fakePool.FindOrChooseWorkerForContainerReturns(nil, disasterErr) 1519 }) 1520 1521 It("returns the error", func() { 1522 Expect(err).To(HaveOccurred()) 1523 Expect(err).To(Equal(disasterErr)) 1524 Expect(versionResult).To(Equal(runtime.VersionResult{})) 1525 }) 1526 }) 1527 1528 Context("found a container that has run resource.Put and exited", func() { 1529 BeforeEach(func() { 1530 fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil) 1531 fakeContainer.PropertyStub = func(prop string) (result string, err error) { 1532 if prop == "concourse:exit-status" { 1533 return "8", nil 1534 } 1535 return "", errors.New("unhandled property") 1536 } 1537 }) 1538 1539 It("does not invoke resource.Put", func() { 1540 Expect(fakeResource.PutCallCount()).To(Equal(0)) 1541 }) 1542 1543 It("returns result of container process", func() { 1544 Expect(err).ToNot(HaveOccurred()) 1545 Expect(status).To(Equal(8)) 1546 }) 1547 }) 1548 1549 Context("calling resource.Put", func() { 1550 BeforeEach(func() { 1551 fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil) 1552 fakeContainer.PropertyReturns("0", fmt.Errorf("property not found")) 1553 }) 1554 1555 It("invokes the Starting Event on the delegate", func() { 1556 Expect(fakeEventDelegate.StartingCallCount()).Should((Equal(1))) 1557 }) 1558 1559 It("calls resource.Put with the correct ctx, processSpec and container", func() { 1560 actualCtx, actualProcessSpec, actualContainer := fakeResource.PutArgsForCall(0) 1561 Expect(actualCtx).To(Equal(ctx)) 1562 Expect(actualProcessSpec).To(Equal(fakeProcessSpec)) 1563 Expect(actualContainer).To(Equal(fakeContainer)) 1564 }) 1565 1566 Context("when PUT returns an error", func() { 1567 1568 Context("when the error is ErrResourceScriptFailed", func() { 1569 var ( 1570 scriptFailErr runtime.ErrResourceScriptFailed 1571 ) 1572 BeforeEach(func() { 1573 scriptFailErr = runtime.ErrResourceScriptFailed{ 1574 ExitStatus: 10, 1575 } 1576 1577 fakeResource.PutReturns( 1578 runtime.VersionResult{}, 1579 scriptFailErr, 1580 ) 1581 }) 1582 1583 It("returns a PutResult with the exit status from ErrResourceScriptFailed", func() { 1584 Expect(status).To(Equal(10)) 1585 Expect(err).To(BeNil()) 1586 }) 1587 }) 1588 1589 Context("when the error is NOT ErrResourceScriptFailed", func() { 1590 BeforeEach(func() { 1591 fakeResource.PutReturns( 1592 runtime.VersionResult{}, 1593 disasterErr, 1594 ) 1595 }) 1596 1597 It("returns an error", func() { 1598 Expect(err).To(Equal(disasterErr)) 1599 }) 1600 1601 }) 1602 }) 1603 1604 Context("when PUT succeeds", func() { 1605 var expectedVersionResult runtime.VersionResult 1606 BeforeEach(func() { 1607 expectedVersionResult = runtime.VersionResult{ 1608 Version: atc.Version(map[string]string{"foo": "bar"}), 1609 Metadata: nil, 1610 } 1611 1612 fakeResource.PutReturns(expectedVersionResult, nil) 1613 }) 1614 It("returns the correct VersionResult and ExitStatus", func() { 1615 Expect(err).To(BeNil()) 1616 Expect(status).To(Equal(0)) 1617 Expect(versionResult).To(Equal(expectedVersionResult)) 1618 }) 1619 }) 1620 }) 1621 1622 Context("worker.FindOrCreateContainer errored", func() { 1623 BeforeEach(func() { 1624 fakeChosenWorker.FindOrCreateContainerReturns(nil, disasterErr) 1625 }) 1626 1627 It("returns the error immediately", func() { 1628 Expect(err).To(HaveOccurred()) 1629 Expect(err).To(Equal(disasterErr)) 1630 Expect(versionResult).To(Equal(runtime.VersionResult{})) 1631 }) 1632 }) 1633 }) 1634 })