1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
11 "git.curoverse.com/arvados.git/lib/dispatchcloud/container"
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 "github.com/Sirupsen/logrus"
16 check "gopkg.in/check.v1"
20 logger = logrus.StandardLogger()
22 // arbitrary example instance types
23 types = func() (r []arvados.InstanceType) {
24 for i := 0; i < 16; i++ {
25 r = append(r, test.InstanceType(i))
30 // arbitrary example container UUIDs
31 uuids = func() (r []string) {
32 for i := 0; i < 16; i++ {
33 r = append(r, test.ContainerUUID(i))
39 type stubQueue struct {
40 ents map[string]container.QueueEnt
43 func (q *stubQueue) Entries() map[string]container.QueueEnt {
46 func (q *stubQueue) Lock(uuid string) error {
47 return q.setState(uuid, arvados.ContainerStateLocked)
49 func (q *stubQueue) Unlock(uuid string) error {
50 return q.setState(uuid, arvados.ContainerStateQueued)
52 func (q *stubQueue) Get(uuid string) (arvados.Container, bool) {
53 ent, ok := q.ents[uuid]
54 return ent.Container, ok
56 func (q *stubQueue) setState(uuid string, state arvados.ContainerState) error {
57 ent, ok := q.ents[uuid]
59 return fmt.Errorf("no such ent: %q", uuid)
61 ent.Container.State = state
66 type stubQuotaError struct {
70 func (stubQuotaError) IsQuotaError() bool { return true }
72 type stubPool struct {
73 notify <-chan struct{}
74 unalloc map[arvados.InstanceType]int // idle+booting+unknown
75 idle map[arvados.InstanceType]int
76 running map[string]bool
79 creates []arvados.InstanceType
84 func (p *stubPool) AtQuota() bool { return p.atQuota }
85 func (p *stubPool) Subscribe() <-chan struct{} { return p.notify }
86 func (p *stubPool) Unsubscribe(<-chan struct{}) {}
87 func (p *stubPool) Running() map[string]bool { return p.running }
88 func (p *stubPool) Unallocated() map[arvados.InstanceType]int {
89 r := map[arvados.InstanceType]int{}
90 for it, n := range p.unalloc {
95 func (p *stubPool) Create(it arvados.InstanceType) error {
96 p.creates = append(p.creates, it)
98 return stubQuotaError{errors.New("quota")}
104 func (p *stubPool) Shutdown(arvados.InstanceType) bool {
108 func (p *stubPool) Workers() map[worker.State]int {
109 return map[worker.State]int{
110 worker.StateBooting: len(p.unalloc) - len(p.idle),
111 worker.StateRunning: len(p.idle) - len(p.running),
114 func (p *stubPool) StartContainer(it arvados.InstanceType, ctr arvados.Container) bool {
115 p.starts = append(p.starts, ctr.UUID)
121 p.running[ctr.UUID] = true
125 var _ = check.Suite(&SchedulerSuite{})
127 type SchedulerSuite struct{}
129 // Map priority=4 container to idle node. Create a new instance for
130 // the priority=3 container. Don't try to start any priority<3
131 // containers because priority=3 container didn't start
132 // immediately. Don't try to create any other nodes after the failed
134 func (*SchedulerSuite) TestMapIdle(c *check.C) {
136 ents: map[string]container.QueueEnt{
138 Container: arvados.Container{UUID: uuids[1], Priority: 1, State: arvados.ContainerStateQueued},
139 InstanceType: types[1],
142 Container: arvados.Container{UUID: uuids[2], Priority: 2, State: arvados.ContainerStateQueued},
143 InstanceType: types[1],
146 Container: arvados.Container{UUID: uuids[3], Priority: 3, State: arvados.ContainerStateQueued},
147 InstanceType: types[1],
150 Container: arvados.Container{UUID: uuids[4], Priority: 4, State: arvados.ContainerStateQueued},
151 InstanceType: types[1],
156 unalloc: map[arvados.InstanceType]int{
160 idle: map[arvados.InstanceType]int{
164 running: map[string]bool{},
167 Map(logger, &queue, &pool)
168 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{types[1]})
169 c.Check(pool.starts, check.DeepEquals, []string{uuids[4], uuids[3]})
170 c.Check(pool.running, check.DeepEquals, map[string]bool{uuids[4]: true})
173 // Shutdown some nodes if Create() fails -- and without even calling
174 // Create(), if AtQuota() is true.
175 func (*SchedulerSuite) TestMapShutdownAtQuota(c *check.C) {
176 for quota := 0; quota < 2; quota++ {
177 shouldCreate := types[1 : 1+quota]
179 ents: map[string]container.QueueEnt{
181 Container: arvados.Container{UUID: uuids[1], Priority: 1, State: arvados.ContainerStateQueued},
182 InstanceType: types[1],
188 unalloc: map[arvados.InstanceType]int{
191 idle: map[arvados.InstanceType]int{
194 running: map[string]bool{},
195 creates: []arvados.InstanceType{},
199 Map(logger, &queue, &pool)
200 c.Check(pool.creates, check.DeepEquals, shouldCreate)
201 c.Check(pool.starts, check.DeepEquals, []string{})
202 c.Check(pool.shutdowns, check.Not(check.Equals), 0)
206 // Start lower-priority containers while waiting for new/existing
207 // workers to come up for higher-priority containers.
208 func (*SchedulerSuite) TestMapStartWhileCreating(c *check.C) {
210 unalloc: map[arvados.InstanceType]int{
214 idle: map[arvados.InstanceType]int{
218 running: map[string]bool{},
222 ents: map[string]container.QueueEnt{
224 // create a new worker
225 Container: arvados.Container{UUID: uuids[1], Priority: 1, State: arvados.ContainerStateQueued},
226 InstanceType: types[1],
229 // tentatively map to unalloc worker
230 Container: arvados.Container{UUID: uuids[2], Priority: 2, State: arvados.ContainerStateQueued},
231 InstanceType: types[1],
234 // start now on idle worker
235 Container: arvados.Container{UUID: uuids[3], Priority: 3, State: arvados.ContainerStateQueued},
236 InstanceType: types[1],
239 // create a new worker
240 Container: arvados.Container{UUID: uuids[4], Priority: 4, State: arvados.ContainerStateQueued},
241 InstanceType: types[2],
244 // tentatively map to unalloc worker
245 Container: arvados.Container{UUID: uuids[5], Priority: 5, State: arvados.ContainerStateQueued},
246 InstanceType: types[2],
249 // start now on idle worker
250 Container: arvados.Container{UUID: uuids[6], Priority: 6, State: arvados.ContainerStateQueued},
251 InstanceType: types[2],
255 Map(logger, &queue, &pool)
256 c.Check(pool.creates, check.DeepEquals, []arvados.InstanceType{types[2], types[1]})
257 c.Check(pool.starts, check.DeepEquals, []string{uuids[6], uuids[5], uuids[3], uuids[2]})
258 c.Check(pool.running, check.DeepEquals, map[string]bool{uuids[3]: true, uuids[6]: true})