github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/placement_test.go (about) 1 package worker_test 2 3 import ( 4 "errors" 5 6 "code.cloudfoundry.org/lager" 7 "code.cloudfoundry.org/lager/lagertest" 8 "github.com/pf-qiu/concourse/v6/atc/db" 9 . "github.com/pf-qiu/concourse/v6/atc/worker" 10 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 11 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 ) 15 16 //go:generate counterfeiter . ContainerPlacementStrategy 17 18 var ( 19 strategy ContainerPlacementStrategy 20 21 spec ContainerSpec 22 metadata db.ContainerMetadata 23 workers []Worker 24 25 chosenWorker Worker 26 chooseErr error 27 28 newStrategyError error 29 30 compatibleWorkerOneCache1 *workerfakes.FakeWorker 31 compatibleWorkerOneCache2 *workerfakes.FakeWorker 32 compatibleWorkerTwoCaches *workerfakes.FakeWorker 33 compatibleWorkerNoCaches1 *workerfakes.FakeWorker 34 compatibleWorkerNoCaches2 *workerfakes.FakeWorker 35 36 logger *lagertest.TestLogger 37 ) 38 39 var _ = Describe("FewestBuildContainersPlacementStrategy", func() { 40 Describe("Choose", func() { 41 var compatibleWorker1 *workerfakes.FakeWorker 42 var compatibleWorker2 *workerfakes.FakeWorker 43 var compatibleWorker3 *workerfakes.FakeWorker 44 45 BeforeEach(func() { 46 logger = lagertest.NewTestLogger("build-containers-equal-placement-test") 47 strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"fewest-build-containers"}}) 48 Expect(newStrategyError).ToNot(HaveOccurred()) 49 compatibleWorker1 = new(workerfakes.FakeWorker) 50 compatibleWorker1.NameReturns("compatibleWorker1") 51 compatibleWorker2 = new(workerfakes.FakeWorker) 52 compatibleWorker2.NameReturns("compatibleWorker2") 53 compatibleWorker3 = new(workerfakes.FakeWorker) 54 compatibleWorker3.NameReturns("compatibleWorker3") 55 56 spec = ContainerSpec{ 57 ImageSpec: ImageSpec{ResourceType: "some-type"}, 58 59 TeamID: 4567, 60 61 Inputs: []InputSource{}, 62 } 63 }) 64 65 Context("when there is only one worker", func() { 66 BeforeEach(func() { 67 workers = []Worker{compatibleWorker1} 68 compatibleWorker1.BuildContainersReturns(20) 69 }) 70 71 It("picks that worker", func() { 72 chosenWorker, chooseErr = strategy.Choose( 73 logger, 74 workers, 75 spec, 76 ) 77 Expect(chooseErr).ToNot(HaveOccurred()) 78 Expect(chosenWorker).To(Equal(compatibleWorker1)) 79 }) 80 }) 81 82 Context("when there are multiple workers", func() { 83 BeforeEach(func() { 84 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 85 86 compatibleWorker1.BuildContainersReturns(30) 87 compatibleWorker2.BuildContainersReturns(20) 88 compatibleWorker3.BuildContainersReturns(10) 89 }) 90 91 Context("when the container is not of type 'check'", func() { 92 It("picks the one with least amount of containers", func() { 93 Consistently(func() Worker { 94 chosenWorker, chooseErr = strategy.Choose( 95 logger, 96 workers, 97 spec, 98 ) 99 Expect(chooseErr).ToNot(HaveOccurred()) 100 return chosenWorker 101 }).Should(Equal(compatibleWorker3)) 102 }) 103 104 Context("when there is more than one worker with the same number of build containers", func() { 105 BeforeEach(func() { 106 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 107 compatibleWorker1.BuildContainersReturns(10) 108 }) 109 110 It("picks any of them", func() { 111 Consistently(func() Worker { 112 chosenWorker, chooseErr = strategy.Choose( 113 logger, 114 workers, 115 spec, 116 ) 117 Expect(chooseErr).ToNot(HaveOccurred()) 118 return chosenWorker 119 }).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker3))) 120 }) 121 }) 122 123 }) 124 }) 125 }) 126 }) 127 128 var _ = Describe("VolumeLocalityPlacementStrategy", func() { 129 Describe("Choose", func() { 130 JustBeforeEach(func() { 131 chosenWorker, chooseErr = strategy.Choose( 132 logger, 133 workers, 134 spec, 135 ) 136 }) 137 138 BeforeEach(func() { 139 logger = lagertest.NewTestLogger("volume-locality-placement-test") 140 strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"volume-locality"}}) 141 Expect(newStrategyError).ToNot(HaveOccurred()) 142 143 fakeInput1 := new(workerfakes.FakeInputSource) 144 fakeInput1AS := new(workerfakes.FakeArtifactSource) 145 fakeInput1AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) { 146 switch worker { 147 case compatibleWorkerOneCache1, compatibleWorkerOneCache2, compatibleWorkerTwoCaches: 148 return new(workerfakes.FakeVolume), true, nil 149 default: 150 return nil, false, nil 151 } 152 } 153 fakeInput1.SourceReturns(fakeInput1AS) 154 155 fakeInput2 := new(workerfakes.FakeInputSource) 156 fakeInput2AS := new(workerfakes.FakeArtifactSource) 157 fakeInput2AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) { 158 switch worker { 159 case compatibleWorkerTwoCaches: 160 return new(workerfakes.FakeVolume), true, nil 161 default: 162 return nil, false, nil 163 } 164 } 165 fakeInput2.SourceReturns(fakeInput2AS) 166 167 spec = ContainerSpec{ 168 ImageSpec: ImageSpec{ResourceType: "some-type"}, 169 170 TeamID: 4567, 171 172 Inputs: []InputSource{ 173 fakeInput1, 174 fakeInput2, 175 }, 176 } 177 178 compatibleWorkerOneCache1 = new(workerfakes.FakeWorker) 179 compatibleWorkerOneCache1.SatisfiesReturns(true) 180 compatibleWorkerOneCache1.NameReturns("compatibleWorkerOneCache1") 181 182 compatibleWorkerOneCache2 = new(workerfakes.FakeWorker) 183 compatibleWorkerOneCache2.SatisfiesReturns(true) 184 compatibleWorkerOneCache2.NameReturns("compatibleWorkerOneCache2") 185 186 compatibleWorkerTwoCaches = new(workerfakes.FakeWorker) 187 compatibleWorkerTwoCaches.SatisfiesReturns(true) 188 compatibleWorkerTwoCaches.NameReturns("compatibleWorkerTwoCaches") 189 190 compatibleWorkerNoCaches1 = new(workerfakes.FakeWorker) 191 compatibleWorkerNoCaches1.SatisfiesReturns(true) 192 compatibleWorkerNoCaches1.NameReturns("compatibleWorkerNoCaches1") 193 194 compatibleWorkerNoCaches2 = new(workerfakes.FakeWorker) 195 compatibleWorkerNoCaches2.SatisfiesReturns(true) 196 compatibleWorkerNoCaches2.NameReturns("compatibleWorkerNoCaches2") 197 }) 198 199 Context("with one having the most local caches", func() { 200 BeforeEach(func() { 201 workers = []Worker{ 202 compatibleWorkerOneCache1, 203 compatibleWorkerTwoCaches, 204 compatibleWorkerNoCaches1, 205 compatibleWorkerNoCaches2, 206 } 207 }) 208 209 It("creates it on the worker with the most caches", func() { 210 Expect(chooseErr).ToNot(HaveOccurred()) 211 Expect(chosenWorker).To(Equal(compatibleWorkerTwoCaches)) 212 }) 213 }) 214 215 Context("with multiple with the same amount of local caches", func() { 216 BeforeEach(func() { 217 workers = []Worker{ 218 compatibleWorkerOneCache1, 219 compatibleWorkerOneCache2, 220 compatibleWorkerNoCaches1, 221 compatibleWorkerNoCaches2, 222 } 223 }) 224 225 It("creates it on a random one of the two", func() { 226 Expect(chooseErr).ToNot(HaveOccurred()) 227 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2))) 228 229 workerChoiceCounts := map[Worker]int{} 230 231 for i := 0; i < 100; i++ { 232 worker, err := strategy.Choose( 233 logger, 234 workers, 235 spec, 236 ) 237 Expect(err).ToNot(HaveOccurred()) 238 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2))) 239 workerChoiceCounts[worker]++ 240 } 241 242 Expect(workerChoiceCounts[compatibleWorkerOneCache1]).ToNot(BeZero()) 243 Expect(workerChoiceCounts[compatibleWorkerOneCache2]).ToNot(BeZero()) 244 Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).To(BeZero()) 245 Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).To(BeZero()) 246 }) 247 }) 248 249 Context("with none having any local caches", func() { 250 BeforeEach(func() { 251 workers = []Worker{ 252 compatibleWorkerNoCaches1, 253 compatibleWorkerNoCaches2, 254 } 255 }) 256 257 It("creates it on a random one of them", func() { 258 Expect(chooseErr).ToNot(HaveOccurred()) 259 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2))) 260 261 workerChoiceCounts := map[Worker]int{} 262 263 for i := 0; i < 100; i++ { 264 worker, err := strategy.Choose( 265 logger, 266 workers, 267 spec, 268 ) 269 Expect(err).ToNot(HaveOccurred()) 270 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2))) 271 workerChoiceCounts[worker]++ 272 } 273 274 Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero()) 275 Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero()) 276 }) 277 }) 278 }) 279 }) 280 281 var _ = Describe("No strategy should equal to random strategy", func() { 282 Describe("Choose", func() { 283 JustBeforeEach(func() { 284 chosenWorker, chooseErr = strategy.Choose( 285 logger, 286 workers, 287 spec, 288 ) 289 }) 290 291 BeforeEach(func() { 292 strategy = NewRandomPlacementStrategy() 293 294 workers = []Worker{ 295 compatibleWorkerNoCaches1, 296 compatibleWorkerNoCaches2, 297 } 298 }) 299 300 It("creates it on a random one of them", func() { 301 Expect(chooseErr).ToNot(HaveOccurred()) 302 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2))) 303 304 workerChoiceCounts := map[Worker]int{} 305 306 for i := 0; i < 100; i++ { 307 worker, err := strategy.Choose( 308 logger, 309 workers, 310 spec, 311 ) 312 Expect(err).ToNot(HaveOccurred()) 313 Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2))) 314 workerChoiceCounts[worker]++ 315 } 316 317 Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero()) 318 Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero()) 319 }) 320 }) 321 }) 322 323 var _ = Describe("LimitActiveTasksPlacementStrategy", func() { 324 Describe("Choose", func() { 325 var compatibleWorker1 *workerfakes.FakeWorker 326 var compatibleWorker2 *workerfakes.FakeWorker 327 var compatibleWorker3 *workerfakes.FakeWorker 328 329 Context("when MaxActiveTasksPerWorker less than 0", func() { 330 BeforeEach(func() { 331 logger = lagertest.NewTestLogger("active-tasks-equal-placement-test") 332 strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: -1}) 333 }) 334 It("should fail", func() { 335 Expect(newStrategyError).To(HaveOccurred()) 336 Expect(newStrategyError).To(Equal(errors.New("max-active-tasks-per-worker must be greater or equal than 0"))) 337 Expect(strategy).To(BeNil()) 338 }) 339 }) 340 341 Context("when MaxActiveTasksPerWorker less than 0", func() { 342 BeforeEach(func() { 343 logger = lagertest.NewTestLogger("active-tasks-equal-placement-test") 344 strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: 0}) 345 Expect(newStrategyError).ToNot(HaveOccurred()) 346 347 compatibleWorker1 = new(workerfakes.FakeWorker) 348 compatibleWorker1.NameReturns("compatibleWorker1") 349 compatibleWorker2 = new(workerfakes.FakeWorker) 350 compatibleWorker2.NameReturns("compatibleWorker2") 351 compatibleWorker3 = new(workerfakes.FakeWorker) 352 compatibleWorker3.NameReturns("compatibleWorker3") 353 354 spec = ContainerSpec{ 355 ImageSpec: ImageSpec{ResourceType: "some-type"}, 356 357 Type: "task", 358 359 TeamID: 4567, 360 361 Inputs: []InputSource{}, 362 } 363 }) 364 365 Context("when there is only one worker with any amount of running tasks", func() { 366 BeforeEach(func() { 367 workers = []Worker{compatibleWorker1} 368 compatibleWorker1.ActiveTasksReturns(42, nil) 369 }) 370 371 It("picks that worker", func() { 372 chosenWorker, chooseErr = strategy.Choose( 373 logger, 374 workers, 375 spec, 376 ) 377 Expect(chooseErr).ToNot(HaveOccurred()) 378 Expect(chosenWorker).To(Equal(compatibleWorker1)) 379 }) 380 }) 381 382 Context("when there are multiple workers", func() { 383 BeforeEach(func() { 384 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 385 386 compatibleWorker1.ActiveTasksReturns(2, nil) 387 compatibleWorker2.ActiveTasksReturns(1, nil) 388 compatibleWorker3.ActiveTasksReturns(2, nil) 389 }) 390 391 It("a task picks the one with least amount of active tasks", func() { 392 Consistently(func() Worker { 393 chosenWorker, chooseErr = strategy.Choose( 394 logger, 395 workers, 396 spec, 397 ) 398 Expect(chooseErr).ToNot(HaveOccurred()) 399 return chosenWorker 400 }).Should(Equal(compatibleWorker2)) 401 }) 402 403 Context("when all the workers have the same number of active tasks", func() { 404 BeforeEach(func() { 405 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 406 compatibleWorker1.ActiveTasksReturns(1, nil) 407 compatibleWorker2.ActiveTasksReturns(1, nil) 408 compatibleWorker3.ActiveTasksReturns(1, nil) 409 }) 410 411 It("a task picks any of them", func() { 412 Consistently(func() Worker { 413 chosenWorker, chooseErr = strategy.Choose( 414 logger, 415 workers, 416 spec, 417 ) 418 Expect(chooseErr).ToNot(HaveOccurred()) 419 return chosenWorker 420 }).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker2), Equal(compatibleWorker3))) 421 }) 422 }) 423 }) 424 Context("when max-tasks-per-worker is set to 1", func() { 425 BeforeEach(func() { 426 strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: 1}) 427 Expect(newStrategyError).ToNot(HaveOccurred()) 428 }) 429 Context("when there are multiple workers", func() { 430 BeforeEach(func() { 431 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 432 433 compatibleWorker1.ActiveTasksReturns(1, nil) 434 compatibleWorker2.ActiveTasksReturns(0, nil) 435 compatibleWorker3.ActiveTasksReturns(1, nil) 436 }) 437 438 It("picks the worker with no active tasks", func() { 439 chosenWorker, chooseErr = strategy.Choose( 440 logger, 441 workers, 442 spec, 443 ) 444 Expect(chooseErr).ToNot(HaveOccurred()) 445 Expect(chosenWorker).To(Equal(compatibleWorker2)) 446 }) 447 }) 448 449 Context("when all workers have active tasks", func() { 450 BeforeEach(func() { 451 workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3} 452 453 compatibleWorker1.ActiveTasksReturns(1, nil) 454 compatibleWorker2.ActiveTasksReturns(1, nil) 455 compatibleWorker3.ActiveTasksReturns(1, nil) 456 }) 457 458 It("picks no worker", func() { 459 chosenWorker, chooseErr = strategy.Choose( 460 logger, 461 workers, 462 spec, 463 ) 464 Expect(chooseErr).ToNot(HaveOccurred()) 465 Expect(chosenWorker).To(BeNil()) 466 }) 467 Context("when the container is not of type 'task'", func() { 468 BeforeEach(func() { 469 spec.Type = "" 470 }) 471 It("picks any worker", func() { 472 Consistently(func() Worker { 473 chosenWorker, chooseErr = strategy.Choose( 474 logger, 475 workers, 476 spec, 477 ) 478 Expect(chooseErr).ToNot(HaveOccurred()) 479 return chosenWorker 480 }).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker2), Equal(compatibleWorker3))) 481 }) 482 }) 483 }) 484 }) 485 }) 486 }) 487 }) 488 489 var _ = Describe("ChainedPlacementStrategy #Choose", func() { 490 491 var someWorker1 *workerfakes.FakeWorker 492 var someWorker2 *workerfakes.FakeWorker 493 var someWorker3 *workerfakes.FakeWorker 494 495 BeforeEach(func() { 496 logger = lagertest.NewTestLogger("build-containers-equal-placement-test") 497 strategy, newStrategyError = NewContainerPlacementStrategy( 498 ContainerPlacementStrategyOptions{ 499 ContainerPlacementStrategy: []string{"fewest-build-containers", "volume-locality"}, 500 }) 501 Expect(newStrategyError).ToNot(HaveOccurred()) 502 someWorker1 = new(workerfakes.FakeWorker) 503 someWorker1.NameReturns("worker1") 504 someWorker2 = new(workerfakes.FakeWorker) 505 someWorker2.NameReturns("worker2") 506 someWorker3 = new(workerfakes.FakeWorker) 507 someWorker3.NameReturns("worker3") 508 509 spec = ContainerSpec{ 510 ImageSpec: ImageSpec{ResourceType: "some-type"}, 511 512 TeamID: 4567, 513 514 Inputs: []InputSource{}, 515 } 516 }) 517 518 Context("when there are multiple workers", func() { 519 BeforeEach(func() { 520 workers = []Worker{someWorker1, someWorker2, someWorker3} 521 522 someWorker1.BuildContainersReturns(30) 523 someWorker2.BuildContainersReturns(20) 524 someWorker3.BuildContainersReturns(10) 525 }) 526 527 It("picks the one with least amount of containers", func() { 528 Consistently(func() Worker { 529 chosenWorker, chooseErr = strategy.Choose( 530 logger, 531 workers, 532 spec, 533 ) 534 Expect(chooseErr).ToNot(HaveOccurred()) 535 return chosenWorker 536 }).Should(Equal(someWorker3)) 537 }) 538 539 Context("when there is more than one worker with the same number of build containers", func() { 540 BeforeEach(func() { 541 workers = []Worker{someWorker1, someWorker2, someWorker3} 542 someWorker1.BuildContainersReturns(10) 543 someWorker2.BuildContainersReturns(20) 544 someWorker3.BuildContainersReturns(10) 545 546 fakeInput1 := new(workerfakes.FakeInputSource) 547 fakeInput1AS := new(workerfakes.FakeArtifactSource) 548 fakeInput1AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) { 549 switch worker { 550 case someWorker3: 551 return new(workerfakes.FakeVolume), true, nil 552 default: 553 return nil, false, nil 554 } 555 } 556 fakeInput1.SourceReturns(fakeInput1AS) 557 558 spec = ContainerSpec{ 559 ImageSpec: ImageSpec{ResourceType: "some-type"}, 560 561 TeamID: 4567, 562 563 Inputs: []InputSource{ 564 fakeInput1, 565 }, 566 } 567 }) 568 It("picks the one with the most volumes", func() { 569 Consistently(func() Worker { 570 cWorker, cErr := strategy.Choose( 571 logger, 572 workers, 573 spec, 574 ) 575 Expect(cErr).ToNot(HaveOccurred()) 576 return cWorker 577 }).Should(Equal(someWorker3)) 578 579 }) 580 }) 581 582 }) 583 })