1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
8 "git.arvados.org/arvados.git/sdk/go/arvados"
9 check "gopkg.in/check.v1"
12 var _ = check.Suite(&NodeSizeSuite{})
14 const GiB = arvados.ByteSize(1 << 30)
16 type NodeSizeSuite struct{}
18 func (*NodeSizeSuite) TestChooseNotConfigured(c *check.C) {
19 _, err := ChooseInstanceType(&arvados.Cluster{}, &arvados.Container{
20 RuntimeConstraints: arvados.RuntimeConstraints{
25 c.Check(err, check.Equals, ErrInstanceTypesNotConfigured)
28 func (*NodeSizeSuite) TestChooseUnsatisfiable(c *check.C) {
29 checkUnsatisfiable := func(ctr *arvados.Container) {
30 _, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: map[string]arvados.InstanceType{
31 "small1": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Name: "small1"},
32 "small2": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Name: "small2"},
33 "small4": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Name: "small4", Scratch: GiB},
35 c.Check(err, check.FitsTypeOf, ConstraintsNotSatisfiableError{})
38 for _, rc := range []arvados.RuntimeConstraints{
39 {RAM: 9876543210, VCPUs: 2},
40 {RAM: 1234567890, VCPUs: 20},
41 {RAM: 1234567890, VCPUs: 2, KeepCacheRAM: 9876543210},
43 checkUnsatisfiable(&arvados.Container{RuntimeConstraints: rc})
45 checkUnsatisfiable(&arvados.Container{
46 Mounts: map[string]arvados.Mount{"/tmp": {Kind: "tmp", Capacity: int64(2 * GiB)}},
47 RuntimeConstraints: arvados.RuntimeConstraints{RAM: 12345, VCPUs: 1},
51 func (*NodeSizeSuite) TestChoose(c *check.C) {
52 for _, menu := range []map[string]arvados.InstanceType{
54 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
55 "best": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
56 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 2 * GiB, Name: "small"},
59 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
60 "goodenough": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "goodenough"},
61 "best": {Price: 2.2, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
62 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 2 * GiB, Name: "small"},
65 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 2 * GiB, Name: "small"},
66 "goodenough": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "goodenough"},
67 "best": {Price: 2.2, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
68 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
71 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: GiB, Name: "small"},
72 "nearly": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: GiB, Name: "nearly"},
73 "best": {Price: 3.3, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
74 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
77 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: GiB, Name: "small"},
78 "nearly": {Price: 2.2, RAM: 1200000000, VCPUs: 4, Scratch: 2 * GiB, Name: "nearly"},
79 "best": {Price: 3.3, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
80 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
83 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu, Containers: arvados.ContainersConfig{
84 LocalKeepBlobBuffersPerVCPU: 1,
85 ReserveExtraRAM: 268435456,
86 }}, &arvados.Container{
87 Mounts: map[string]arvados.Mount{
88 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
90 RuntimeConstraints: arvados.RuntimeConstraints{
93 KeepCacheRAM: 123456789,
96 c.Assert(err, check.IsNil)
97 c.Assert(best, check.Not(check.HasLen), 0)
98 c.Check(best[0].Name, check.Equals, "best")
99 c.Check(best[0].RAM >= 1234567890, check.Equals, true)
100 c.Check(best[0].VCPUs >= 2, check.Equals, true)
101 c.Check(best[0].Scratch >= 2*GiB, check.Equals, true)
102 for i := range best {
103 // If multiple instance types are returned
104 // then they should all have the same price,
105 // because we didn't set MaximumPriceFactor>1.
106 c.Check(best[i].Price, check.Equals, best[0].Price)
111 func (*NodeSizeSuite) TestMaximumPriceFactor(c *check.C) {
112 menu := map[string]arvados.InstanceType{
113 "best+7": {Price: 3.4, RAM: 8000000000, VCPUs: 8, Scratch: 64 * GiB, Name: "best+7"},
114 "best+5": {Price: 3.0, RAM: 8000000000, VCPUs: 8, Scratch: 16 * GiB, Name: "best+5"},
115 "best+3": {Price: 2.6, RAM: 4000000000, VCPUs: 8, Scratch: 16 * GiB, Name: "best+3"},
116 "best+2": {Price: 2.4, RAM: 4000000000, VCPUs: 8, Scratch: 4 * GiB, Name: "best+2"},
117 "best+1": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 4 * GiB, Name: "best+1"},
118 "best": {Price: 2.0, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
119 "small+1": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 16 * GiB, Name: "small+1"},
120 "small": {Price: 1.0, RAM: 2000000000, VCPUs: 2, Scratch: 1 * GiB, Name: "small"},
122 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu, Containers: arvados.ContainersConfig{
123 MaximumPriceFactor: 1.5,
124 }}, &arvados.Container{
125 Mounts: map[string]arvados.Mount{
126 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
128 RuntimeConstraints: arvados.RuntimeConstraints{
131 KeepCacheRAM: 123456789,
134 c.Assert(err, check.IsNil)
135 c.Assert(best, check.HasLen, 5)
136 c.Check(best[0].Name, check.Equals, "best") // best price is $2
137 c.Check(best[1].Name, check.Equals, "best+1")
138 c.Check(best[2].Name, check.Equals, "best+2")
139 c.Check(best[3].Name, check.Equals, "best+3")
140 c.Check(best[4].Name, check.Equals, "best+5") // max price is $2 * 1.5 = $3
143 func (*NodeSizeSuite) TestChooseWithBlobBuffersOverhead(c *check.C) {
144 menu := map[string]arvados.InstanceType{
145 "nearly": {Price: 2.2, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "small"},
146 "best": {Price: 3.3, RAM: 8000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
147 "costly": {Price: 4.4, RAM: 12000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
149 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu, Containers: arvados.ContainersConfig{
150 LocalKeepBlobBuffersPerVCPU: 16, // 1 GiB per vcpu => 2 GiB
151 ReserveExtraRAM: 268435456,
152 }}, &arvados.Container{
153 Mounts: map[string]arvados.Mount{
154 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
156 RuntimeConstraints: arvados.RuntimeConstraints{
159 KeepCacheRAM: 123456789,
162 c.Check(err, check.IsNil)
163 c.Assert(best, check.HasLen, 1)
164 c.Check(best[0].Name, check.Equals, "best")
167 func (*NodeSizeSuite) TestChoosePreemptible(c *check.C) {
168 menu := map[string]arvados.InstanceType{
169 "costly": {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Preemptible: true, Name: "costly"},
170 "almost best": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "almost best"},
171 "best": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Preemptible: true, Name: "best"},
172 "small": {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 2 * GiB, Preemptible: true, Name: "small"},
174 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu}, &arvados.Container{
175 Mounts: map[string]arvados.Mount{
176 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
178 RuntimeConstraints: arvados.RuntimeConstraints{
181 KeepCacheRAM: 123456789,
183 SchedulingParameters: arvados.SchedulingParameters{
187 c.Check(err, check.IsNil)
188 c.Assert(best, check.HasLen, 1)
189 c.Check(best[0].Name, check.Equals, "best")
190 c.Check(best[0].RAM >= 1234567890, check.Equals, true)
191 c.Check(best[0].VCPUs >= 2, check.Equals, true)
192 c.Check(best[0].Scratch >= 2*GiB, check.Equals, true)
193 c.Check(best[0].Preemptible, check.Equals, true)
196 func (*NodeSizeSuite) TestScratchForDockerImage(c *check.C) {
197 n := EstimateScratchSpace(&arvados.Container{
198 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+342",
200 // Actual image is 371.1 MiB (according to workbench)
201 // Estimated size is 384 MiB (402653184 bytes)
202 // Want to reserve 2x the estimated size, so 805306368 bytes
203 c.Check(n, check.Equals, int64(805306368))
205 n = EstimateScratchSpace(&arvados.Container{
206 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+-342",
208 // Parse error will return 0
209 c.Check(n, check.Equals, int64(0))
211 n = EstimateScratchSpace(&arvados.Container{
212 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+34",
214 // Short manifest will return 0
215 c.Check(n, check.Equals, int64(0))
218 func (*NodeSizeSuite) TestChooseGPU(c *check.C) {
219 menu := map[string]arvados.InstanceType{
220 "costly": {Price: 4.4, RAM: 4 * GiB, VCPUs: 8, Scratch: 2 * GiB, Name: "costly",
221 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 2, HardwareTarget: "9.0", DriverVersion: "11.0", VRAM: 2 * GiB}},
223 "low_capability": {Price: 2.1, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "low_capability",
224 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "8.0", DriverVersion: "11.0", VRAM: 2 * GiB}},
226 "best": {Price: 2.2, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "best",
227 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "9.0", DriverVersion: "11.0", VRAM: 2 * GiB}},
229 "low_driver": {Price: 2.1, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "low_driver",
230 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "9.0", DriverVersion: "10.0", VRAM: 2 * GiB}},
232 "cheap_gpu": {Price: 2.0, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "cheap_gpu",
233 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "8.0", DriverVersion: "10.0", VRAM: 2 * GiB}},
235 "more_vram": {Price: 2.3, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "more_vram",
236 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "8.0", DriverVersion: "10.0", VRAM: 8 * GiB}},
238 "invalid_gpu": {Price: 1.9, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "invalid_gpu",
239 GPU: arvados.GPUFeatures{Stack: "cuda", DeviceCount: 1, HardwareTarget: "12.0.12", DriverVersion: "12.0.12", VRAM: 2 * GiB}},
241 "gpu_rocm": {Price: 2.0, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "gpu_rocm",
242 GPU: arvados.GPUFeatures{Stack: "rocm", DeviceCount: 1, HardwareTarget: "gfx1100", DriverVersion: "6.2", VRAM: 20 * GiB}},
244 "cheap_gpu_rocm": {Price: 1.9, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "cheap_gpu_rocm",
245 GPU: arvados.GPUFeatures{Stack: "rocm", DeviceCount: 1, HardwareTarget: "gfx1103", DriverVersion: "6.2", VRAM: 8 * GiB}},
247 "unspecified_vram": {Price: 2.0, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "unspecified_vram",
248 GPU: arvados.GPUFeatures{Stack: "rocm", DeviceCount: 1, HardwareTarget: "gfx1104", DriverVersion: "6.2", VRAM: 0}},
250 "non_gpu": {Price: 1.1, RAM: 2 * GiB, VCPUs: 4, Scratch: 2 * GiB, Name: "non_gpu"},
253 type GPUTestCase struct {
254 GPU arvados.GPURuntimeConstraints
255 SelectedInstance string
257 cases := []GPUTestCase{
259 GPU: arvados.GPURuntimeConstraints{
262 HardwareTarget: []string{"9.0"},
263 DriverVersion: "11.0",
266 SelectedInstance: "best",
269 GPU: arvados.GPURuntimeConstraints{
272 HardwareTarget: []string{"9.0"},
273 DriverVersion: "11.0",
276 SelectedInstance: "costly",
279 GPU: arvados.GPURuntimeConstraints{
282 HardwareTarget: []string{"8.0"},
283 DriverVersion: "11.0",
286 SelectedInstance: "low_capability",
289 GPU: arvados.GPURuntimeConstraints{
292 HardwareTarget: []string{"9.0"},
293 DriverVersion: "10.0",
296 SelectedInstance: "low_driver",
299 GPU: arvados.GPURuntimeConstraints{
302 HardwareTarget: []string{"8.0"},
303 DriverVersion: "11.0",
306 SelectedInstance: "more_vram",
309 GPU: arvados.GPURuntimeConstraints{
312 HardwareTarget: []string{},
313 DriverVersion: "10.0",
316 SelectedInstance: "",
319 GPU: arvados.GPURuntimeConstraints{
322 HardwareTarget: []string{"gfx1100"},
323 DriverVersion: "6.2",
326 SelectedInstance: "gpu_rocm",
329 GPU: arvados.GPURuntimeConstraints{
332 HardwareTarget: []string{"gfx1100", "gfx1103"},
333 DriverVersion: "6.2",
336 SelectedInstance: "cheap_gpu_rocm",
339 GPU: arvados.GPURuntimeConstraints{
342 HardwareTarget: []string{"gfx1104"},
343 DriverVersion: "6.2",
346 SelectedInstance: "unspecified_vram",
349 GPU: arvados.GPURuntimeConstraints{
352 HardwareTarget: []string{""},
356 SelectedInstance: "non_gpu",
360 for _, tc := range cases {
361 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu}, &arvados.Container{
362 Mounts: map[string]arvados.Mount{
363 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
365 RuntimeConstraints: arvados.RuntimeConstraints{
368 KeepCacheRAM: 123456789,
373 c.Check(err, check.IsNil)
374 c.Assert(best, check.HasLen, 1)
375 c.Check(best[0].Name, check.Equals, tc.SelectedInstance)
377 c.Check(err, check.Not(check.IsNil))