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 "github.com/sirupsen/logrus"
15 check "gopkg.in/check.v1"
19 logger = logrus.StandardLogger()
21 // arbitrary example container UUIDs
22 uuids = func() (r []string) {
23 for i := 0; i < 16; i++ {
24 r = append(r, test.ContainerUUID(i))
30 type stubQuotaError struct {
34 func (stubQuotaError) IsQuotaError() bool { return true }
36 type stubPool struct {
37 notify <-chan struct{}
38 unalloc map[arvados.InstanceType]int // idle+booting+unknown
39 idle map[arvados.InstanceType]int
40 running map[string]time.Time
43 creates []arvados.InstanceType
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 { return p.running }
52 func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
53 r := map[arvados.InstanceType]int{}
54 for it, n := range p.unalloc {
59 func (p *stubPool) Create(it arvados.InstanceType) error {
60 p.creates = append(p.creates, it)
62 return stubQuotaError{errors.New("quota")}
68 func (p *stubPool) KillContainer(uuid string) {
69 p.running[uuid] = time.Now()
71 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
75 func (p *stubPool) CountWorkers() map[worker.State]int {
76 return map[worker.State]int{
77 worker.StateBooting: len(p.unalloc) - len(p.idle),
78 worker.StateIdle: len(p.idle),
79 worker.StateRunning: len(p.running),
82 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
83 p.starts = append(p.starts, ctr.UUID)
89 p.running[ctr.UUID] = time.Time{}
93 var _ = check.Suite(&SchedulerSuite{})
95 type SchedulerSuite struct{}
97 // Assign priority=4 container to idle node. Create a new instance for
98 // the priority=3 container. Don't try to start any priority<3
99 // containers because priority=3 container didn't start
100 // immediately. Don't try to create any other nodes after the failed
102 func (*SchedulerSuite) TestUseIdleWorkers(c *check.C) {
104 ChooseType: func(ctr *arvados.Container) (arvados.InstanceType, error) {
105 return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
107 Containers: []arvados.Container{
109 UUID: test.ContainerUUID(1),
111 State: arvados.ContainerStateLocked,
112 RuntimeConstraints: arvados.RuntimeConstraints{
118 UUID: test.ContainerUUID(2),
120 State: arvados.ContainerStateLocked,
121 RuntimeConstraints: arvados.RuntimeConstraints{
127 UUID: test.ContainerUUID(3),
129 State: arvados.ContainerStateLocked,
130 RuntimeConstraints: arvados.RuntimeConstraints{
136 UUID: test.ContainerUUID(4),
138 State: arvados.ContainerStateLocked,
139 RuntimeConstraints: arvados.RuntimeConstraints{
148 unalloc: map[arvados.InstanceType]int{
149 test.InstanceType(1): 1,
150 test.InstanceType(2): 2,
152 idle: map[arvados.InstanceType]int{
153 test.InstanceType(1): 1,
154 test.InstanceType(2): 2,
156 running: map[string]time.Time{},
159 New(logger, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
160 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(1)})
161 c.Check(pool.starts, check.DeepEquals, []string{test.ContainerUUID(4)})
162 c.Check(pool.running, check.HasLen, 1)
163 for uuid := range pool.running {
164 c.Check(uuid, check.Equals, uuids[4])
168 // If Create() fails, shutdown some nodes, and don't call Create()
169 // again. Don't call Create() at all if AtQuota() is true.
170 func (*SchedulerSuite) TestShutdownAtQuota(c *check.C) {
171 for quota := 0; quota < 2; quota++ {
172 c.Logf("quota=%d", quota)
173 shouldCreate := []arvados.InstanceType{}
174 for i := 0; i < quota; i++ {
175 shouldCreate = append(shouldCreate, test.InstanceType(3))
178 ChooseType: func(ctr *arvados.Container) (arvados.InstanceType, error) {
179 return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
181 Containers: []arvados.Container{
183 UUID: test.ContainerUUID(2),
185 State: arvados.ContainerStateLocked,
186 RuntimeConstraints: arvados.RuntimeConstraints{
192 UUID: test.ContainerUUID(3),
194 State: arvados.ContainerStateLocked,
195 RuntimeConstraints: arvados.RuntimeConstraints{
205 unalloc: map[arvados.InstanceType]int{
206 test.InstanceType(2): 2,
208 idle: map[arvados.InstanceType]int{
209 test.InstanceType(2): 2,
211 running: map[string]time.Time{},
212 creates: []arvados.InstanceType{},
216 New(logger, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
217 c.Check(pool.creates, check.DeepEquals, shouldCreate)
218 c.Check(pool.starts, check.DeepEquals, []string{})
219 c.Check(pool.shutdowns, check.Not(check.Equals), 0)
223 // Start lower-priority containers while waiting for new/existing
224 // workers to come up for higher-priority containers.
225 func (*SchedulerSuite) TestStartWhileCreating(c *check.C) {
227 unalloc: map[arvados.InstanceType]int{
228 test.InstanceType(1): 2,
229 test.InstanceType(2): 2,
231 idle: map[arvados.InstanceType]int{
232 test.InstanceType(1): 1,
233 test.InstanceType(2): 1,
235 running: map[string]time.Time{},
239 ChooseType: func(ctr *arvados.Container) (arvados.InstanceType, error) {
240 return test.InstanceType(ctr.RuntimeConstraints.VCPUs), nil
242 Containers: []arvados.Container{
244 // create a new worker
245 UUID: test.ContainerUUID(1),
247 State: arvados.ContainerStateLocked,
248 RuntimeConstraints: arvados.RuntimeConstraints{
254 // tentatively map to unalloc worker
255 UUID: test.ContainerUUID(2),
257 State: arvados.ContainerStateLocked,
258 RuntimeConstraints: arvados.RuntimeConstraints{
264 // start now on idle worker
265 UUID: test.ContainerUUID(3),
267 State: arvados.ContainerStateLocked,
268 RuntimeConstraints: arvados.RuntimeConstraints{
274 // create a new worker
275 UUID: test.ContainerUUID(4),
277 State: arvados.ContainerStateLocked,
278 RuntimeConstraints: arvados.RuntimeConstraints{
284 // tentatively map to unalloc worker
285 UUID: test.ContainerUUID(5),
287 State: arvados.ContainerStateLocked,
288 RuntimeConstraints: arvados.RuntimeConstraints{
294 // start now on idle worker
295 UUID: test.ContainerUUID(6),
297 State: arvados.ContainerStateLocked,
298 RuntimeConstraints: arvados.RuntimeConstraints{
306 New(logger, &queue, &pool, time.Millisecond, time.Millisecond).runQueue()
307 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{test.InstanceType(2), test.InstanceType(1)})
308 c.Check(pool.starts, check.DeepEquals, []string{uuids[6], uuids[5], uuids[3], uuids[2]})
309 running := map[string]bool{}
310 for uuid, t := range pool.running {
312 running[uuid] = false
317 c.Check(running, check.DeepEquals, map[string]bool{uuids[3]: false, uuids[6]: false})