github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/worker_test.go (about) 1 package worker_test 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 10 "code.cloudfoundry.org/garden" 11 "code.cloudfoundry.org/lager" 12 "github.com/concourse/baggageclaim" 13 "github.com/concourse/baggageclaim/baggageclaimfakes" 14 15 "code.cloudfoundry.org/lager/lagertest" 16 "github.com/pf-qiu/concourse/v6/atc" 17 "github.com/pf-qiu/concourse/v6/atc/db" 18 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 19 . "github.com/pf-qiu/concourse/v6/atc/worker" 20 "github.com/pf-qiu/concourse/v6/atc/worker/gclient/gclientfakes" 21 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 22 "github.com/cppforlife/go-semi-semantic/version" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 ) 26 27 var _ = Describe("Worker", func() { 28 var ( 29 logger *lagertest.TestLogger 30 fakeVolumeClient *workerfakes.FakeVolumeClient 31 activeContainers int 32 resourceTypes []atc.WorkerResourceType 33 platform string 34 tags atc.Tags 35 teamID int 36 ephemeral bool 37 workerName string 38 gardenWorker Worker 39 workerVersion string 40 fakeGardenClient *gclientfakes.FakeClient 41 fakeImageFactory *workerfakes.FakeImageFactory 42 fakeImage *workerfakes.FakeImage 43 fakeDBWorker *dbfakes.FakeWorker 44 fakeDBVolumeRepository *dbfakes.FakeVolumeRepository 45 fakeResourceCacheFactory *dbfakes.FakeResourceCacheFactory 46 fakeDBTeamFactory *dbfakes.FakeTeamFactory 47 fakeDBTeam *dbfakes.FakeTeam 48 fakeCreatingContainer *dbfakes.FakeCreatingContainer 49 fakeCreatedContainer *dbfakes.FakeCreatedContainer 50 fakeGardenContainer *gclientfakes.FakeContainer 51 fakeBaggageclaimClient *baggageclaimfakes.FakeClient 52 fakeFetcher *workerfakes.FakeFetcher 53 54 fakeLocalInput *workerfakes.FakeInputSource 55 fakeRemoteInput *workerfakes.FakeInputSource 56 fakeRemoteInputAS *workerfakes.FakeStreamableArtifactSource 57 58 fakeBindMount *workerfakes.FakeBindMountSource 59 60 fakeRemoteInputContainerVolume *workerfakes.FakeVolume 61 fakeLocalVolume *workerfakes.FakeVolume 62 fakeOutputVolume *workerfakes.FakeVolume 63 fakeLocalCOWVolume *workerfakes.FakeVolume 64 65 ctx context.Context 66 fakeContainerOwner *dbfakes.FakeContainerOwner 67 containerMetadata db.ContainerMetadata 68 69 stubbedVolumes map[string]*workerfakes.FakeVolume 70 volumeSpecs map[string]VolumeSpec 71 72 findOrCreateErr error 73 findOrCreateContainer Container 74 ) 75 76 BeforeEach(func() { 77 logger = lagertest.NewTestLogger("test") 78 fakeVolumeClient = new(workerfakes.FakeVolumeClient) 79 activeContainers = 42 80 resourceTypes = []atc.WorkerResourceType{ 81 { 82 Type: "some-base-type", 83 Image: "some-resource-image", 84 Version: "some-version", 85 }, 86 } 87 platform = "some-platform" 88 tags = atc.Tags{"some", "tags"} 89 teamID = 17 90 ephemeral = true 91 workerName = "some-worker" 92 workerVersion = "1.2.3" 93 fakeDBWorker = new(dbfakes.FakeWorker) 94 95 fakeGardenClient = new(gclientfakes.FakeClient) 96 fakeImageFactory = new(workerfakes.FakeImageFactory) 97 fakeImage = new(workerfakes.FakeImage) 98 fakeImageFactory.GetImageReturns(fakeImage, nil) 99 fakeFetcher = new(workerfakes.FakeFetcher) 100 101 fakeCreatingContainer = new(dbfakes.FakeCreatingContainer) 102 fakeCreatingContainer.HandleReturns("some-handle") 103 fakeCreatedContainer = new(dbfakes.FakeCreatedContainer) 104 fakeCreatedContainer.HandleReturns("some-handle") 105 106 fakeDBVolumeRepository = new(dbfakes.FakeVolumeRepository) 107 fakeResourceCacheFactory = new(dbfakes.FakeResourceCacheFactory) 108 109 fakeDBTeamFactory = new(dbfakes.FakeTeamFactory) 110 fakeDBTeam = new(dbfakes.FakeTeam) 111 fakeDBTeamFactory.GetByIDReturns(fakeDBTeam) 112 113 fakeBaggageclaimClient = new(baggageclaimfakes.FakeClient) 114 115 fakeLocalInput = new(workerfakes.FakeInputSource) 116 fakeLocalInput.DestinationPathReturns("/some/work-dir/local-input") 117 fakeLocalInputAS := new(workerfakes.FakeArtifactSource) 118 fakeLocalVolume = new(workerfakes.FakeVolume) 119 fakeLocalVolume.PathReturns("/fake/local/volume") 120 fakeLocalVolume.COWStrategyReturns(baggageclaim.COWStrategy{ 121 Parent: new(baggageclaimfakes.FakeVolume), 122 }) 123 fakeLocalInputAS.ExistsOnReturns(fakeLocalVolume, true, nil) 124 fakeLocalInput.SourceReturns(fakeLocalInputAS) 125 126 fakeBindMount = new(workerfakes.FakeBindMountSource) 127 fakeBindMount.VolumeOnReturns(garden.BindMount{ 128 SrcPath: "some/source", 129 DstPath: "some/destination", 130 Mode: garden.BindMountModeRO, 131 }, true, nil) 132 133 fakeRemoteInput = new(workerfakes.FakeInputSource) 134 fakeRemoteInput.DestinationPathReturns("/some/work-dir/remote-input") 135 fakeRemoteInputAS = new(workerfakes.FakeStreamableArtifactSource) 136 fakeRemoteInputAS.ExistsOnReturns(nil, false, nil) 137 fakeRemoteInput.SourceReturns(fakeRemoteInputAS) 138 139 fakeScratchVolume := new(workerfakes.FakeVolume) 140 fakeScratchVolume.PathReturns("/fake/scratch/volume") 141 142 fakeWorkdirVolume := new(workerfakes.FakeVolume) 143 fakeWorkdirVolume.PathReturns("/fake/work-dir/volume") 144 145 fakeOutputVolume = new(workerfakes.FakeVolume) 146 fakeOutputVolume.PathReturns("/fake/output/volume") 147 148 fakeLocalCOWVolume = new(workerfakes.FakeVolume) 149 fakeLocalCOWVolume.PathReturns("/fake/local/cow/volume") 150 151 fakeRemoteInputContainerVolume = new(workerfakes.FakeVolume) 152 fakeRemoteInputContainerVolume.PathReturns("/fake/remote/input/container/volume") 153 154 stubbedVolumes = map[string]*workerfakes.FakeVolume{ 155 "/scratch": fakeScratchVolume, 156 "/some/work-dir": fakeWorkdirVolume, 157 "/some/work-dir/local-input": fakeLocalCOWVolume, 158 "/some/work-dir/remote-input": fakeRemoteInputContainerVolume, 159 "/some/work-dir/output": fakeOutputVolume, 160 } 161 162 volumeSpecs = map[string]VolumeSpec{} 163 164 fakeVolumeClient.FindOrCreateCOWVolumeForContainerStub = func(logger lager.Logger, volumeSpec VolumeSpec, creatingContainer db.CreatingContainer, volume Volume, teamID int, mountPath string) (Volume, error) { 165 Expect(volume).To(Equal(fakeLocalVolume)) 166 167 volume, found := stubbedVolumes[mountPath] 168 if !found { 169 panic("unknown container volume: " + mountPath) 170 } 171 172 volumeSpecs[mountPath] = volumeSpec 173 174 return volume, nil 175 } 176 177 fakeVolumeClient.FindOrCreateVolumeForContainerStub = func(logger lager.Logger, volumeSpec VolumeSpec, creatingContainer db.CreatingContainer, teamID int, mountPath string) (Volume, error) { 178 volume, found := stubbedVolumes[mountPath] 179 if !found { 180 panic("unknown container volume: " + mountPath) 181 } 182 183 volumeSpecs[mountPath] = volumeSpec 184 185 return volume, nil 186 } 187 ctx = context.Background() 188 189 fakeContainerOwner = new(dbfakes.FakeContainerOwner) 190 191 fakeImage.FetchForContainerReturns(FetchedImage{ 192 Metadata: ImageMetadata{ 193 Env: []string{"IMAGE=ENV"}, 194 }, 195 URL: "some-image-url", 196 }, nil) 197 containerMetadata = db.ContainerMetadata{ 198 StepName: "some-step", 199 } 200 201 fakeGardenContainer = new(gclientfakes.FakeContainer) 202 fakeGardenClient.CreateReturns(fakeGardenContainer, nil) 203 }) 204 205 JustBeforeEach(func() { 206 fakeDBWorker.ActiveContainersReturns(activeContainers) 207 fakeDBWorker.ResourceTypesReturns(resourceTypes) 208 fakeDBWorker.PlatformReturns(platform) 209 fakeDBWorker.TagsReturns(tags) 210 fakeDBWorker.EphemeralReturns(ephemeral) 211 fakeDBWorker.TeamIDReturns(teamID) 212 fakeDBWorker.NameReturns(workerName) 213 fakeDBWorker.VersionReturns(&workerVersion) 214 fakeDBWorker.HTTPProxyURLReturns("http://proxy.com") 215 fakeDBWorker.HTTPSProxyURLReturns("https://proxy.com") 216 fakeDBWorker.NoProxyReturns("http://noproxy.com") 217 218 gardenWorker = NewGardenWorker( 219 fakeGardenClient, 220 fakeDBVolumeRepository, 221 fakeVolumeClient, 222 fakeImageFactory, 223 fakeFetcher, 224 fakeDBTeamFactory, 225 fakeDBWorker, 226 fakeResourceCacheFactory, 227 0, 228 ) 229 }) 230 231 Describe("IsVersionCompatible", func() { 232 It("is compatible when versions are the same", func() { 233 requiredVersion := version.MustNewVersionFromString("1.2.3") 234 Expect( 235 gardenWorker.IsVersionCompatible(logger, requiredVersion), 236 ).To(BeTrue()) 237 }) 238 239 It("is not compatible when versions are different in major version", func() { 240 requiredVersion := version.MustNewVersionFromString("2.2.3") 241 Expect( 242 gardenWorker.IsVersionCompatible(logger, requiredVersion), 243 ).To(BeFalse()) 244 }) 245 246 It("is compatible when worker minor version is newer", func() { 247 requiredVersion := version.MustNewVersionFromString("1.1.3") 248 Expect( 249 gardenWorker.IsVersionCompatible(logger, requiredVersion), 250 ).To(BeTrue()) 251 }) 252 253 It("is not compatible when worker minor version is older", func() { 254 requiredVersion := version.MustNewVersionFromString("1.3.3") 255 Expect( 256 gardenWorker.IsVersionCompatible(logger, requiredVersion), 257 ).To(BeFalse()) 258 }) 259 260 Context("when worker version is empty", func() { 261 BeforeEach(func() { 262 workerVersion = "" 263 }) 264 265 It("is not compatible", func() { 266 requiredVersion := version.MustNewVersionFromString("1.2.3") 267 Expect( 268 gardenWorker.IsVersionCompatible(logger, requiredVersion), 269 ).To(BeFalse()) 270 }) 271 }) 272 273 Context("when worker version does not have minor version", func() { 274 BeforeEach(func() { 275 workerVersion = "1" 276 }) 277 278 It("is compatible when it is the same", func() { 279 requiredVersion := version.MustNewVersionFromString("1") 280 Expect( 281 gardenWorker.IsVersionCompatible(logger, requiredVersion), 282 ).To(BeTrue()) 283 }) 284 285 It("is not compatible when it is different", func() { 286 requiredVersion := version.MustNewVersionFromString("2") 287 Expect( 288 gardenWorker.IsVersionCompatible(logger, requiredVersion), 289 ).To(BeFalse()) 290 }) 291 292 It("is not compatible when compared version has minor vesion", func() { 293 requiredVersion := version.MustNewVersionFromString("1.2") 294 Expect( 295 gardenWorker.IsVersionCompatible(logger, requiredVersion), 296 ).To(BeFalse()) 297 }) 298 }) 299 }) 300 301 Describe("FindCreatedContainerByHandle", func() { 302 var ( 303 foundContainer Container 304 findErr error 305 found bool 306 ) 307 308 JustBeforeEach(func() { 309 foundContainer, found, findErr = gardenWorker.FindContainerByHandle(logger, 42, "some-container-handle") 310 }) 311 Context("when the gardenClient returns a container and no error", func() { 312 var ( 313 fakeContainer *gclientfakes.FakeContainer 314 ) 315 316 BeforeEach(func() { 317 fakeContainer = new(gclientfakes.FakeContainer) 318 fakeContainer.HandleReturns("provider-handle") 319 320 fakeDBVolumeRepository.FindVolumesForContainerReturns([]db.CreatedVolume{}, nil) 321 322 fakeDBTeam.FindCreatedContainerByHandleReturns(fakeCreatedContainer, true, nil) 323 fakeGardenClient.LookupReturns(fakeContainer, nil) 324 }) 325 326 It("returns the container", func() { 327 Expect(findErr).NotTo(HaveOccurred()) 328 Expect(found).To(BeTrue()) 329 Expect(foundContainer.Handle()).To(Equal(fakeContainer.Handle())) 330 }) 331 332 Describe("the found container", func() { 333 It("can be destroyed", func() { 334 err := foundContainer.Destroy() 335 Expect(err).NotTo(HaveOccurred()) 336 337 By("destroying via garden") 338 Expect(fakeGardenClient.DestroyCallCount()).To(Equal(1)) 339 actualHandle := fakeGardenClient.DestroyArgsForCall(0) 340 Expect(actualHandle).To(Equal("provider-handle")) 341 }) 342 }) 343 344 Context("when the concourse:volumes property is present", func() { 345 var ( 346 expectedHandle1Volume *workerfakes.FakeVolume 347 expectedHandle2Volume *workerfakes.FakeVolume 348 ) 349 350 BeforeEach(func() { 351 expectedHandle1Volume = new(workerfakes.FakeVolume) 352 expectedHandle2Volume = new(workerfakes.FakeVolume) 353 354 expectedHandle1Volume.HandleReturns("handle-1") 355 expectedHandle2Volume.HandleReturns("handle-2") 356 357 expectedHandle1Volume.PathReturns("/handle-1/path") 358 expectedHandle2Volume.PathReturns("/handle-2/path") 359 360 fakeVolumeClient.LookupVolumeStub = func(logger lager.Logger, handle string) (Volume, bool, error) { 361 if handle == "handle-1" { 362 return expectedHandle1Volume, true, nil 363 } else if handle == "handle-2" { 364 return expectedHandle2Volume, true, nil 365 } else { 366 panic("unknown handle: " + handle) 367 } 368 } 369 370 dbVolume1 := new(dbfakes.FakeCreatedVolume) 371 dbVolume2 := new(dbfakes.FakeCreatedVolume) 372 fakeDBVolumeRepository.FindVolumesForContainerReturns([]db.CreatedVolume{dbVolume1, dbVolume2}, nil) 373 dbVolume1.HandleReturns("handle-1") 374 dbVolume2.HandleReturns("handle-2") 375 dbVolume1.PathReturns("/handle-1/path") 376 dbVolume2.PathReturns("/handle-2/path") 377 }) 378 379 Describe("VolumeMounts", func() { 380 It("returns all bound volumes based on properties on the container", func() { 381 Expect(findErr).NotTo(HaveOccurred()) 382 Expect(found).To(BeTrue()) 383 Expect(foundContainer.VolumeMounts()).To(ConsistOf([]VolumeMount{ 384 {Volume: expectedHandle1Volume, MountPath: "/handle-1/path"}, 385 {Volume: expectedHandle2Volume, MountPath: "/handle-2/path"}, 386 })) 387 }) 388 389 Context("when LookupVolume returns an error", func() { 390 disaster := errors.New("nope") 391 392 BeforeEach(func() { 393 fakeVolumeClient.LookupVolumeReturns(nil, false, disaster) 394 }) 395 396 It("returns the error on lookup", func() { 397 Expect(findErr).To(Equal(disaster)) 398 }) 399 }) 400 }) 401 }) 402 403 Context("when the user property is present", func() { 404 var ( 405 actualSpec garden.ProcessSpec 406 actualIO garden.ProcessIO 407 ) 408 409 BeforeEach(func() { 410 actualSpec = garden.ProcessSpec{ 411 Path: "some-path", 412 Args: []string{"some", "args"}, 413 Env: []string{"some=env"}, 414 Dir: "some-dir", 415 } 416 417 actualIO = garden.ProcessIO{} 418 419 fakeContainer.PropertiesReturns(garden.Properties{"user": "maverick"}, nil) 420 }) 421 422 JustBeforeEach(func() { 423 foundContainer.Run(context.TODO(), actualSpec, actualIO) 424 }) 425 426 Describe("Run", func() { 427 It("calls Run() on the garden container and injects the user", func() { 428 Expect(fakeContainer.RunCallCount()).To(Equal(1)) 429 _, spec, io := fakeContainer.RunArgsForCall(0) 430 Expect(spec).To(Equal(garden.ProcessSpec{ 431 Path: "some-path", 432 Args: []string{"some", "args"}, 433 Env: []string{"some=env"}, 434 Dir: "some-dir", 435 User: "maverick", 436 })) 437 Expect(io).To(Equal(garden.ProcessIO{})) 438 }) 439 }) 440 }) 441 442 Context("when the user property is not present", func() { 443 var ( 444 actualSpec garden.ProcessSpec 445 actualIO garden.ProcessIO 446 ) 447 448 BeforeEach(func() { 449 actualSpec = garden.ProcessSpec{ 450 Path: "some-path", 451 Args: []string{"some", "args"}, 452 Env: []string{"some=env"}, 453 Dir: "some-dir", 454 } 455 456 actualIO = garden.ProcessIO{} 457 458 fakeContainer.PropertiesReturns(garden.Properties{"user": ""}, nil) 459 }) 460 461 JustBeforeEach(func() { 462 foundContainer.Run(context.TODO(), actualSpec, actualIO) 463 }) 464 465 Describe("Run", func() { 466 It("calls Run() on the garden container and injects the default user", func() { 467 Expect(fakeContainer.RunCallCount()).To(Equal(1)) 468 _, spec, io := fakeContainer.RunArgsForCall(0) 469 Expect(spec).To(Equal(garden.ProcessSpec{ 470 Path: "some-path", 471 Args: []string{"some", "args"}, 472 Env: []string{"some=env"}, 473 Dir: "some-dir", 474 User: "root", 475 })) 476 Expect(io).To(Equal(garden.ProcessIO{})) 477 Expect(fakeContainer.RunCallCount()).To(Equal(1)) 478 }) 479 }) 480 }) 481 }) 482 483 Context("when the gardenClient returns garden.ContainerNotFoundError", func() { 484 BeforeEach(func() { 485 fakeGardenClient.LookupReturns(nil, garden.ContainerNotFoundError{Handle: "some-handle"}) 486 }) 487 It("returns false and no error", func() { 488 Expect(findErr).ToNot(HaveOccurred()) 489 Expect(found).To(BeFalse()) 490 }) 491 }) 492 493 Context("when the gardenClient returns an error", func() { 494 var expectedErr error 495 496 BeforeEach(func() { 497 expectedErr = fmt.Errorf("container not found") 498 fakeGardenClient.LookupReturns(nil, expectedErr) 499 }) 500 501 It("returns nil and forwards the error", func() { 502 Expect(findErr).To(Equal(expectedErr)) 503 504 Expect(foundContainer).To(BeNil()) 505 }) 506 }) 507 }) 508 509 Describe("CreateVolume", func() { 510 var ( 511 fakeVolume *workerfakes.FakeVolume 512 volume Volume 513 err error 514 ) 515 516 BeforeEach(func() { 517 fakeVolume = new(workerfakes.FakeVolume) 518 fakeVolumeClient.CreateVolumeReturns(fakeVolume, nil) 519 }) 520 521 JustBeforeEach(func() { 522 volume, err = gardenWorker.CreateVolume(logger, VolumeSpec{}, 42, db.VolumeTypeArtifact) 523 }) 524 525 It("calls the volume client", func() { 526 Expect(fakeVolumeClient.CreateVolumeCallCount()).To(Equal(1)) 527 528 Expect(err).ToNot(HaveOccurred()) 529 Expect(volume).To(Equal(fakeVolume)) 530 }) 531 }) 532 533 Describe("Satisfies", func() { 534 var ( 535 spec WorkerSpec 536 537 satisfies bool 538 ) 539 540 BeforeEach(func() { 541 spec = WorkerSpec{ 542 Tags: []string{"some", "tags"}, 543 TeamID: teamID, 544 } 545 }) 546 547 JustBeforeEach(func() { 548 satisfies = gardenWorker.Satisfies(logger, spec) 549 }) 550 551 Context("when the platform is compatible", func() { 552 BeforeEach(func() { 553 spec.Platform = "some-platform" 554 }) 555 556 Context("when no tags are specified", func() { 557 BeforeEach(func() { 558 spec.Tags = nil 559 }) 560 561 It("returns false", func() { 562 Expect(satisfies).To(BeFalse()) 563 }) 564 }) 565 566 Context("when the worker has no tags", func() { 567 BeforeEach(func() { 568 tags = []string{} 569 spec.Tags = []string{} 570 }) 571 572 It("returns true", func() { 573 Expect(satisfies).To(BeTrue()) 574 }) 575 }) 576 577 Context("when all of the requested tags are present", func() { 578 BeforeEach(func() { 579 spec.Tags = []string{"some", "tags"} 580 }) 581 582 It("returns true", func() { 583 Expect(satisfies).To(BeTrue()) 584 }) 585 }) 586 587 Context("when some of the requested tags are present", func() { 588 BeforeEach(func() { 589 spec.Tags = []string{"some"} 590 }) 591 592 It("returns true", func() { 593 Expect(satisfies).To(BeTrue()) 594 }) 595 }) 596 597 Context("when any of the requested tags are not present", func() { 598 BeforeEach(func() { 599 spec.Tags = []string{"bogus", "tags"} 600 }) 601 602 It("returns false", func() { 603 Expect(satisfies).To(BeFalse()) 604 }) 605 }) 606 }) 607 608 Context("when the platform is incompatible", func() { 609 BeforeEach(func() { 610 spec.Platform = "some-bogus-platform" 611 }) 612 613 It("returns false", func() { 614 Expect(satisfies).To(BeFalse()) 615 }) 616 }) 617 618 Context("when the resource type is supported by the worker", func() { 619 BeforeEach(func() { 620 spec.ResourceType = "some-base-type" 621 }) 622 623 It("returns true", func() { 624 Expect(satisfies).To(BeTrue()) 625 }) 626 627 Context("when all of the requested tags are present", func() { 628 BeforeEach(func() { 629 spec.Tags = []string{"some", "tags"} 630 }) 631 632 It("returns true", func() { 633 Expect(satisfies).To(BeTrue()) 634 }) 635 }) 636 637 Context("when some of the requested tags are present", func() { 638 BeforeEach(func() { 639 spec.Tags = []string{"some"} 640 }) 641 642 It("returns true", func() { 643 Expect(satisfies).To(BeTrue()) 644 }) 645 }) 646 647 Context("when any of the requested tags are not present", func() { 648 BeforeEach(func() { 649 spec.Tags = []string{"bogus", "tags"} 650 }) 651 652 It("returns false", func() { 653 Expect(satisfies).To(BeFalse()) 654 }) 655 }) 656 }) 657 658 Context("when the type is not supported by the worker", func() { 659 BeforeEach(func() { 660 spec.ResourceType = "some-bogus-type" 661 }) 662 663 It("returns false", func() { 664 Expect(satisfies).To(BeFalse()) 665 }) 666 }) 667 668 Context("when spec specifies team", func() { 669 BeforeEach(func() { 670 teamID = 123 671 spec.TeamID = teamID 672 }) 673 674 Context("when worker belongs to same team", func() { 675 It("returns true", func() { 676 Expect(satisfies).To(BeTrue()) 677 }) 678 }) 679 680 Context("when worker belongs to different team", func() { 681 BeforeEach(func() { 682 teamID = 777 683 }) 684 685 It("returns false", func() { 686 Expect(satisfies).To(BeFalse()) 687 }) 688 }) 689 690 Context("when worker does not belong to any team", func() { 691 It("returns true", func() { 692 Expect(satisfies).To(BeTrue()) 693 }) 694 }) 695 }) 696 697 Context("when spec does not specify a team", func() { 698 Context("when worker belongs to no team", func() { 699 BeforeEach(func() { 700 teamID = 0 701 }) 702 703 It("returns true", func() { 704 Expect(satisfies).To(BeTrue()) 705 }) 706 }) 707 708 Context("when worker belongs to any team", func() { 709 BeforeEach(func() { 710 teamID = 555 711 }) 712 713 It("returns false", func() { 714 Expect(satisfies).To(BeFalse()) 715 }) 716 }) 717 }) 718 }) 719 720 Describe("FindOrCreateContainer", func() { 721 CertsVolumeExists := func() { 722 fakeCertsVolume := new(baggageclaimfakes.FakeVolume) 723 fakeBaggageclaimClient.LookupVolumeReturns(fakeCertsVolume, true, nil) 724 } 725 726 var containerSpec ContainerSpec 727 728 BeforeEach(func() { 729 cpu := uint64(1024) 730 memory := uint64(1024) 731 732 containerSpec = ContainerSpec{ 733 TeamID: 73410, 734 735 ImageSpec: ImageSpec{ 736 ImageArtifactSource: new(workerfakes.FakeStreamableArtifactSource), 737 }, 738 739 User: "some-user", 740 Env: []string{"SOME=ENV"}, 741 742 Dir: "/some/work-dir", 743 744 Inputs: []InputSource{ 745 fakeLocalInput, 746 fakeRemoteInput, 747 }, 748 749 Outputs: OutputPaths{ 750 "some-output": "/some/work-dir/output", 751 }, 752 BindMounts: []BindMountSource{ 753 fakeBindMount, 754 }, 755 Limits: ContainerLimits{ 756 CPU: &cpu, 757 Memory: &memory, 758 }, 759 } 760 }) 761 762 JustBeforeEach(func() { 763 findOrCreateContainer, findOrCreateErr = gardenWorker.FindOrCreateContainer( 764 ctx, 765 logger, 766 fakeContainerOwner, 767 containerMetadata, 768 containerSpec, 769 ) 770 }) 771 disasterErr := errors.New("disaster") 772 773 Context("when container exists in database in creating state", func() { 774 BeforeEach(func() { 775 fakeDBWorker.FindContainerReturns(fakeCreatingContainer, nil, nil) 776 }) 777 778 It("does not create a new db container", func() { 779 Expect(fakeDBWorker.CreateContainerCallCount()).To(Equal(0)) 780 }) 781 782 Context("when container exists in garden", func() { 783 BeforeEach(func() { 784 fakeGardenClient.LookupReturns(fakeGardenContainer, nil) 785 }) 786 787 It("marks container as created", func() { 788 Expect(fakeCreatingContainer.CreatedCallCount()).To(Equal(1)) 789 }) 790 791 It("returns worker container", func() { 792 Expect(findOrCreateContainer).ToNot(BeNil()) 793 }) 794 }) 795 796 Context("when container does not exist in garden", func() { 797 BeforeEach(func() { 798 fakeGardenClient.LookupReturns(nil, garden.ContainerNotFoundError{}) 799 }) 800 BeforeEach(CertsVolumeExists) 801 802 It("creates container in garden", func() { 803 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 804 }) 805 806 It("marks container as created", func() { 807 Expect(fakeCreatingContainer.CreatedCallCount()).To(Equal(1)) 808 }) 809 810 It("returns worker container", func() { 811 Expect(findOrCreateContainer).ToNot(BeNil()) 812 }) 813 814 It("creates the container in garden with the input and output volumes in alphabetical order", func() { 815 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 816 817 actualSpec := fakeGardenClient.CreateArgsForCall(0) 818 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 819 Handle: "some-handle", 820 RootFSPath: "some-image-url", 821 Properties: garden.Properties{"user": "some-user"}, 822 BindMounts: []garden.BindMount{ 823 { 824 SrcPath: "some/source", 825 DstPath: "some/destination", 826 Mode: garden.BindMountModeRO, 827 }, 828 { 829 SrcPath: "/fake/scratch/volume", 830 DstPath: "/scratch", 831 Mode: garden.BindMountModeRW, 832 }, 833 { 834 SrcPath: "/fake/work-dir/volume", 835 DstPath: "/some/work-dir", 836 Mode: garden.BindMountModeRW, 837 }, 838 { 839 SrcPath: "/fake/local/cow/volume", 840 DstPath: "/some/work-dir/local-input", 841 Mode: garden.BindMountModeRW, 842 }, 843 { 844 SrcPath: "/fake/output/volume", 845 DstPath: "/some/work-dir/output", 846 Mode: garden.BindMountModeRW, 847 }, 848 { 849 SrcPath: "/fake/remote/input/container/volume", 850 DstPath: "/some/work-dir/remote-input", 851 Mode: garden.BindMountModeRW, 852 }, 853 }, 854 Limits: garden.Limits{ 855 CPU: garden.CPULimits{LimitInShares: 1024}, 856 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 857 }, 858 Env: []string{ 859 "IMAGE=ENV", 860 "SOME=ENV", 861 "http_proxy=http://proxy.com", 862 "https_proxy=https://proxy.com", 863 "no_proxy=http://noproxy.com", 864 }, 865 })) 866 }) 867 868 Context("when the input and output destination paths overlap", func() { 869 var ( 870 fakeRemoteInputUnderInput *workerfakes.FakeInputSource 871 fakeRemoteInputUnderInputAS *workerfakes.FakeArtifactSource 872 fakeRemoteInputUnderOutput *workerfakes.FakeInputSource 873 fakeRemoteInputUnderOutputAS *workerfakes.FakeArtifactSource 874 875 fakeOutputUnderInputVolume *workerfakes.FakeVolume 876 fakeOutputUnderOutputVolume *workerfakes.FakeVolume 877 fakeRemoteInputUnderInputContainerVolume *workerfakes.FakeVolume 878 fakeRemoteInputUnderOutputContainerVolume *workerfakes.FakeVolume 879 ) 880 881 BeforeEach(func() { 882 fakeRemoteInputUnderInput = new(workerfakes.FakeInputSource) 883 fakeRemoteInputUnderInput.DestinationPathReturns("/some/work-dir/remote-input/other-input") 884 fakeRemoteInputUnderInputAS = new(workerfakes.FakeArtifactSource) 885 fakeRemoteInputUnderInputAS.ExistsOnReturns(nil, false, nil) 886 fakeRemoteInputUnderInput.SourceReturns(fakeRemoteInputUnderInputAS) 887 888 fakeRemoteInputUnderOutput = new(workerfakes.FakeInputSource) 889 fakeRemoteInputUnderOutput.DestinationPathReturns("/some/work-dir/output/input") 890 fakeRemoteInputUnderOutputAS = new(workerfakes.FakeArtifactSource) 891 fakeRemoteInputUnderOutputAS.ExistsOnReturns(nil, false, nil) 892 fakeRemoteInputUnderOutput.SourceReturns(fakeRemoteInputUnderOutputAS) 893 894 fakeOutputUnderInputVolume = new(workerfakes.FakeVolume) 895 fakeOutputUnderInputVolume.PathReturns("/fake/output/under/input/volume") 896 fakeOutputUnderOutputVolume = new(workerfakes.FakeVolume) 897 fakeOutputUnderOutputVolume.PathReturns("/fake/output/other-output/volume") 898 899 fakeRemoteInputUnderInputContainerVolume = new(workerfakes.FakeVolume) 900 fakeRemoteInputUnderInputContainerVolume.PathReturns("/fake/remote/input/other-input/container/volume") 901 fakeRemoteInputUnderOutputContainerVolume = new(workerfakes.FakeVolume) 902 fakeRemoteInputUnderOutputContainerVolume.PathReturns("/fake/output/input/container/volume") 903 904 stubbedVolumes["/some/work-dir/remote-input/other-input"] = fakeRemoteInputUnderInputContainerVolume 905 stubbedVolumes["/some/work-dir/output/input"] = fakeRemoteInputUnderOutputContainerVolume 906 stubbedVolumes["/some/work-dir/output/other-output"] = fakeOutputUnderOutputVolume 907 stubbedVolumes["/some/work-dir/local-input/output"] = fakeOutputUnderInputVolume 908 }) 909 910 Context("outputs are nested under inputs", func() { 911 BeforeEach(func() { 912 containerSpec.Inputs = []InputSource{ 913 fakeLocalInput, 914 } 915 containerSpec.Outputs = OutputPaths{ 916 "some-output-under-input": "/some/work-dir/local-input/output", 917 } 918 }) 919 920 It("creates the container with correct bind mounts", func() { 921 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 922 923 actualSpec := fakeGardenClient.CreateArgsForCall(0) 924 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 925 Handle: "some-handle", 926 RootFSPath: "some-image-url", 927 Properties: garden.Properties{"user": "some-user"}, 928 BindMounts: []garden.BindMount{ 929 { 930 SrcPath: "some/source", 931 DstPath: "some/destination", 932 Mode: garden.BindMountModeRO, 933 }, 934 { 935 SrcPath: "/fake/scratch/volume", 936 DstPath: "/scratch", 937 Mode: garden.BindMountModeRW, 938 }, 939 { 940 SrcPath: "/fake/work-dir/volume", 941 DstPath: "/some/work-dir", 942 Mode: garden.BindMountModeRW, 943 }, 944 { 945 SrcPath: "/fake/local/cow/volume", 946 DstPath: "/some/work-dir/local-input", 947 Mode: garden.BindMountModeRW, 948 }, 949 { 950 SrcPath: "/fake/output/under/input/volume", 951 DstPath: "/some/work-dir/local-input/output", 952 Mode: garden.BindMountModeRW, 953 }, 954 }, 955 Limits: garden.Limits{ 956 CPU: garden.CPULimits{LimitInShares: 1024}, 957 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 958 }, 959 Env: []string{ 960 "IMAGE=ENV", 961 "SOME=ENV", 962 "http_proxy=http://proxy.com", 963 "https_proxy=https://proxy.com", 964 "no_proxy=http://noproxy.com", 965 }, 966 })) 967 }) 968 }) 969 970 Context("inputs are nested under inputs", func() { 971 BeforeEach(func() { 972 containerSpec.Inputs = []InputSource{ 973 fakeRemoteInput, 974 fakeRemoteInputUnderInput, 975 } 976 containerSpec.Outputs = OutputPaths{} 977 }) 978 979 It("creates the container with correct bind mounts", func() { 980 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 981 982 actualSpec := fakeGardenClient.CreateArgsForCall(0) 983 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 984 Handle: "some-handle", 985 RootFSPath: "some-image-url", 986 Properties: garden.Properties{"user": "some-user"}, 987 BindMounts: []garden.BindMount{ 988 { 989 SrcPath: "some/source", 990 DstPath: "some/destination", 991 Mode: garden.BindMountModeRO, 992 }, 993 { 994 SrcPath: "/fake/scratch/volume", 995 DstPath: "/scratch", 996 Mode: garden.BindMountModeRW, 997 }, 998 { 999 SrcPath: "/fake/work-dir/volume", 1000 DstPath: "/some/work-dir", 1001 Mode: garden.BindMountModeRW, 1002 }, 1003 { 1004 SrcPath: "/fake/remote/input/container/volume", 1005 DstPath: "/some/work-dir/remote-input", 1006 Mode: garden.BindMountModeRW, 1007 }, 1008 { 1009 SrcPath: "/fake/remote/input/other-input/container/volume", 1010 DstPath: "/some/work-dir/remote-input/other-input", 1011 Mode: garden.BindMountModeRW, 1012 }, 1013 }, 1014 Limits: garden.Limits{ 1015 CPU: garden.CPULimits{LimitInShares: 1024}, 1016 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 1017 }, 1018 Env: []string{ 1019 "IMAGE=ENV", 1020 "SOME=ENV", 1021 "http_proxy=http://proxy.com", 1022 "https_proxy=https://proxy.com", 1023 "no_proxy=http://noproxy.com", 1024 }, 1025 })) 1026 }) 1027 }) 1028 1029 Context("outputs are nested under outputs", func() { 1030 BeforeEach(func() { 1031 containerSpec.Inputs = []InputSource{} 1032 containerSpec.Outputs = OutputPaths{ 1033 "some-output": "/some/work-dir/output", 1034 "some-output-under-output": "/some/work-dir/output/other-output", 1035 } 1036 }) 1037 1038 It("creates the container with correct bind mounts", func() { 1039 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1040 1041 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1042 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 1043 Handle: "some-handle", 1044 RootFSPath: "some-image-url", 1045 Properties: garden.Properties{"user": "some-user"}, 1046 BindMounts: []garden.BindMount{ 1047 { 1048 SrcPath: "some/source", 1049 DstPath: "some/destination", 1050 Mode: garden.BindMountModeRO, 1051 }, 1052 { 1053 SrcPath: "/fake/scratch/volume", 1054 DstPath: "/scratch", 1055 Mode: garden.BindMountModeRW, 1056 }, 1057 { 1058 SrcPath: "/fake/work-dir/volume", 1059 DstPath: "/some/work-dir", 1060 Mode: garden.BindMountModeRW, 1061 }, 1062 { 1063 SrcPath: "/fake/output/volume", 1064 DstPath: "/some/work-dir/output", 1065 Mode: garden.BindMountModeRW, 1066 }, 1067 { 1068 SrcPath: "/fake/output/other-output/volume", 1069 DstPath: "/some/work-dir/output/other-output", 1070 Mode: garden.BindMountModeRW, 1071 }, 1072 }, 1073 Limits: garden.Limits{ 1074 CPU: garden.CPULimits{LimitInShares: 1024}, 1075 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 1076 }, 1077 Env: []string{ 1078 "IMAGE=ENV", 1079 "SOME=ENV", 1080 "http_proxy=http://proxy.com", 1081 "https_proxy=https://proxy.com", 1082 "no_proxy=http://noproxy.com", 1083 }, 1084 })) 1085 }) 1086 }) 1087 1088 Context("inputs are nested under outputs", func() { 1089 BeforeEach(func() { 1090 containerSpec.Inputs = []InputSource{ 1091 fakeRemoteInputUnderOutput, 1092 } 1093 containerSpec.Outputs = OutputPaths{ 1094 "some-output": "/some/work-dir/output", 1095 } 1096 }) 1097 1098 It("creates the container with correct bind mounts", func() { 1099 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1100 1101 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1102 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 1103 Handle: "some-handle", 1104 RootFSPath: "some-image-url", 1105 Properties: garden.Properties{"user": "some-user"}, 1106 BindMounts: []garden.BindMount{ 1107 { 1108 SrcPath: "some/source", 1109 DstPath: "some/destination", 1110 Mode: garden.BindMountModeRO, 1111 }, 1112 { 1113 SrcPath: "/fake/scratch/volume", 1114 DstPath: "/scratch", 1115 Mode: garden.BindMountModeRW, 1116 }, 1117 { 1118 SrcPath: "/fake/work-dir/volume", 1119 DstPath: "/some/work-dir", 1120 Mode: garden.BindMountModeRW, 1121 }, 1122 { 1123 SrcPath: "/fake/output/volume", 1124 DstPath: "/some/work-dir/output", 1125 Mode: garden.BindMountModeRW, 1126 }, 1127 { 1128 SrcPath: "/fake/output/input/container/volume", 1129 DstPath: "/some/work-dir/output/input", 1130 Mode: garden.BindMountModeRW, 1131 }, 1132 }, 1133 Limits: garden.Limits{ 1134 CPU: garden.CPULimits{LimitInShares: 1024}, 1135 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 1136 }, 1137 Env: []string{ 1138 "IMAGE=ENV", 1139 "SOME=ENV", 1140 "http_proxy=http://proxy.com", 1141 "https_proxy=https://proxy.com", 1142 "no_proxy=http://noproxy.com", 1143 }, 1144 })) 1145 1146 }) 1147 }) 1148 1149 Context("input and output share the same destination path", func() { 1150 BeforeEach(func() { 1151 containerSpec.Inputs = []InputSource{ 1152 fakeRemoteInput, 1153 } 1154 containerSpec.Outputs = OutputPaths{ 1155 "some-output": "/some/work-dir/remote-input", 1156 } 1157 }) 1158 1159 It("creates the container with correct bind mounts", func() { 1160 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1161 1162 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1163 Expect(actualSpec).To(Equal(garden.ContainerSpec{ 1164 Handle: "some-handle", 1165 RootFSPath: "some-image-url", 1166 Properties: garden.Properties{"user": "some-user"}, 1167 BindMounts: []garden.BindMount{ 1168 { 1169 SrcPath: "some/source", 1170 DstPath: "some/destination", 1171 Mode: garden.BindMountModeRO, 1172 }, 1173 { 1174 SrcPath: "/fake/scratch/volume", 1175 DstPath: "/scratch", 1176 Mode: garden.BindMountModeRW, 1177 }, 1178 { 1179 SrcPath: "/fake/work-dir/volume", 1180 DstPath: "/some/work-dir", 1181 Mode: garden.BindMountModeRW, 1182 }, 1183 { 1184 SrcPath: "/fake/remote/input/container/volume", 1185 DstPath: "/some/work-dir/remote-input", 1186 Mode: garden.BindMountModeRW, 1187 }, 1188 }, 1189 Limits: garden.Limits{ 1190 CPU: garden.CPULimits{LimitInShares: 1024}, 1191 Memory: garden.MemoryLimits{LimitInBytes: 1024}, 1192 }, 1193 Env: []string{ 1194 "IMAGE=ENV", 1195 "SOME=ENV", 1196 "http_proxy=http://proxy.com", 1197 "https_proxy=https://proxy.com", 1198 "no_proxy=http://noproxy.com", 1199 }, 1200 })) 1201 }) 1202 1203 }) 1204 }) 1205 1206 Context("when the certs volume does not exist on the worker", func() { 1207 BeforeEach(func() { 1208 fakeBaggageclaimClient.LookupVolumeReturns(nil, false, nil) 1209 }) 1210 It("creates the container in garden, but does not bind mount any certs", func() { 1211 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1212 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1213 Expect(actualSpec.BindMounts).ToNot(ContainElement( 1214 garden.BindMount{ 1215 SrcPath: "/the/certs/volume/path", 1216 DstPath: "/etc/ssl/certs", 1217 Mode: garden.BindMountModeRO, 1218 }, 1219 )) 1220 }) 1221 }) 1222 1223 It("creates each volume unprivileged", func() { 1224 Expect(volumeSpecs).To(Equal(map[string]VolumeSpec{ 1225 "/scratch": {Strategy: baggageclaim.EmptyStrategy{}}, 1226 "/some/work-dir": {Strategy: baggageclaim.EmptyStrategy{}}, 1227 "/some/work-dir/output": {Strategy: baggageclaim.EmptyStrategy{}}, 1228 "/some/work-dir/local-input": {Strategy: fakeLocalVolume.COWStrategy()}, 1229 "/some/work-dir/remote-input": {Strategy: baggageclaim.EmptyStrategy{}}, 1230 })) 1231 }) 1232 1233 It("streams remote inputs into newly created container volumes", func() { 1234 Expect(fakeRemoteInputAS.StreamToCallCount()).To(Equal(1)) 1235 _, ad := fakeRemoteInputAS.StreamToArgsForCall(0) 1236 1237 err := ad.StreamIn(context.TODO(), ".", baggageclaim.GzipEncoding, bytes.NewBufferString("some-stream")) 1238 Expect(err).ToNot(HaveOccurred()) 1239 1240 Expect(fakeRemoteInputContainerVolume.StreamInCallCount()).To(Equal(1)) 1241 1242 _, dst, encoding, from := fakeRemoteInputContainerVolume.StreamInArgsForCall(0) 1243 Expect(dst).To(Equal(".")) 1244 Expect(encoding).To(Equal(baggageclaim.GzipEncoding)) 1245 Expect(ioutil.ReadAll(from)).To(Equal([]byte("some-stream"))) 1246 }) 1247 1248 It("marks container as created", func() { 1249 Expect(fakeCreatingContainer.CreatedCallCount()).To(Equal(1)) 1250 }) 1251 1252 Context("when the fetched image was privileged", func() { 1253 BeforeEach(func() { 1254 fakeImage.FetchForContainerReturns(FetchedImage{ 1255 Privileged: true, 1256 Metadata: ImageMetadata{ 1257 Env: []string{"IMAGE=ENV"}, 1258 }, 1259 URL: "some-image-url", 1260 }, nil) 1261 }) 1262 1263 It("creates the container privileged", func() { 1264 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1265 1266 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1267 Expect(actualSpec.Privileged).To(BeTrue()) 1268 }) 1269 1270 It("creates each volume privileged", func() { 1271 Expect(volumeSpecs).To(Equal(map[string]VolumeSpec{ 1272 "/scratch": {Privileged: true, Strategy: baggageclaim.EmptyStrategy{}}, 1273 "/some/work-dir": {Privileged: true, Strategy: baggageclaim.EmptyStrategy{}}, 1274 "/some/work-dir/output": {Privileged: true, Strategy: baggageclaim.EmptyStrategy{}}, 1275 "/some/work-dir/local-input": {Privileged: true, Strategy: fakeLocalVolume.COWStrategy()}, 1276 "/some/work-dir/remote-input": {Privileged: true, Strategy: baggageclaim.EmptyStrategy{}}, 1277 })) 1278 }) 1279 1280 }) 1281 1282 Context("when an input has the path set to the workdir itself", func() { 1283 BeforeEach(func() { 1284 fakeLocalInput.DestinationPathReturns("/some/work-dir") 1285 delete(stubbedVolumes, "/some/work-dir/local-input") 1286 stubbedVolumes["/some/work-dir"] = fakeLocalCOWVolume 1287 }) 1288 1289 It("does not create or mount a work-dir, as we support this for backwards-compatibility", func() { 1290 Expect(fakeGardenClient.CreateCallCount()).To(Equal(1)) 1291 1292 actualSpec := fakeGardenClient.CreateArgsForCall(0) 1293 Expect(actualSpec.BindMounts).To(Equal([]garden.BindMount{ 1294 { 1295 SrcPath: "some/source", 1296 DstPath: "some/destination", 1297 Mode: garden.BindMountModeRO, 1298 }, 1299 { 1300 SrcPath: "/fake/scratch/volume", 1301 DstPath: "/scratch", 1302 Mode: garden.BindMountModeRW, 1303 }, 1304 { 1305 SrcPath: "/fake/local/cow/volume", 1306 DstPath: "/some/work-dir", 1307 Mode: garden.BindMountModeRW, 1308 }, 1309 { 1310 SrcPath: "/fake/output/volume", 1311 DstPath: "/some/work-dir/output", 1312 Mode: garden.BindMountModeRW, 1313 }, 1314 { 1315 SrcPath: "/fake/remote/input/container/volume", 1316 DstPath: "/some/work-dir/remote-input", 1317 Mode: garden.BindMountModeRW, 1318 }, 1319 })) 1320 }) 1321 }) 1322 1323 Context("when failing to create container in garden", func() { 1324 BeforeEach(func() { 1325 fakeGardenClient.CreateReturns(nil, disasterErr) 1326 }) 1327 1328 It("returns an error", func() { 1329 Expect(findOrCreateErr).To(Equal(fmt.Errorf("find or create container on worker some-worker: %w", disasterErr))) 1330 }) 1331 1332 It("does not mark container as created", func() { 1333 Expect(fakeCreatingContainer.CreatedCallCount()).To(Equal(0)) 1334 }) 1335 1336 It("marks the container as failed", func() { 1337 Expect(fakeCreatingContainer.FailedCallCount()).To(Equal(1)) 1338 }) 1339 }) 1340 1341 Context("when failing to create container in garden", func() { 1342 BeforeEach(func() { 1343 fakeGardenClient.CreateReturns(nil, disasterErr) 1344 }) 1345 1346 It("returns an error", func() { 1347 Expect(findOrCreateErr).To(Equal(fmt.Errorf("find or create container on worker some-worker: %w", disasterErr))) 1348 }) 1349 1350 It("does not mark container as created", func() { 1351 Expect(fakeCreatingContainer.CreatedCallCount()).To(Equal(0)) 1352 }) 1353 }) 1354 }) 1355 1356 }) 1357 1358 Context("when container exists in database in created state", func() { 1359 BeforeEach(func() { 1360 fakeDBWorker.FindContainerReturns(nil, fakeCreatedContainer, nil) 1361 }) 1362 1363 It("does not create a new db container", func() { 1364 Expect(fakeDBWorker.CreateContainerCallCount()).To(Equal(0)) 1365 }) 1366 1367 Context("when container exists in garden", func() { 1368 BeforeEach(func() { 1369 fakeGardenClient.LookupReturns(fakeGardenContainer, nil) 1370 }) 1371 1372 It("returns container", func() { 1373 Expect(findOrCreateErr).ToNot(HaveOccurred()) 1374 Expect(findOrCreateContainer).ToNot(BeNil()) 1375 }) 1376 }) 1377 1378 Context("when container does not exist in garden", func() { 1379 var containerNotFoundErr error 1380 1381 BeforeEach(func() { 1382 containerNotFoundErr = garden.ContainerNotFoundError{Handle: fakeCreatedContainer.Handle()} 1383 fakeGardenClient.LookupReturns(nil, containerNotFoundErr) 1384 }) 1385 1386 It("returns an error", func() { 1387 Expect(findOrCreateErr).To(Equal(fmt.Errorf("find or create container on worker some-worker: %w", containerNotFoundErr))) 1388 }) 1389 }) 1390 }) 1391 1392 Context("when container does not exist in database", func() { 1393 1394 BeforeEach(func() { 1395 fakeDBWorker.FindContainerReturns(nil, nil, nil) 1396 fakeDBWorker.CreateContainerReturns(fakeCreatingContainer, nil) 1397 }) 1398 1399 It("attemps to create container in the db", func() { 1400 Expect(fakeDBWorker.CreateContainerCallCount()).To(Equal(1)) 1401 }) 1402 1403 Context("having db container creation erroring", func() { 1404 Context("with ContainerOwnerDisappearedError", func() { 1405 BeforeEach(func() { 1406 fakeDBWorker.CreateContainerReturns(nil, db.ContainerOwnerDisappearedError{}) 1407 }) 1408 1409 It("fails w/ ResourceConfigCheckSessionExpiredError", func() { 1410 Expect(findOrCreateErr).To(HaveOccurred()) 1411 Expect(findOrCreateErr).To(Equal(fmt.Errorf("find or create container on worker some-worker: %w", ResourceConfigCheckSessionExpiredError))) 1412 }) 1413 }) 1414 1415 Context("with a non-specific error", func() { 1416 var someErr error = errors.New("err") 1417 1418 BeforeEach(func() { 1419 fakeDBWorker.CreateContainerReturns(nil, someErr) 1420 }) 1421 1422 It("fails with the same err", func() { 1423 Expect(findOrCreateErr).To(HaveOccurred()) 1424 Expect(findOrCreateErr).To(MatchError("find or create container on worker some-worker: create container: err")) 1425 Expect(errors.Is(findOrCreateErr, someErr)).To(BeTrue()) 1426 }) 1427 }) 1428 }) 1429 1430 Context("having db container creation succeeding", func() { 1431 It("creates a creating container in database", func() { 1432 owner, metadata := fakeDBWorker.CreateContainerArgsForCall(0) 1433 Expect(owner).To(Equal(fakeContainerOwner)) 1434 Expect(metadata).To(Equal(containerMetadata)) 1435 }) 1436 }) 1437 1438 }) 1439 }) 1440 })