Add 'sdk/java-v2/' from commit '55f103e336ca9fb8bf1720d2ef4ee8dd4e221118'
[arvados.git] / lib / dispatchcloud / scheduler / run_queue_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package scheduler
6
7 import (
8         "context"
9         "sync"
10         "time"
11
12         "git.curoverse.com/arvados.git/lib/dispatchcloud/test"
13         "git.curoverse.com/arvados.git/lib/dispatchcloud/worker"
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15         "git.curoverse.com/arvados.git/sdk/go/ctxlog"
16         check "gopkg.in/check.v1"
17 )
18
19 var (
20         // arbitrary example container UUIDs
21         uuids = func() (r []string) {
22                 for i := 0; i < 16; i++ {
23                         r = append(r, test.ContainerUUID(i))
24                 }
25                 return
26         }()
27 )
28
29 type stubQuotaError struct {
30         error
31 }
32
33 func (stubQuotaError) IsQuotaError() bool { return true }
34
35 type stubPool struct {
36         notify    <-chan struct{}
37         unalloc   map[arvados.InstanceType]int // idle+booting+unknown
38         idle      map[arvados.InstanceType]int
39         running   map[string]time.Time
40         atQuota   bool
41         canCreate int
42         creates   []arvados.InstanceType
43         starts    []string
44         shutdowns int
45         sync.Mutex
46 }
47
48 func (p *stubPool) AtQuota() bool               { return p.atQuota }
49 func (p *stubPool) Subscribe() <-chan struct{}  { return p.notify }
50 func (p *stubPool) Unsubscribe(<-chan struct{}) {}
51 func (p *stubPool) Running() map[string]time.Time {
52         p.Lock()
53         defer p.Unlock()
54         r := map[string]time.Time{}
55         for k, v := range p.running {
56                 r[k] = v
57         }
58         return r
59 }
60 func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
61         p.Lock()
62         defer p.Unlock()
63         r := map[arvados.InstanceType]int{}
64         for it, n := range p.unalloc {
65                 r[it] = n
66         }
67         return r
68 }
69 func (p *stubPool) Create(it arvados.InstanceType) bool {
70         p.Lock()
71         defer p.Unlock()
72         p.creates = append(p.creates, it)
73         if p.canCreate < 1 {
74                 return false
75         }
76         p.canCreate--
77         p.unalloc[it]++
78         return true
79 }
80 func (p *stubPool) KillContainer(uuid string) {
81         p.Lock()
82         defer p.Unlock()
83         delete(p.running, uuid)
84 }
85 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
86         p.shutdowns++
87         return false
88 }
89 func (p *stubPool) CountWorkers() map[worker.State]int {
90         p.Lock()
91         defer p.Unlock()
92         return map[worker.State]int{
93                 worker.StateBooting: len(p.unalloc) - len(p.idle),
94                 worker.StateIdle:    len(p.idle),
95                 worker.StateRunning: len(p.running),
96         }
97 }
98 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
99         p.Lock()
100         defer p.Unlock()
101         p.starts = append(p.starts, ctr.UUID)
102         if p.idle[it] == 0 {
103                 return false
104         }
105         p.idle[it]--
106         p.unalloc[it]--
107         p.running[ctr.UUID] = time.Time{}
108         return true
109 }
110
111 func chooseType(ctr *arvados.Container) (arvados.InstanceType, error) {
112         return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
113 }
114
115 var _ = check.Suite(&SchedulerSuite{})
116
117 type SchedulerSuite struct{}
118
119 // Assign priority=4 container to idle node. Create a new instance for
120 // the priority=3 container. Don't try to start any priority<3
121 // containers because priority=3 container didn't start
122 // immediately. Don't try to create any other nodes after the failed
123 // create.
124 func (*SchedulerSuite) TestUseIdleWorkers(c *check.C) {
125         ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
126         queue := test.Queue{
127                 ChooseType: chooseType,
128                 Containers: []arvados.Container{
129                         {
130                                 UUID:     test.ContainerUUID(1),
131                                 Priority: 1,
132                                 State:    arvados.ContainerStateLocked,
133                                 RuntimeConstraints: arvados.RuntimeConstraints{
134                                         VCPUs: 1,
135                                         RAM:   1 << 30,
136                                 },
137                         },
138                         {
139                                 UUID:     test.ContainerUUID(2),
140                                 Priority: 2,
141                                 State:    arvados.ContainerStateLocked,
142                                 RuntimeConstraints: arvados.RuntimeConstraints{
143                                         VCPUs: 1,
144                                         RAM:   1 << 30,
145                                 },
146                         },
147                         {
148                                 UUID:     test.ContainerUUID(3),
149                                 Priority: 3,
150                                 State:    arvados.ContainerStateLocked,
151                                 RuntimeConstraints: arvados.RuntimeConstraints{
152                                         VCPUs: 1,
153                                         RAM:   1 << 30,
154                                 },
155                         },
156                         {
157                                 UUID:     test.ContainerUUID(4),
158                                 Priority: 4,
159                                 State:    arvados.ContainerStateLocked,
160                                 RuntimeConstraints: arvados.RuntimeConstraints{
161                                         VCPUs: 1,
162                                         RAM:   1 << 30,
163                                 },
164                         },
165                 },
166         }
167         queue.Update()
168         pool := stubPool{
169                 unalloc: map[arvados.InstanceType]int{
170                         test.InstanceType(1): 1,
171                         test.InstanceType(2): 2,
172                 },
173                 idle: map[arvados.InstanceType]int{
174                         test.InstanceType(1): 1,
175                         test.InstanceType(2): 2,
176                 },
177                 running:   map[string]time.Time{},
178                 canCreate: 0,
179         }
180         New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
181         c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(1)})
182         c.Check(pool.starts, check.DeepEquals, []string{test.ContainerUUID(4)})
183         c.Check(pool.running, check.HasLen, 1)
184         for uuid := range pool.running {
185                 c.Check(uuid, check.Equals, uuids[4])
186         }
187 }
188
189 // If Create() fails, shutdown some nodes, and don't call Create()
190 // again.  Don't call Create() at all if AtQuota() is true.
191 func (*SchedulerSuite) TestShutdownAtQuota(c *check.C) {
192         ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
193         for quota := 0; quota < 2; quota++ {
194                 c.Logf("quota=%d", quota)
195                 shouldCreate := []arvados.InstanceType{}
196                 for i := 0; i < quota; i++ {
197                         shouldCreate = append(shouldCreate, test.InstanceType(3))
198                 }
199                 queue := test.Queue{
200                         ChooseType: chooseType,
201                         Containers: []arvados.Container{
202                                 {
203                                         UUID:     test.ContainerUUID(2),
204                                         Priority: 2,
205                                         State:    arvados.ContainerStateLocked,
206                                         RuntimeConstraints: arvados.RuntimeConstraints{
207                                                 VCPUs: 2,
208                                                 RAM:   2 << 30,
209                                         },
210                                 },
211                                 {
212                                         UUID:     test.ContainerUUID(3),
213                                         Priority: 3,
214                                         State:    arvados.ContainerStateLocked,
215                                         RuntimeConstraints: arvados.RuntimeConstraints{
216                                                 VCPUs: 3,
217                                                 RAM:   3 << 30,
218                                         },
219                                 },
220                         },
221                 }
222                 queue.Update()
223                 pool := stubPool{
224                         atQuota: quota == 0,
225                         unalloc: map[arvados.InstanceType]int{
226                                 test.InstanceType(2): 2,
227                         },
228                         idle: map[arvados.InstanceType]int{
229                                 test.InstanceType(2): 2,
230                         },
231                         running:   map[string]time.Time{},
232                         creates:   []arvados.InstanceType{},
233                         starts:    []string{},
234                         canCreate: 0,
235                 }
236                 New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
237                 c.Check(pool.creates, check.DeepEquals, shouldCreate)
238                 c.Check(pool.starts, check.DeepEquals, []string{})
239                 c.Check(pool.shutdowns, check.Not(check.Equals), 0)
240         }
241 }
242
243 // Start lower-priority containers while waiting for new/existing
244 // workers to come up for higher-priority containers.
245 func (*SchedulerSuite) TestStartWhileCreating(c *check.C) {
246         ctx := ctxlog.Context(context.Background(), ctxlog.TestLogger(c))
247         pool := stubPool{
248                 unalloc: map[arvados.InstanceType]int{
249                         test.InstanceType(1): 2,
250                         test.InstanceType(2): 2,
251                 },
252                 idle: map[arvados.InstanceType]int{
253                         test.InstanceType(1): 1,
254                         test.InstanceType(2): 1,
255                 },
256                 running:   map[string]time.Time{},
257                 canCreate: 4,
258         }
259         queue := test.Queue{
260                 ChooseType: chooseType,
261                 Containers: []arvados.Container{
262                         {
263                                 // create a new worker
264                                 UUID:     test.ContainerUUID(1),
265                                 Priority: 1,
266                                 State:    arvados.ContainerStateLocked,
267                                 RuntimeConstraints: arvados.RuntimeConstraints{
268                                         VCPUs: 1,
269                                         RAM:   1 << 30,
270                                 },
271                         },
272                         {
273                                 // tentatively map to unalloc worker
274                                 UUID:     test.ContainerUUID(2),
275                                 Priority: 2,
276                                 State:    arvados.ContainerStateLocked,
277                                 RuntimeConstraints: arvados.RuntimeConstraints{
278                                         VCPUs: 1,
279                                         RAM:   1 << 30,
280                                 },
281                         },
282                         {
283                                 // start now on idle worker
284                                 UUID:     test.ContainerUUID(3),
285                                 Priority: 3,
286                                 State:    arvados.ContainerStateLocked,
287                                 RuntimeConstraints: arvados.RuntimeConstraints{
288                                         VCPUs: 1,
289                                         RAM:   1 << 30,
290                                 },
291                         },
292                         {
293                                 // create a new worker
294                                 UUID:     test.ContainerUUID(4),
295                                 Priority: 4,
296                                 State:    arvados.ContainerStateLocked,
297                                 RuntimeConstraints: arvados.RuntimeConstraints{
298                                         VCPUs: 2,
299                                         RAM:   2 << 30,
300                                 },
301                         },
302                         {
303                                 // tentatively map to unalloc worker
304                                 UUID:     test.ContainerUUID(5),
305                                 Priority: 5,
306                                 State:    arvados.ContainerStateLocked,
307                                 RuntimeConstraints: arvados.RuntimeConstraints{
308                                         VCPUs: 2,
309                                         RAM:   2 << 30,
310                                 },
311                         },
312                         {
313                                 // start now on idle worker
314                                 UUID:     test.ContainerUUID(6),
315                                 Priority: 6,
316                                 State:    arvados.ContainerStateLocked,
317                                 RuntimeConstraints: arvados.RuntimeConstraints{
318                                         VCPUs: 2,
319                                         RAM:   2 << 30,
320                                 },
321                         },
322                 },
323         }
324         queue.Update()
325         New(ctx, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
326         c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(2), test.InstanceType(1)})
327         c.Check(pool.starts, check.DeepEquals, []string{uuids[6], uuids[5], uuids[3], uuids[2]})
328         running := map[string]bool{}
329         for uuid, t := range pool.running {
330                 if t.IsZero() {
331                         running[uuid] = false
332                 } else {
333                         running[uuid] = true
334                 }
335         }
336         c.Check(running, check.DeepEquals, map[string]bool{uuids[3]: false, uuids[6]: false})
337 }