github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/client_test.go (about)

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