1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
11 "git.curoverse.com/arvados.git/lib/dispatchcloud/test"
12 "git.curoverse.com/arvados.git/lib/dispatchcloud/worker"
13 "git.curoverse.com/arvados.git/sdk/go/arvados"
14 check "gopkg.in/check.v1"
18 // arbitrary example container UUIDs
19 uuids = func() (r []string) {
20 for i := 0; i < 16; i++ {
21 r = append(r, test.ContainerUUID(i))
27 type stubQuotaError struct {
31 func (stubQuotaError) IsQuotaError() bool { return true }
33 type stubPool struct {
34 notify <-chan struct{}
35 unalloc map[arvados.InstanceType]int // idle+booting+unknown
36 idle map[arvados.InstanceType]int
37 running map[string]time.Time
40 creates []arvados.InstanceType
46 func (p *stubPool) AtQuota() bool { return p.atQuota }
47 func (p *stubPool) Subscribe() <-chan struct{} { return p.notify }
48 func (p *stubPool) Unsubscribe(<-chan struct{}) {}
49 func (p *stubPool) Running() map[string]time.Time {
52 r := map[string]time.Time{}
53 for k, v := range p.running {
58 func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
61 r := map[arvados.InstanceType]int{}
62 for it, n := range p.unalloc {
67 func (p *stubPool) Create(it arvados.InstanceType) bool {
70 p.creates = append(p.creates, it)
78 func (p *stubPool) KillContainer(uuid string) {
81 delete(p.running, uuid)
83 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
87 func (p *stubPool) CountWorkers() map[worker.State]int {
90 return map[worker.State]int{
91 worker.StateBooting: len(p.unalloc) - len(p.idle),
92 worker.StateIdle: len(p.idle),
93 worker.StateRunning: len(p.running),
96 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
99 p.starts = append(p.starts, ctr.UUID)
105 p.running[ctr.UUID] = time.Time{}
109 func chooseType(ctr *arvados.Container) (arvados.InstanceType, error) {
110 return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
113 var _ = check.Suite(&SchedulerSuite{})
115 type SchedulerSuite struct{}
117 // Assign priority=4 container to idle node. Create a new instance for
118 // the priority=3 container. Don't try to start any priority<3
119 // containers because priority=3 container didn't start
120 // immediately. Don't try to create any other nodes after the failed
122 func (*SchedulerSuite) TestUseIdleWorkers(c *check.C) {
124 ChooseType: chooseType,
125 Containers: []arvados.Container{
127 UUID: test.ContainerUUID(1),
129 State: arvados.ContainerStateLocked,
130 RuntimeConstraints: arvados.RuntimeConstraints{
136 UUID: test.ContainerUUID(2),
138 State: arvados.ContainerStateLocked,
139 RuntimeConstraints: arvados.RuntimeConstraints{
145 UUID: test.ContainerUUID(3),
147 State: arvados.ContainerStateLocked,
148 RuntimeConstraints: arvados.RuntimeConstraints{
154 UUID: test.ContainerUUID(4),
156 State: arvados.ContainerStateLocked,
157 RuntimeConstraints: arvados.RuntimeConstraints{
166 unalloc: map[arvados.InstanceType]int{
167 test.InstanceType(1): 1,
168 test.InstanceType(2): 2,
170 idle: map[arvados.InstanceType]int{
171 test.InstanceType(1): 1,
172 test.InstanceType(2): 2,
174 running: map[string]time.Time{},
177 New(test.Logger(), &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
178 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(1)})
179 c.Check(pool.starts, check.DeepEquals, []string{test.ContainerUUID(4)})
180 c.Check(pool.running, check.HasLen, 1)
181 for uuid := range pool.running {
182 c.Check(uuid, check.Equals, uuids[4])
186 // If Create() fails, shutdown some nodes, and don't call Create()
187 // again. Don't call Create() at all if AtQuota() is true.
188 func (*SchedulerSuite) TestShutdownAtQuota(c *check.C) {
189 for quota := 0; quota < 2; quota++ {
190 c.Logf("quota=%d", quota)
191 shouldCreate := []arvados.InstanceType{}
192 for i := 0; i < quota; i++ {
193 shouldCreate = append(shouldCreate, test.InstanceType(3))
196 ChooseType: chooseType,
197 Containers: []arvados.Container{
199 UUID: test.ContainerUUID(2),
201 State: arvados.ContainerStateLocked,
202 RuntimeConstraints: arvados.RuntimeConstraints{
208 UUID: test.ContainerUUID(3),
210 State: arvados.ContainerStateLocked,
211 RuntimeConstraints: arvados.RuntimeConstraints{
221 unalloc: map[arvados.InstanceType]int{
222 test.InstanceType(2): 2,
224 idle: map[arvados.InstanceType]int{
225 test.InstanceType(2): 2,
227 running: map[string]time.Time{},
228 creates: []arvados.InstanceType{},
232 New(test.Logger(), &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
233 c.Check(pool.creates, check.DeepEquals, shouldCreate)
234 c.Check(pool.starts, check.DeepEquals, []string{})
235 c.Check(pool.shutdowns, check.Not(check.Equals), 0)
239 // Start lower-priority containers while waiting for new/existing
240 // workers to come up for higher-priority containers.
241 func (*SchedulerSuite) TestStartWhileCreating(c *check.C) {
243 unalloc: map[arvados.InstanceType]int{
244 test.InstanceType(1): 2,
245 test.InstanceType(2): 2,
247 idle: map[arvados.InstanceType]int{
248 test.InstanceType(1): 1,
249 test.InstanceType(2): 1,
251 running: map[string]time.Time{},
255 ChooseType: chooseType,
256 Containers: []arvados.Container{
258 // create a new worker
259 UUID: test.ContainerUUID(1),
261 State: arvados.ContainerStateLocked,
262 RuntimeConstraints: arvados.RuntimeConstraints{
268 // tentatively map to unalloc worker
269 UUID: test.ContainerUUID(2),
271 State: arvados.ContainerStateLocked,
272 RuntimeConstraints: arvados.RuntimeConstraints{
278 // start now on idle worker
279 UUID: test.ContainerUUID(3),
281 State: arvados.ContainerStateLocked,
282 RuntimeConstraints: arvados.RuntimeConstraints{
288 // create a new worker
289 UUID: test.ContainerUUID(4),
291 State: arvados.ContainerStateLocked,
292 RuntimeConstraints: arvados.RuntimeConstraints{
298 // tentatively map to unalloc worker
299 UUID: test.ContainerUUID(5),
301 State: arvados.ContainerStateLocked,
302 RuntimeConstraints: arvados.RuntimeConstraints{
308 // start now on idle worker
309 UUID: test.ContainerUUID(6),
311 State: arvados.ContainerStateLocked,
312 RuntimeConstraints: arvados.RuntimeConstraints{
320 New(test.Logger(), &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
321 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(2), test.InstanceType(1)})
322 c.Check(pool.starts, check.DeepEquals, []string{uuids[6], uuids[5], uuids[3], uuids[2]})
323 running := map[string]bool{}
324 for uuid, t := range pool.running {
326 running[uuid] = false
331 c.Check(running, check.DeepEquals, map[string]bool{uuids[3]: false, uuids[6]: false})