1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
12 "git.arvados.org/arvados.git/lib/dispatchcloud/test"
13 "git.arvados.org/arvados.git/lib/dispatchcloud/worker"
14 "git.arvados.org/arvados.git/sdk/go/arvados"
15 "git.arvados.org/arvados.git/sdk/go/ctxlog"
16 check "gopkg.in/check.v1"
20 // arbitrary example container UUIDs
21 uuids = func() (r []string) {
22 for i := 0; i < 16; i++ {
23 r = append(r, test.ContainerUUID(i))
29 type stubQuotaError struct {
33 func (stubQuotaError) IsQuotaError() bool { return true }
35 type stubPool struct {
36 notify <-chan struct{}
37 unalloc map[arvados.InstanceType]int // idle+booting+unknown
38 idle map[arvados.InstanceType]int
39 unknown map[arvados.InstanceType]int
40 running map[string]time.Time
43 creates []arvados.InstanceType
49 func (p *stubPool) AtQuota() bool { return p.atQuota }
50 func (p *stubPool) Subscribe() <-chan struct{} { return p.notify }
51 func (p *stubPool) Unsubscribe(<-chan struct{}) {}
52 func (p *stubPool) Running() map[string]time.Time {
55 r := map[string]time.Time{}
56 for k, v := range p.running {
61 func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
64 r := map[arvados.InstanceType]int{}
65 for it, n := range p.unalloc {
66 r[it] = n - p.unknown[it]
70 func (p *stubPool) Create(it arvados.InstanceType) bool {
73 p.creates = append(p.creates, it)
81 func (p *stubPool) ForgetContainer(uuid string) {
83 func (p *stubPool) KillContainer(uuid, reason string) bool {
86 defer delete(p.running, uuid)
87 t, ok := p.running[uuid]
88 return ok && t.IsZero()
90 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
94 func (p *stubPool) CountWorkers() map[worker.State]int {
97 return map[worker.State]int{
98 worker.StateBooting: len(p.unalloc) - len(p.idle),
99 worker.StateIdle: len(p.idle),
100 worker.StateRunning: len(p.running),
101 worker.StateUnknown: len(p.unknown),
104 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
107 p.starts = append(p.starts, ctr.UUID)
113 p.running[ctr.UUID] = time.Time{}
117 func chooseType(ctr *arvados.Container) (arvados.InstanceType, error) {
118 return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
121 var _ = check.Suite(&SchedulerSuite{})
123 type SchedulerSuite struct{}
125 // Assign priority=4 container to idle node. Create a new instance for
126 // the priority=3 container. Don't try to start any priority<3
127 // containers because priority=3 container didn't start
128 // immediately. Don't try to create any other nodes after the failed
130 func (*SchedulerSuite) TestUseIdleWorkers(c *check.C) {
131 ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
133 ChooseType: chooseType,
134 Containers: []arvados.Container{
136 UUID: test.ContainerUUID(1),
138 State: arvados.ContainerStateLocked,
139 RuntimeConstraints: arvados.RuntimeConstraints{
145 UUID: test.ContainerUUID(2),
147 State: arvados.ContainerStateLocked,
148 RuntimeConstraints: arvados.RuntimeConstraints{
154 UUID: test.ContainerUUID(3),
156 State: arvados.ContainerStateLocked,
157 RuntimeConstraints: arvados.RuntimeConstraints{
163 UUID: test.ContainerUUID(4),
165 State: arvados.ContainerStateLocked,
166 RuntimeConstraints: arvados.RuntimeConstraints{
175 unalloc: map[arvados.InstanceType]int{
176 test.InstanceType(1): 1,
177 test.InstanceType(2): 2,
179 idle: map[arvados.InstanceType]int{
180 test.InstanceType(1): 1,
181 test.InstanceType(2): 2,
183 running: map[string]time.Time{},
186 New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
187 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(1)})
188 c.Check(pool.starts, check.DeepEquals, []string{test.ContainerUUID(4)})
189 c.Check(pool.running, check.HasLen, 1)
190 for uuid := range pool.running {
191 c.Check(uuid, check.Equals, uuids[4])
195 // If Create() fails, shutdown some nodes, and don't call Create()
196 // again. Don't call Create() at all if AtQuota() is true.
197 func (*SchedulerSuite) TestShutdownAtQuota(c *check.C) {
198 ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
199 for quota := 0; quota < 2; quota++ {
200 c.Logf("quota=%d", quota)
201 shouldCreate := []arvados.InstanceType{}
202 for i := 0; i < quota; i++ {
203 shouldCreate = append(shouldCreate, test.InstanceType(3))
206 ChooseType: chooseType,
207 Containers: []arvados.Container{
209 UUID: test.ContainerUUID(2),
211 State: arvados.ContainerStateLocked,
212 RuntimeConstraints: arvados.RuntimeConstraints{
218 UUID: test.ContainerUUID(3),
220 State: arvados.ContainerStateLocked,
221 RuntimeConstraints: arvados.RuntimeConstraints{
231 unalloc: map[arvados.InstanceType]int{
232 test.InstanceType(2): 2,
234 idle: map[arvados.InstanceType]int{
235 test.InstanceType(2): 2,
237 running: map[string]time.Time{},
238 creates: []arvados.InstanceType{},
242 New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
243 c.Check(pool.creates, check.DeepEquals, shouldCreate)
244 c.Check(pool.starts, check.DeepEquals, []string{})
245 c.Check(pool.shutdowns, check.Not(check.Equals), 0)
249 // Start lower-priority containers while waiting for new/existing
250 // workers to come up for higher-priority containers.
251 func (*SchedulerSuite) TestStartWhileCreating(c *check.C) {
252 ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
254 unalloc: map[arvados.InstanceType]int{
255 test.InstanceType(1): 2,
256 test.InstanceType(2): 2,
258 idle: map[arvados.InstanceType]int{
259 test.InstanceType(1): 1,
260 test.InstanceType(2): 1,
262 running: map[string]time.Time{},
266 ChooseType: chooseType,
267 Containers: []arvados.Container{
269 // create a new worker
270 UUID: test.ContainerUUID(1),
272 State: arvados.ContainerStateLocked,
273 RuntimeConstraints: arvados.RuntimeConstraints{
279 // tentatively map to unalloc worker
280 UUID: test.ContainerUUID(2),
282 State: arvados.ContainerStateLocked,
283 RuntimeConstraints: arvados.RuntimeConstraints{
289 // start now on idle worker
290 UUID: test.ContainerUUID(3),
292 State: arvados.ContainerStateLocked,
293 RuntimeConstraints: arvados.RuntimeConstraints{
299 // create a new worker
300 UUID: test.ContainerUUID(4),
302 State: arvados.ContainerStateLocked,
303 RuntimeConstraints: arvados.RuntimeConstraints{
309 // tentatively map to unalloc worker
310 UUID: test.ContainerUUID(5),
312 State: arvados.ContainerStateLocked,
313 RuntimeConstraints: arvados.RuntimeConstraints{
319 // start now on idle worker
320 UUID: test.ContainerUUID(6),
322 State: arvados.ContainerStateLocked,
323 RuntimeConstraints: arvados.RuntimeConstraints{
331 New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
332 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(2), test.InstanceType(1)})
333 c.Check(pool.starts, check.DeepEquals, []string{uuids[6], uuids[5], uuids[3], uuids[2]})
334 running := map[string]bool{}
335 for uuid, t := range pool.running {
337 running[uuid] = false
342 c.Check(running, check.DeepEquals, map[string]bool{uuids[3]: false, uuids[6]: false})
345 func (*SchedulerSuite) TestKillNonexistentContainer(c *check.C) {
346 ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
348 unalloc: map[arvados.InstanceType]int{
349 test.InstanceType(2): 0,
351 idle: map[arvados.InstanceType]int{
352 test.InstanceType(2): 0,
354 running: map[string]time.Time{
355 test.ContainerUUID(2): {},
359 ChooseType: chooseType,
360 Containers: []arvados.Container{
362 // create a new worker
363 UUID: test.ContainerUUID(1),
365 State: arvados.ContainerStateLocked,
366 RuntimeConstraints: arvados.RuntimeConstraints{
374 sch := New(ctx, &queue, &pool, time.Millisecond, time.Millisecond)
375 c.Check(pool.running, check.HasLen, 1)
377 for deadline := time.Now().Add(time.Second); len(pool.Running()) > 0 && time.Now().Before(deadline); time.Sleep(time.Millisecond) {
379 c.Check(pool.Running(), check.HasLen, 0)