1package worker_test
2
3import (
4	"code.cloudfoundry.org/lager"
5	"code.cloudfoundry.org/lager/lagertest"
6	"github.com/concourse/concourse/atc/db"
7	. "github.com/concourse/concourse/atc/worker"
8	"github.com/concourse/concourse/atc/worker/workerfakes"
9
10	. "github.com/onsi/ginkgo"
11	. "github.com/onsi/gomega"
12)
13
14//go:generate counterfeiter . ContainerPlacementStrategy
15
16var (
17	strategy ContainerPlacementStrategy
18
19	spec     ContainerSpec
20	metadata db.ContainerMetadata
21	workers  []Worker
22
23	chosenWorker Worker
24	chooseErr    error
25
26	compatibleWorkerOneCache1 *workerfakes.FakeWorker
27	compatibleWorkerOneCache2 *workerfakes.FakeWorker
28	compatibleWorkerTwoCaches *workerfakes.FakeWorker
29	compatibleWorkerNoCaches1 *workerfakes.FakeWorker
30	compatibleWorkerNoCaches2 *workerfakes.FakeWorker
31
32	logger *lagertest.TestLogger
33)
34
35var _ = Describe("FewestBuildContainersPlacementStrategy", func() {
36	Describe("Choose", func() {
37		var compatibleWorker1 *workerfakes.FakeWorker
38		var compatibleWorker2 *workerfakes.FakeWorker
39		var compatibleWorker3 *workerfakes.FakeWorker
40
41		BeforeEach(func() {
42			logger = lagertest.NewTestLogger("build-containers-equal-placement-test")
43			strategy = NewFewestBuildContainersPlacementStrategy()
44			compatibleWorker1 = new(workerfakes.FakeWorker)
45			compatibleWorker2 = new(workerfakes.FakeWorker)
46			compatibleWorker3 = new(workerfakes.FakeWorker)
47
48			spec = ContainerSpec{
49				ImageSpec: ImageSpec{ResourceType: "some-type"},
50
51				TeamID: 4567,
52
53				Inputs: []InputSource{},
54			}
55		})
56
57		Context("when there is only one worker", func() {
58			BeforeEach(func() {
59				workers = []Worker{compatibleWorker1}
60				compatibleWorker1.BuildContainersReturns(20)
61			})
62
63			It("picks that worker", func() {
64				chosenWorker, chooseErr = strategy.Choose(
65					logger,
66					workers,
67					spec,
68				)
69				Expect(chooseErr).ToNot(HaveOccurred())
70				Expect(chosenWorker).To(Equal(compatibleWorker1))
71			})
72		})
73
74		Context("when there are multiple workers", func() {
75			BeforeEach(func() {
76				workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
77
78				compatibleWorker1.BuildContainersReturns(30)
79				compatibleWorker2.BuildContainersReturns(20)
80				compatibleWorker3.BuildContainersReturns(10)
81			})
82
83			Context("when the container is not of type 'check'", func() {
84				It("picks the one with least amount of containers", func() {
85					Consistently(func() Worker {
86						chosenWorker, chooseErr = strategy.Choose(
87							logger,
88							workers,
89							spec,
90						)
91						Expect(chooseErr).ToNot(HaveOccurred())
92						return chosenWorker
93					}).Should(Equal(compatibleWorker3))
94				})
95
96				Context("when there is more than one worker with the same number of build containers", func() {
97					BeforeEach(func() {
98						workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
99						compatibleWorker1.BuildContainersReturns(10)
100					})
101
102					It("picks any of them", func() {
103						Consistently(func() Worker {
104							chosenWorker, chooseErr = strategy.Choose(
105								logger,
106								workers,
107								spec,
108							)
109							Expect(chooseErr).ToNot(HaveOccurred())
110							return chosenWorker
111						}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker3)))
112					})
113				})
114
115			})
116		})
117	})
118})
119
120var _ = Describe("VolumeLocalityPlacementStrategy", func() {
121	Describe("Choose", func() {
122		JustBeforeEach(func() {
123			chosenWorker, chooseErr = strategy.Choose(
124				logger,
125				workers,
126				spec,
127			)
128		})
129
130		BeforeEach(func() {
131			logger = lagertest.NewTestLogger("volume-locality-placement-test")
132			strategy = NewVolumeLocalityPlacementStrategy()
133
134			fakeInput1 := new(workerfakes.FakeInputSource)
135			fakeInput1AS := new(workerfakes.FakeArtifactSource)
136			fakeInput1AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) {
137				switch worker {
138				case compatibleWorkerOneCache1, compatibleWorkerOneCache2, compatibleWorkerTwoCaches:
139					return new(workerfakes.FakeVolume), true, nil
140				default:
141					return nil, false, nil
142				}
143			}
144			fakeInput1.SourceReturns(fakeInput1AS)
145
146			fakeInput2 := new(workerfakes.FakeInputSource)
147			fakeInput2AS := new(workerfakes.FakeArtifactSource)
148			fakeInput2AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) {
149				switch worker {
150				case compatibleWorkerTwoCaches:
151					return new(workerfakes.FakeVolume), true, nil
152				default:
153					return nil, false, nil
154				}
155			}
156			fakeInput2.SourceReturns(fakeInput2AS)
157
158			spec = ContainerSpec{
159				ImageSpec: ImageSpec{ResourceType: "some-type"},
160
161				TeamID: 4567,
162
163				Inputs: []InputSource{
164					fakeInput1,
165					fakeInput2,
166				},
167			}
168
169			compatibleWorkerOneCache1 = new(workerfakes.FakeWorker)
170			compatibleWorkerOneCache1.SatisfiesReturns(true)
171
172			compatibleWorkerOneCache2 = new(workerfakes.FakeWorker)
173			compatibleWorkerOneCache2.SatisfiesReturns(true)
174
175			compatibleWorkerTwoCaches = new(workerfakes.FakeWorker)
176			compatibleWorkerTwoCaches.SatisfiesReturns(true)
177
178			compatibleWorkerNoCaches1 = new(workerfakes.FakeWorker)
179			compatibleWorkerNoCaches1.SatisfiesReturns(true)
180
181			compatibleWorkerNoCaches2 = new(workerfakes.FakeWorker)
182			compatibleWorkerNoCaches2.SatisfiesReturns(true)
183		})
184
185		Context("with one having the most local caches", func() {
186			BeforeEach(func() {
187				workers = []Worker{
188					compatibleWorkerOneCache1,
189					compatibleWorkerTwoCaches,
190					compatibleWorkerNoCaches1,
191					compatibleWorkerNoCaches2,
192				}
193			})
194
195			It("creates it on the worker with the most caches", func() {
196				Expect(chooseErr).ToNot(HaveOccurred())
197				Expect(chosenWorker).To(Equal(compatibleWorkerTwoCaches))
198			})
199		})
200
201		Context("with multiple with the same amount of local caches", func() {
202			BeforeEach(func() {
203				workers = []Worker{
204					compatibleWorkerOneCache1,
205					compatibleWorkerOneCache2,
206					compatibleWorkerNoCaches1,
207					compatibleWorkerNoCaches2,
208				}
209			})
210
211			It("creates it on a random one of the two", func() {
212				Expect(chooseErr).ToNot(HaveOccurred())
213				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2)))
214
215				workerChoiceCounts := map[Worker]int{}
216
217				for i := 0; i < 100; i++ {
218					worker, err := strategy.Choose(
219						logger,
220						workers,
221						spec,
222					)
223					Expect(err).ToNot(HaveOccurred())
224					Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2)))
225					workerChoiceCounts[worker]++
226				}
227
228				Expect(workerChoiceCounts[compatibleWorkerOneCache1]).ToNot(BeZero())
229				Expect(workerChoiceCounts[compatibleWorkerOneCache2]).ToNot(BeZero())
230				Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).To(BeZero())
231				Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).To(BeZero())
232			})
233		})
234
235		Context("with none having any local caches", func() {
236			BeforeEach(func() {
237				workers = []Worker{
238					compatibleWorkerNoCaches1,
239					compatibleWorkerNoCaches2,
240				}
241			})
242
243			It("creates it on a random one of them", func() {
244				Expect(chooseErr).ToNot(HaveOccurred())
245				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
246
247				workerChoiceCounts := map[Worker]int{}
248
249				for i := 0; i < 100; i++ {
250					worker, err := strategy.Choose(
251						logger,
252						workers,
253						spec,
254					)
255					Expect(err).ToNot(HaveOccurred())
256					Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
257					workerChoiceCounts[worker]++
258				}
259
260				Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero())
261				Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero())
262			})
263		})
264	})
265})
266
267var _ = Describe("RandomPlacementStrategy", func() {
268	Describe("Choose", func() {
269		JustBeforeEach(func() {
270			chosenWorker, chooseErr = strategy.Choose(
271				logger,
272				workers,
273				spec,
274			)
275		})
276
277		BeforeEach(func() {
278			strategy = NewRandomPlacementStrategy()
279
280			workers = []Worker{
281				compatibleWorkerNoCaches1,
282				compatibleWorkerNoCaches2,
283			}
284		})
285
286		It("creates it on a random one of them", func() {
287			Expect(chooseErr).ToNot(HaveOccurred())
288			Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
289
290			workerChoiceCounts := map[Worker]int{}
291
292			for i := 0; i < 100; i++ {
293				worker, err := strategy.Choose(
294					logger,
295					workers,
296					spec,
297				)
298				Expect(err).ToNot(HaveOccurred())
299				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
300				workerChoiceCounts[worker]++
301			}
302
303			Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero())
304			Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero())
305		})
306	})
307})
308
309var _ = Describe("LimitActiveTasksPlacementStrategy", func() {
310	Describe("Choose", func() {
311		var compatibleWorker1 *workerfakes.FakeWorker
312		var compatibleWorker2 *workerfakes.FakeWorker
313		var compatibleWorker3 *workerfakes.FakeWorker
314
315		BeforeEach(func() {
316			logger = lagertest.NewTestLogger("active-tasks-equal-placement-test")
317			strategy = NewLimitActiveTasksPlacementStrategy(0)
318			compatibleWorker1 = new(workerfakes.FakeWorker)
319			compatibleWorker2 = new(workerfakes.FakeWorker)
320			compatibleWorker3 = new(workerfakes.FakeWorker)
321
322			spec = ContainerSpec{
323				ImageSpec: ImageSpec{ResourceType: "some-type"},
324
325				Type: "task",
326
327				TeamID: 4567,
328
329				Inputs: []InputSource{},
330			}
331		})
332
333		Context("when there is only one worker with any amount of running tasks", func() {
334			BeforeEach(func() {
335				workers = []Worker{compatibleWorker1}
336				compatibleWorker1.ActiveTasksReturns(42, nil)
337			})
338
339			It("picks that worker", func() {
340				chosenWorker, chooseErr = strategy.Choose(
341					logger,
342					workers,
343					spec,
344				)
345				Expect(chooseErr).ToNot(HaveOccurred())
346				Expect(chosenWorker).To(Equal(compatibleWorker1))
347			})
348		})
349
350		Context("when there are multiple workers", func() {
351			BeforeEach(func() {
352				workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
353
354				compatibleWorker1.ActiveTasksReturns(2, nil)
355				compatibleWorker2.ActiveTasksReturns(1, nil)
356				compatibleWorker3.ActiveTasksReturns(2, nil)
357			})
358
359			It("a task picks the one with least amount of active tasks", func() {
360				Consistently(func() Worker {
361					chosenWorker, chooseErr = strategy.Choose(
362						logger,
363						workers,
364						spec,
365					)
366					Expect(chooseErr).ToNot(HaveOccurred())
367					return chosenWorker
368				}).Should(Equal(compatibleWorker2))
369			})
370
371			Context("when all the workers have the same number of active tasks", func() {
372				BeforeEach(func() {
373					workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
374					compatibleWorker1.ActiveTasksReturns(1, nil)
375					compatibleWorker3.ActiveTasksReturns(1, nil)
376				})
377
378				It("a task picks any of them", func() {
379					Consistently(func() Worker {
380						chosenWorker, chooseErr = strategy.Choose(
381							logger,
382							workers,
383							spec,
384						)
385						Expect(chooseErr).ToNot(HaveOccurred())
386						return chosenWorker
387					}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker3)))
388				})
389			})
390		})
391		Context("when max-tasks-per-worker is set to 1", func() {
392			BeforeEach(func() {
393				strategy = NewLimitActiveTasksPlacementStrategy(1)
394			})
395			Context("when there are multiple workers", func() {
396				BeforeEach(func() {
397					workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
398
399					compatibleWorker1.ActiveTasksReturns(1, nil)
400					compatibleWorker2.ActiveTasksReturns(0, nil)
401					compatibleWorker3.ActiveTasksReturns(1, nil)
402				})
403
404				It("picks the worker with no active tasks", func() {
405					chosenWorker, chooseErr = strategy.Choose(
406						logger,
407						workers,
408						spec,
409					)
410					Expect(chooseErr).ToNot(HaveOccurred())
411					Expect(chosenWorker).To(Equal(compatibleWorker2))
412				})
413			})
414
415			Context("when all workers have active tasks", func() {
416				BeforeEach(func() {
417					workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
418
419					compatibleWorker1.ActiveTasksReturns(1, nil)
420					compatibleWorker2.ActiveTasksReturns(1, nil)
421					compatibleWorker3.ActiveTasksReturns(1, nil)
422				})
423
424				It("picks no worker", func() {
425					chosenWorker, chooseErr = strategy.Choose(
426						logger,
427						workers,
428						spec,
429					)
430					Expect(chooseErr).ToNot(HaveOccurred())
431					Expect(chosenWorker).To(BeNil())
432				})
433				Context("when the container is not of type 'task'", func() {
434					BeforeEach(func() {
435						spec.Type = ""
436					})
437					It("picks any worker", func() {
438						Consistently(func() Worker {
439							chosenWorker, chooseErr = strategy.Choose(
440								logger,
441								workers,
442								spec,
443							)
444							Expect(chooseErr).ToNot(HaveOccurred())
445							return chosenWorker
446						}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker3)))
447					})
448				})
449			})
450		})
451	})
452})
453