1448 lines
41 KiB
Go
1448 lines
41 KiB
Go
package worker_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
|
|
"code.cloudfoundry.org/garden"
|
|
"code.cloudfoundry.org/garden/gardenfakes"
|
|
"github.com/concourse/concourse/atc"
|
|
"github.com/concourse/concourse/atc/db/dbfakes"
|
|
"github.com/concourse/concourse/atc/db/lock/lockfakes"
|
|
"github.com/concourse/concourse/atc/exec/execfakes"
|
|
"github.com/concourse/concourse/atc/runtime"
|
|
"github.com/onsi/gomega/gbytes"
|
|
|
|
"code.cloudfoundry.org/lager/lagertest"
|
|
"github.com/concourse/baggageclaim"
|
|
"github.com/concourse/concourse/atc/db"
|
|
"github.com/concourse/concourse/atc/worker"
|
|
"github.com/concourse/concourse/atc/worker/workerfakes"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("Client", func() {
|
|
var (
|
|
logger *lagertest.TestLogger
|
|
fakePool *workerfakes.FakePool
|
|
fakeProvider *workerfakes.FakeWorkerProvider
|
|
client worker.Client
|
|
fakeLock *lockfakes.FakeLock
|
|
fakeLockFactory *lockfakes.FakeLockFactory
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
logger = lagertest.NewTestLogger("test")
|
|
fakePool = new(workerfakes.FakePool)
|
|
fakeProvider = new(workerfakes.FakeWorkerProvider)
|
|
|
|
client = worker.NewClient(fakePool, fakeProvider)
|
|
})
|
|
|
|
Describe("FindContainer", func() {
|
|
var (
|
|
foundContainer worker.Container
|
|
found bool
|
|
findErr error
|
|
)
|
|
|
|
JustBeforeEach(func() {
|
|
foundContainer, found, findErr = client.FindContainer(
|
|
logger,
|
|
4567,
|
|
"some-handle",
|
|
)
|
|
})
|
|
|
|
Context("when looking up the worker errors", func() {
|
|
BeforeEach(func() {
|
|
fakeProvider.FindWorkerForContainerReturns(nil, false, errors.New("nope"))
|
|
})
|
|
|
|
It("errors", func() {
|
|
Expect(findErr).To(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when worker is not found", func() {
|
|
BeforeEach(func() {
|
|
fakeProvider.FindWorkerForContainerReturns(nil, false, nil)
|
|
})
|
|
|
|
It("returns not found", func() {
|
|
Expect(findErr).NotTo(HaveOccurred())
|
|
Expect(found).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when a worker is found with the container", func() {
|
|
var fakeWorker *workerfakes.FakeWorker
|
|
var fakeContainer *workerfakes.FakeContainer
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakeProvider.FindWorkerForContainerReturns(fakeWorker, true, nil)
|
|
|
|
fakeContainer = new(workerfakes.FakeContainer)
|
|
fakeWorker.FindContainerByHandleReturns(fakeContainer, true, nil)
|
|
})
|
|
|
|
It("succeeds", func() {
|
|
Expect(found).To(BeTrue())
|
|
Expect(findErr).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("returns the created container", func() {
|
|
Expect(foundContainer).To(Equal(fakeContainer))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("FindVolume", func() {
|
|
var (
|
|
foundVolume worker.Volume
|
|
found bool
|
|
findErr error
|
|
)
|
|
|
|
JustBeforeEach(func() {
|
|
foundVolume, found, findErr = client.FindVolume(
|
|
logger,
|
|
4567,
|
|
"some-handle",
|
|
)
|
|
})
|
|
|
|
Context("when looking up the worker errors", func() {
|
|
BeforeEach(func() {
|
|
fakeProvider.FindWorkerForVolumeReturns(nil, false, errors.New("nope"))
|
|
})
|
|
|
|
It("errors", func() {
|
|
Expect(findErr).To(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when worker is not found", func() {
|
|
BeforeEach(func() {
|
|
fakeProvider.FindWorkerForVolumeReturns(nil, false, nil)
|
|
})
|
|
|
|
It("returns not found", func() {
|
|
Expect(findErr).NotTo(HaveOccurred())
|
|
Expect(found).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when a worker is found with the volume", func() {
|
|
var fakeWorker *workerfakes.FakeWorker
|
|
var fakeVolume *workerfakes.FakeVolume
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakeProvider.FindWorkerForVolumeReturns(fakeWorker, true, nil)
|
|
|
|
fakeVolume = new(workerfakes.FakeVolume)
|
|
fakeWorker.LookupVolumeReturns(fakeVolume, true, nil)
|
|
})
|
|
|
|
It("succeeds", func() {
|
|
Expect(found).To(BeTrue())
|
|
Expect(findErr).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("returns the volume", func() {
|
|
Expect(foundVolume).To(Equal(fakeVolume))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("CreateVolume", func() {
|
|
var (
|
|
fakeWorker *workerfakes.FakeWorker
|
|
volumeSpec worker.VolumeSpec
|
|
workerSpec worker.WorkerSpec
|
|
volumeType db.VolumeType
|
|
err error
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
volumeSpec = worker.VolumeSpec{
|
|
Strategy: baggageclaim.EmptyStrategy{},
|
|
}
|
|
|
|
workerSpec = worker.WorkerSpec{
|
|
TeamID: 1,
|
|
}
|
|
|
|
volumeType = db.VolumeTypeArtifact
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
_, err = client.CreateVolume(logger, volumeSpec, workerSpec, volumeType)
|
|
})
|
|
|
|
Context("when no workers can be found", func() {
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerReturns(nil, errors.New("nope"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when the worker can be found", func() {
|
|
BeforeEach(func() {
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakePool.FindOrChooseWorkerReturns(fakeWorker, nil)
|
|
})
|
|
|
|
It("creates the volume on the worker", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(fakeWorker.CreateVolumeCallCount()).To(Equal(1))
|
|
l, spec, id, t := fakeWorker.CreateVolumeArgsForCall(0)
|
|
Expect(l).To(Equal(logger))
|
|
Expect(spec).To(Equal(volumeSpec))
|
|
Expect(id).To(Equal(1))
|
|
Expect(t).To(Equal(volumeType))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("RunTaskStep", func() {
|
|
var (
|
|
status int
|
|
volumeMounts []worker.VolumeMount
|
|
err error
|
|
|
|
fakeWorker *workerfakes.FakeWorker
|
|
fakeContainerOwner db.ContainerOwner
|
|
fakeWorkerSpec worker.WorkerSpec
|
|
fakeContainerSpec worker.ContainerSpec
|
|
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
|
|
fakeMetadata db.ContainerMetadata
|
|
fakeDelegate *execfakes.FakeTaskDelegate
|
|
fakeImageFetcherSpec worker.ImageFetcherSpec
|
|
fakeTaskProcessSpec worker.ProcessSpec
|
|
fakeContainer *workerfakes.FakeContainer
|
|
eventChan chan runtime.Event
|
|
ctx context.Context
|
|
cancel func()
|
|
)
|
|
JustBeforeEach(func() {
|
|
taskResult := client.RunTaskStep(
|
|
ctx,
|
|
logger,
|
|
fakeLockFactory,
|
|
fakeContainerOwner,
|
|
fakeContainerSpec,
|
|
fakeWorkerSpec,
|
|
fakeStrategy,
|
|
fakeMetadata,
|
|
fakeImageFetcherSpec,
|
|
fakeTaskProcessSpec,
|
|
eventChan,
|
|
)
|
|
status = taskResult.Status
|
|
volumeMounts = taskResult.VolumeMounts
|
|
err = taskResult.Err
|
|
})
|
|
|
|
BeforeEach(func() {
|
|
cpu := uint64(1024)
|
|
memory := uint64(1024)
|
|
|
|
buildId := 1234
|
|
planId := atc.PlanID(42)
|
|
teamId := 123
|
|
fakeDelegate = new(execfakes.FakeTaskDelegate)
|
|
fakeContainerOwner = db.NewBuildStepContainerOwner(
|
|
buildId,
|
|
planId,
|
|
teamId,
|
|
)
|
|
fakeWorkerSpec = worker.WorkerSpec{}
|
|
fakeContainerSpec = worker.ContainerSpec{
|
|
Platform: "some-platform",
|
|
Tags: []string{"step", "tags"},
|
|
TeamID: 123,
|
|
ImageSpec: worker.ImageSpec{
|
|
ImageResource: &worker.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "secret-source-param"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Privileged: false,
|
|
},
|
|
Limits: worker.ContainerLimits{
|
|
CPU: &cpu,
|
|
Memory: &memory,
|
|
},
|
|
Dir: "some-artifact-root",
|
|
Env: []string{"SECURE=secret-task-param"},
|
|
Inputs: []worker.InputSource{},
|
|
Outputs: worker.OutputPaths{},
|
|
}
|
|
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
|
|
fakeMetadata = db.ContainerMetadata{
|
|
WorkingDirectory: "some-artifact-root",
|
|
Type: db.ContainerTypeTask,
|
|
StepName: "some-step",
|
|
}
|
|
fakeImageFetcherSpec = worker.ImageFetcherSpec{
|
|
Delegate: fakeDelegate,
|
|
ResourceTypes: atc.VersionedResourceTypes{},
|
|
}
|
|
fakeTaskProcessSpec = worker.ProcessSpec{
|
|
Path: "/some/path",
|
|
Args: []string{"some", "args"},
|
|
Dir: "/some/dir",
|
|
}
|
|
fakeContainer = new(workerfakes.FakeContainer)
|
|
fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil)
|
|
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakeWorker.SatisfiesReturns(true)
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
|
|
fakeWorker.IncreaseActiveTasksStub = func() error {
|
|
fakeWorker.ActiveTasksReturns(1, nil)
|
|
return nil
|
|
}
|
|
|
|
fakeWorker.DecreaseActiveTasksStub = func() error {
|
|
fakeWorker.ActiveTasksReturns(0, nil)
|
|
return nil
|
|
}
|
|
|
|
fakeLockFactory = new(lockfakes.FakeLockFactory)
|
|
fakeLock = new(lockfakes.FakeLock)
|
|
fakeLockFactory.AcquireReturns(fakeLock, true, nil)
|
|
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
eventChan = make(chan runtime.Event, 1)
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
})
|
|
|
|
Context("choosing a worker", func() {
|
|
BeforeEach(func() {
|
|
// later fakes are uninitialized
|
|
fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "3"}, nil)
|
|
})
|
|
|
|
It("chooses a worker", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1))
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
Context("when a worker is found", func() {
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
|
|
fakeContainer := new(workerfakes.FakeContainer)
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil)
|
|
})
|
|
It("increase the active tasks on the worker", func() {
|
|
Expect(fakeWorker.IncreaseActiveTasksCallCount()).To(Equal(1))
|
|
})
|
|
|
|
Context("when the container is already present on the worker", func() {
|
|
BeforeEach(func() {
|
|
fakePool.ContainerInWorkerReturns(true, nil)
|
|
})
|
|
It("does not increase the active tasks on the worker", func() {
|
|
Expect(fakeWorker.IncreaseActiveTasksCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the task is aborted waiting for an available worker", func() {
|
|
BeforeEach(func() {
|
|
cancel()
|
|
})
|
|
It("exits releasing the lock", func() {
|
|
Expect(err).To(Equal(context.Canceled))
|
|
Expect(status).To(Equal(-1))
|
|
Expect(fakeLock.ReleaseCallCount()).To(Equal(fakeLockFactory.AcquireCallCount()))
|
|
})
|
|
})
|
|
|
|
Context("when a container in worker returns an error", func() {
|
|
BeforeEach(func() {
|
|
fakePool.ContainerInWorkerReturns(false, errors.New("nope"))
|
|
})
|
|
It("release the task-step lock every time it acquires it", func() {
|
|
Expect(fakeLock.ReleaseCallCount()).To(Equal(fakeLockFactory.AcquireCallCount()))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when finding or choosing the worker fails", func() {
|
|
workerDisaster := errors.New("worker selection failed")
|
|
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerForContainerReturns(nil, workerDisaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(workerDisaster))
|
|
})
|
|
})
|
|
|
|
})
|
|
|
|
It("finds or creates a container", func() {
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, cancel, delegate, owner, createdMetadata, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(cancel).ToNot(BeNil())
|
|
Expect(owner).To(Equal(fakeContainerOwner))
|
|
Expect(delegate).To(Equal(fakeDelegate))
|
|
Expect(createdMetadata).To(Equal(db.ContainerMetadata{
|
|
WorkingDirectory: "some-artifact-root",
|
|
Type: db.ContainerTypeTask,
|
|
StepName: "some-step",
|
|
}))
|
|
Expect(containerSpec).To(Equal(fakeContainerSpec))
|
|
})
|
|
|
|
Context("found a container that has already exited", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "8"}, nil)
|
|
})
|
|
|
|
It("does not attach to any process", func() {
|
|
Expect(fakeContainer.AttachCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("returns result of container process", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(status).To(Equal(8))
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Context("when volumes are configured and present on the container", func() {
|
|
var (
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
It("returns all the volume mounts", func() {
|
|
Expect(volumeMounts).To(ConsistOf(
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("container has not already exited", func() {
|
|
var (
|
|
fakeProcess *gardenfakes.FakeProcess
|
|
fakeProcessExitCode int
|
|
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
|
|
stdoutBuf *gbytes.Buffer
|
|
stderrBuf *gbytes.Buffer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeProcess = new(gardenfakes.FakeProcess)
|
|
fakeContainer.PropertiesReturns(garden.Properties{}, nil)
|
|
|
|
// for testing volume mounts being returned
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
Context("found container that is already running", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.AttachReturns(fakeProcess, nil)
|
|
|
|
stdoutBuf = new(gbytes.Buffer)
|
|
stderrBuf = new(gbytes.Buffer)
|
|
fakeTaskProcessSpec = worker.ProcessSpec{
|
|
StdoutWriter: stdoutBuf,
|
|
StderrWriter: stderrBuf,
|
|
}
|
|
})
|
|
|
|
It("does not send a Starting event", func() {
|
|
Expect(eventChan).ToNot(Receive(Equal(runtime.Event{EventType: runtime.StartingEvent, ExitStatus: 0})))
|
|
})
|
|
|
|
It("does not create a new container", func() {
|
|
Expect(fakeContainer.RunCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("attaches to the running process", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(fakeContainer.AttachCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.RunCallCount()).To(Equal(0))
|
|
_, _, actualProcessIO := fakeContainer.AttachArgsForCall(0)
|
|
Expect(actualProcessIO.Stdout).To(Equal(stdoutBuf))
|
|
Expect(actualProcessIO.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits successfully", func() {
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 0
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, nil)
|
|
})
|
|
It("returns a successful result", func() {
|
|
Expect(status).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("returns all the volume mounts", func() {
|
|
Expect(volumeMounts).To(ConsistOf(
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
))
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits with an error", func() {
|
|
disaster := errors.New("process failed")
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 128 + 15
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, disaster)
|
|
})
|
|
It("returns an unsuccessful result", func() {
|
|
Expect(status).To(Equal(fakeProcessExitCode))
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
|
|
It("returns no volume mounts", func() {
|
|
Expect(volumeMounts).To(BeEmpty())
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("created a new container", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.AttachReturns(nil, errors.New("container not running"))
|
|
fakeContainer.RunReturns(fakeProcess, nil)
|
|
|
|
stdoutBuf = new(gbytes.Buffer)
|
|
stderrBuf = new(gbytes.Buffer)
|
|
fakeTaskProcessSpec = worker.ProcessSpec{
|
|
StdoutWriter: stdoutBuf,
|
|
StderrWriter: stderrBuf,
|
|
}
|
|
})
|
|
|
|
It("sends a Starting event", func() {
|
|
Expect(eventChan).To(Receive(Equal(runtime.Event{EventType: "Starting",ExitStatus: 0})))
|
|
})
|
|
|
|
It("runs a new process in the container", func() {
|
|
Eventually(fakeContainer.RunCallCount()).Should(Equal(1))
|
|
|
|
_, gardenProcessSpec, actualProcessIO := fakeContainer.RunArgsForCall(0)
|
|
Expect(gardenProcessSpec.ID).To(Equal("task"))
|
|
Expect(gardenProcessSpec.Path).To(Equal(fakeTaskProcessSpec.Path))
|
|
Expect(gardenProcessSpec.Args).To(ConsistOf(fakeTaskProcessSpec.Args))
|
|
Expect(gardenProcessSpec.Dir).To(Equal(path.Join(fakeMetadata.WorkingDirectory, fakeTaskProcessSpec.Dir)))
|
|
Expect(gardenProcessSpec.TTY).To(Equal(&garden.TTYSpec{WindowSize: &garden.WindowSize{Columns: 500, Rows: 500}}))
|
|
Expect(actualProcessIO.Stdout).To(Equal(stdoutBuf))
|
|
Expect(actualProcessIO.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil // wat?
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits successfully", func() {
|
|
It("returns a successful result", func() {
|
|
Expect(status).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("saves the exit status property", func() {
|
|
Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1))
|
|
|
|
name, value := fakeContainer.SetPropertyArgsForCall(0)
|
|
Expect(name).To(Equal("concourse:exit-status"))
|
|
Expect(value).To(Equal("0"))
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyReturns(nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:exit-status" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when volumes are configured and present on the container", func() {
|
|
var (
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
It("returns all the volume mounts", func() {
|
|
Expect(volumeMounts).To(ConsistOf(
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
))
|
|
})
|
|
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits on failure", func() {
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 128 + 15
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, nil)
|
|
})
|
|
It("returns an unsuccessful result", func() {
|
|
Expect(status).To(Equal(fakeProcessExitCode))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("saves the exit status property", func() {
|
|
Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1))
|
|
|
|
name, value := fakeContainer.SetPropertyArgsForCall(0)
|
|
Expect(name).To(Equal("concourse:exit-status"))
|
|
Expect(value).To(Equal(fmt.Sprint(fakeProcessExitCode)))
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertiesReturns(garden.Properties{"concourse:exit-status": "0"}, nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:exit-status" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
It("returns all the volume mounts", func() {
|
|
Expect(volumeMounts).To(ConsistOf(
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
))
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when running the container fails with an error", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.RunReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
|
|
Context("when 'limit-active-tasks' strategy is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeStrategy.ModifiesActiveTasksReturns(true)
|
|
})
|
|
|
|
It("decrements the active tasks counter on the worker", func() {
|
|
Expect(fakeWorker.ActiveTasks()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("RunPutStep", func() {
|
|
|
|
var (
|
|
ctx context.Context
|
|
cancel func()
|
|
owner db.ContainerOwner
|
|
containerSpec worker.ContainerSpec
|
|
workerSpec worker.WorkerSpec
|
|
source atc.Source
|
|
params atc.Params
|
|
metadata db.ContainerMetadata
|
|
imageSpec worker.ImageFetcherSpec
|
|
events chan runtime.Event
|
|
fakeChosenWorker *workerfakes.FakeWorker
|
|
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
|
|
fakeDelegate *workerfakes.FakeImageFetchingDelegate
|
|
fakeResourceTypes atc.VersionedResourceTypes
|
|
fakeContainer *workerfakes.FakeContainer
|
|
fakeProcessSpec worker.ProcessSpec
|
|
|
|
versionResult runtime.VersionResult
|
|
status int
|
|
err error
|
|
|
|
disasterErr error
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
owner = new(dbfakes.FakeContainerOwner)
|
|
containerSpec = worker.ContainerSpec{}
|
|
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
|
|
workerSpec = worker.WorkerSpec{}
|
|
fakeChosenWorker = new(workerfakes.FakeWorker)
|
|
fakeDelegate = new(workerfakes.FakeImageFetchingDelegate)
|
|
fakeResourceTypes = atc.VersionedResourceTypes{}
|
|
imageSpec = worker.ImageFetcherSpec{
|
|
Delegate: fakeDelegate,
|
|
ResourceTypes: fakeResourceTypes,
|
|
}
|
|
|
|
fakeContainer = new(workerfakes.FakeContainer)
|
|
disasterErr = errors.New("oh no")
|
|
stdout := new(gbytes.Buffer)
|
|
stderr := new(gbytes.Buffer)
|
|
fakeProcessSpec = worker.ProcessSpec{
|
|
Path: "/opt/resource/out",
|
|
StdoutWriter: stdout,
|
|
StderrWriter: stderr,
|
|
}
|
|
events = make(chan runtime.Event, 1)
|
|
|
|
source = atc.Source{"some": "super-secret-source"}
|
|
params = atc.Params{"some-param": "some-value"}
|
|
fakeChosenWorker = new(workerfakes.FakeWorker)
|
|
fakeChosenWorker.NameReturns("some-worker")
|
|
fakeChosenWorker.SatisfiesReturns(true)
|
|
fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeChosenWorker, nil)
|
|
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
result := client.RunPutStep(
|
|
ctx,
|
|
logger,
|
|
owner,
|
|
containerSpec,
|
|
workerSpec,
|
|
source,
|
|
params,
|
|
fakeStrategy,
|
|
metadata,
|
|
imageSpec,
|
|
"/tmp/build/put",
|
|
fakeProcessSpec,
|
|
events,
|
|
)
|
|
versionResult = result.VersionResult
|
|
err = result.Err
|
|
status = result.Status
|
|
})
|
|
|
|
It("finds/chooses a worker", func() {
|
|
Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1))
|
|
|
|
_, _, actualOwner, actualContainerSpec, actualWorkerSpec, strategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(actualOwner).To(Equal(owner))
|
|
Expect(actualContainerSpec).To(Equal(containerSpec))
|
|
Expect(actualWorkerSpec).To(Equal(workerSpec))
|
|
Expect(strategy).To(Equal(fakeStrategy))
|
|
})
|
|
|
|
Context("worker is chosen", func() {
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerReturns(fakeChosenWorker, nil)
|
|
})
|
|
It("finds or creates a put container on that worker", func() {
|
|
Expect(fakeChosenWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, _, actualDelegate, actualOwner, actualMetadata, actualContainerSpec, actualResourceTypes := fakeChosenWorker.FindOrCreateContainerArgsForCall(0)
|
|
|
|
Expect(actualDelegate).To(Equal(fakeDelegate))
|
|
Expect(actualOwner).To(Equal(owner))
|
|
Expect(actualContainerSpec).To(Equal(containerSpec))
|
|
Expect(actualMetadata).To(Equal(metadata))
|
|
Expect(actualResourceTypes).To(Equal(fakeResourceTypes))
|
|
})
|
|
})
|
|
|
|
Context("worker selection returns an error", func() {
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerForContainerReturns(nil, disasterErr)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(Equal(disasterErr))
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
})
|
|
|
|
Context("found a container that has already exited", func() {
|
|
var status int
|
|
BeforeEach(func() {
|
|
status = 8
|
|
fakeChosenWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
fakeContainer.PropertyStub = func(prop string) (result string, err error) {
|
|
if prop == "concourse:exit-status" {
|
|
return "8", nil
|
|
}
|
|
return "", errors.New("unhandled property")
|
|
}
|
|
})
|
|
|
|
It("does not attach to any process", func() {
|
|
Expect(fakeContainer.AttachCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("returns result of container process", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(status).To(Equal(8))
|
|
})
|
|
})
|
|
|
|
Context("container has not already exited", func() {
|
|
var (
|
|
fakeProcess *gardenfakes.FakeProcess
|
|
fakeProcessExitCode int
|
|
|
|
stdoutBuf *gbytes.Buffer
|
|
stderrBuf *gbytes.Buffer
|
|
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
stdoutBuf = new(gbytes.Buffer)
|
|
stderrBuf = new(gbytes.Buffer)
|
|
fakeProcess = new(gardenfakes.FakeProcess)
|
|
fakeContainer.PropertyReturns("", errors.New("not exited"))
|
|
})
|
|
|
|
Context("found container that is already running", func() {
|
|
var expectedVersionResult runtime.VersionResult
|
|
BeforeEach(func() {
|
|
fakeContainer.AttachStub = func(arg1 context.Context, arg2 string, arg3 garden.ProcessIO) (garden.Process, error){
|
|
_, _ = arg3.Stdout.Write([]byte(`{"version": { "foo": "bar" }}`))
|
|
return fakeProcess, nil
|
|
}
|
|
expectedVersionResult = runtime.VersionResult{
|
|
Version: atc.Version(map[string]string{"foo": "bar"}),
|
|
Metadata: nil,
|
|
}
|
|
})
|
|
|
|
It("does not send a Starting event", func() {
|
|
Expect(events).ToNot(Receive(Equal(runtime.Event{EventType: runtime.StartingEvent, ExitStatus: 0})))
|
|
})
|
|
|
|
It("does not create a new container", func() {
|
|
Expect(fakeContainer.RunCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("attaches to the running process", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(fakeContainer.AttachCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.RunCallCount()).To(Equal(0))
|
|
_, _, actualProcessIO := fakeContainer.AttachArgsForCall(0)
|
|
Expect(actualProcessIO.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits successfully", func() {
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 0
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, nil)
|
|
})
|
|
It("returns a successful result", func() {
|
|
Expect(versionResult).To(Equal(expectedVersionResult))
|
|
Expect(status).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
})
|
|
|
|
Context("when the process exits with an error", func() {
|
|
disaster := errors.New("process failed")
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 128 + 15
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, disaster)
|
|
})
|
|
It("returns an unsuccessful result", func() {
|
|
Expect(status).To(Equal(-1))
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(Equal(disaster))
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
|
|
It("returns no version results", func() {
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
})
|
|
|
|
Context("when the process exits with nonzero status", func() {
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 128 + 15
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, nil)
|
|
})
|
|
It("returns an unsuccessful result", func() {
|
|
Expect(status).To(Equal(fakeProcessExitCode))
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(BeAssignableToTypeOf(worker.ErrResourceScriptFailed{}))
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
|
|
It("returns no version results", func() {
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("created a new container", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.AttachReturns(nil, errors.New("container not running"))
|
|
fakeContainer.RunReturns(fakeProcess, nil)
|
|
|
|
stdoutBuf = new(gbytes.Buffer)
|
|
stderrBuf = new(gbytes.Buffer)
|
|
fakeProcessSpec = worker.ProcessSpec{
|
|
Path: "/opt/resource/out",
|
|
Args: []string{"/tmp/build/put"},
|
|
StdoutWriter: stdoutBuf,
|
|
StderrWriter: stderrBuf,
|
|
}
|
|
fakeContainer.RunStub = func(arg1 context.Context, arg2 garden.ProcessSpec, arg3 garden.ProcessIO) (garden.Process, error){
|
|
_, _ = arg3.Stdout.Write([]byte(`{"version": { "foo": "bar" }}`))
|
|
return fakeProcess, nil
|
|
}
|
|
})
|
|
|
|
It("sends a Starting event", func() {
|
|
Expect(events).To(Receive(Equal(runtime.Event{EventType: "Starting",ExitStatus: 0})))
|
|
})
|
|
|
|
It("runs a new process in the container", func() {
|
|
Eventually(fakeContainer.RunCallCount()).Should(Equal(1))
|
|
|
|
_, gardenProcessSpec, actualProcessIO := fakeContainer.RunArgsForCall(0)
|
|
Expect(gardenProcessSpec.ID).To(Equal("resource"))
|
|
Expect(gardenProcessSpec.Path).To(Equal(fakeProcessSpec.Path))
|
|
Expect(gardenProcessSpec.Args).To(ConsistOf(fakeProcessSpec.Args))
|
|
Expect(actualProcessIO.Stdout).To(Not(Equal(stdoutBuf)))
|
|
Expect(actualProcessIO.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil // wat?
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(err).To(Equal(context.Canceled))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits successfully", func() {
|
|
|
|
// It("puts the resource with the given context", func() {
|
|
// Expect(fakeResource.PutCallCount()).To(Equal(1))
|
|
// putCtx, _, _, _ := fakeResource.PutArgsForCall(0)
|
|
// Expect(putCtx).To(Equal(ctx))
|
|
// })
|
|
|
|
// It("puts the resource with the correct source and params", func() {
|
|
// Expect(fakeResource.PutCallCount()).To(Equal(1))
|
|
//
|
|
// _, _, putSource, putParams := fakeResource.PutArgsForCall(0)
|
|
// Expect(putSource).To(Equal(atc.Source{"some": "super-secret-source"}))
|
|
// Expect(putParams).To(Equal(atc.Params{"some-param": "some-value"}))
|
|
// })
|
|
|
|
// It("puts the resource with the io config forwarded", func() {
|
|
// Expect(fakeResource.PutCallCount()).To(Equal(1))
|
|
//
|
|
// _, ioConfig, _, _ := fakeResource.PutArgsForCall(0)
|
|
// Expect(ioConfig.Stdout).To(Equal(stdoutBuf))
|
|
// Expect(ioConfig.Stderr).To(Equal(stderrBuf))
|
|
// })
|
|
|
|
// It("runs the get resource action", func() {
|
|
// Expect(fakeResource.PutCallCount()).To(Equal(1))
|
|
// })
|
|
It("returns a successful result", func() {
|
|
Expect(status).To(BeZero())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("saves the exit status property", func() {
|
|
Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1))
|
|
|
|
name, value := fakeContainer.SetPropertyArgsForCall(0)
|
|
Expect(name).To(Equal("concourse:resource-result"))
|
|
Expect(value).To(Equal(string(`{"version": { "foo": "bar" }}`)))
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyReturns(nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:resource-result" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits on failure", func() {
|
|
BeforeEach(func() {
|
|
fakeProcessExitCode = 128 + 15
|
|
fakeProcess.WaitReturns(fakeProcessExitCode, nil)
|
|
})
|
|
It("returns an unsuccessful result", func() {
|
|
Expect(status).To(Equal(fakeProcessExitCode))
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(BeAssignableToTypeOf(worker.ErrResourceScriptFailed{}))
|
|
})
|
|
})
|
|
|
|
Context("when running the container fails with an error", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.RunReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("worker.FindOrCreateContainer errored", func() {
|
|
BeforeEach(func() {
|
|
fakeChosenWorker.FindOrCreateContainerReturns(nil, disasterErr)
|
|
})
|
|
|
|
It("returns the error immediately", func() {
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err).To(Equal(disasterErr))
|
|
Expect(versionResult).To(Equal(runtime.VersionResult{}))
|
|
})
|
|
})
|
|
})
|
|
})
|