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  })