20978: Add MaximumPriceFactor config.
[arvados.git] / lib / dispatchcloud / node_size_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package dispatchcloud
6
7 import (
8         "git.arvados.org/arvados.git/sdk/go/arvados"
9         check "gopkg.in/check.v1"
10 )
11
12 var _ = check.Suite(&NodeSizeSuite{})
13
14 const GiB = arvados.ByteSize(1 << 30)
15
16 type NodeSizeSuite struct{}
17
18 func (*NodeSizeSuite) TestChooseNotConfigured(c *check.C) {
19         _, err := ChooseInstanceType(&arvados.Cluster{}, &arvados.Container{
20                 RuntimeConstraints: arvados.RuntimeConstraints{
21                         RAM:   1234567890,
22                         VCPUs: 2,
23                 },
24         })
25         c.Check(err, check.Equals, ErrInstanceTypesNotConfigured)
26 }
27
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},
34                 }}, ctr)
35                 c.Check(err, check.FitsTypeOf, ConstraintsNotSatisfiableError{})
36         }
37
38         for _, rc := range []arvados.RuntimeConstraints{
39                 {RAM: 9876543210, VCPUs: 2},
40                 {RAM: 1234567890, VCPUs: 20},
41                 {RAM: 1234567890, VCPUs: 2, KeepCacheRAM: 9876543210},
42         } {
43                 checkUnsatisfiable(&arvados.Container{RuntimeConstraints: rc})
44         }
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},
48         })
49 }
50
51 func (*NodeSizeSuite) TestChoose(c *check.C) {
52         for _, menu := range []map[string]arvados.InstanceType{
53                 {
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"},
57                 },
58                 {
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"},
63                 },
64                 {
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"},
69                 },
70                 {
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"},
75                 },
76                 {
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"},
81                 },
82         } {
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)},
89                         },
90                         RuntimeConstraints: arvados.RuntimeConstraints{
91                                 VCPUs:        2,
92                                 RAM:          987654321,
93                                 KeepCacheRAM: 123456789,
94                         },
95                 })
96                 c.Assert(err, check.IsNil)
97                 c.Assert(best, check.HasLen, 1)
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         }
103 }
104
105 func (*NodeSizeSuite) TestChooseWithBlobBuffersOverhead(c *check.C) {
106         menu := map[string]arvados.InstanceType{
107                 "nearly": {Price: 2.2, RAM: 4000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "small"},
108                 "best":   {Price: 3.3, RAM: 8000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best"},
109                 "costly": {Price: 4.4, RAM: 12000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly"},
110         }
111         best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu, Containers: arvados.ContainersConfig{
112                 LocalKeepBlobBuffersPerVCPU: 16, // 1 GiB per vcpu => 2 GiB
113                 ReserveExtraRAM:             268435456,
114         }}, &arvados.Container{
115                 Mounts: map[string]arvados.Mount{
116                         "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
117                 },
118                 RuntimeConstraints: arvados.RuntimeConstraints{
119                         VCPUs:        2,
120                         RAM:          987654321,
121                         KeepCacheRAM: 123456789,
122                 },
123         })
124         c.Check(err, check.IsNil)
125         c.Assert(best, check.HasLen, 1)
126         c.Check(best[0].Name, check.Equals, "best")
127 }
128
129 func (*NodeSizeSuite) TestChoosePreemptible(c *check.C) {
130         menu := map[string]arvados.InstanceType{
131                 "costly":      {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Preemptible: true, Name: "costly"},
132                 "almost best": {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "almost best"},
133                 "best":        {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Preemptible: true, Name: "best"},
134                 "small":       {Price: 1.1, RAM: 1000000000, VCPUs: 2, Scratch: 2 * GiB, Preemptible: true, Name: "small"},
135         }
136         best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu}, &arvados.Container{
137                 Mounts: map[string]arvados.Mount{
138                         "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
139                 },
140                 RuntimeConstraints: arvados.RuntimeConstraints{
141                         VCPUs:        2,
142                         RAM:          987654321,
143                         KeepCacheRAM: 123456789,
144                 },
145                 SchedulingParameters: arvados.SchedulingParameters{
146                         Preemptible: true,
147                 },
148         })
149         c.Check(err, check.IsNil)
150         c.Assert(best, check.HasLen, 1)
151         c.Check(best[0].Name, check.Equals, "best")
152         c.Check(best[0].RAM >= 1234567890, check.Equals, true)
153         c.Check(best[0].VCPUs >= 2, check.Equals, true)
154         c.Check(best[0].Scratch >= 2*GiB, check.Equals, true)
155         c.Check(best[0].Preemptible, check.Equals, true)
156 }
157
158 func (*NodeSizeSuite) TestScratchForDockerImage(c *check.C) {
159         n := EstimateScratchSpace(&arvados.Container{
160                 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+342",
161         })
162         // Actual image is 371.1 MiB (according to workbench)
163         // Estimated size is 384 MiB (402653184 bytes)
164         // Want to reserve 2x the estimated size, so 805306368 bytes
165         c.Check(n, check.Equals, int64(805306368))
166
167         n = EstimateScratchSpace(&arvados.Container{
168                 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+-342",
169         })
170         // Parse error will return 0
171         c.Check(n, check.Equals, int64(0))
172
173         n = EstimateScratchSpace(&arvados.Container{
174                 ContainerImage: "d5025c0f29f6eef304a7358afa82a822+34",
175         })
176         // Short manifest will return 0
177         c.Check(n, check.Equals, int64(0))
178 }
179
180 func (*NodeSizeSuite) TestChooseGPU(c *check.C) {
181         menu := map[string]arvados.InstanceType{
182                 "costly":         {Price: 4.4, RAM: 4000000000, VCPUs: 8, Scratch: 2 * GiB, Name: "costly", CUDA: arvados.CUDAFeatures{DeviceCount: 2, HardwareCapability: "9.0", DriverVersion: "11.0"}},
183                 "low_capability": {Price: 2.1, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "low_capability", CUDA: arvados.CUDAFeatures{DeviceCount: 1, HardwareCapability: "8.0", DriverVersion: "11.0"}},
184                 "best":           {Price: 2.2, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "best", CUDA: arvados.CUDAFeatures{DeviceCount: 1, HardwareCapability: "9.0", DriverVersion: "11.0"}},
185                 "low_driver":     {Price: 2.1, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "low_driver", CUDA: arvados.CUDAFeatures{DeviceCount: 1, HardwareCapability: "9.0", DriverVersion: "10.0"}},
186                 "cheap_gpu":      {Price: 2.0, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "cheap_gpu", CUDA: arvados.CUDAFeatures{DeviceCount: 1, HardwareCapability: "8.0", DriverVersion: "10.0"}},
187                 "invalid_gpu":    {Price: 1.9, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "invalid_gpu", CUDA: arvados.CUDAFeatures{DeviceCount: 1, HardwareCapability: "12.0.12", DriverVersion: "12.0.12"}},
188                 "non_gpu":        {Price: 1.1, RAM: 2000000000, VCPUs: 4, Scratch: 2 * GiB, Name: "non_gpu"},
189         }
190
191         type GPUTestCase struct {
192                 CUDA             arvados.CUDARuntimeConstraints
193                 SelectedInstance string
194         }
195         cases := []GPUTestCase{
196                 GPUTestCase{
197                         CUDA: arvados.CUDARuntimeConstraints{
198                                 DeviceCount:        1,
199                                 HardwareCapability: "9.0",
200                                 DriverVersion:      "11.0",
201                         },
202                         SelectedInstance: "best",
203                 },
204                 GPUTestCase{
205                         CUDA: arvados.CUDARuntimeConstraints{
206                                 DeviceCount:        2,
207                                 HardwareCapability: "9.0",
208                                 DriverVersion:      "11.0",
209                         },
210                         SelectedInstance: "costly",
211                 },
212                 GPUTestCase{
213                         CUDA: arvados.CUDARuntimeConstraints{
214                                 DeviceCount:        1,
215                                 HardwareCapability: "8.0",
216                                 DriverVersion:      "11.0",
217                         },
218                         SelectedInstance: "low_capability",
219                 },
220                 GPUTestCase{
221                         CUDA: arvados.CUDARuntimeConstraints{
222                                 DeviceCount:        1,
223                                 HardwareCapability: "9.0",
224                                 DriverVersion:      "10.0",
225                         },
226                         SelectedInstance: "low_driver",
227                 },
228                 GPUTestCase{
229                         CUDA: arvados.CUDARuntimeConstraints{
230                                 DeviceCount:        1,
231                                 HardwareCapability: "",
232                                 DriverVersion:      "10.0",
233                         },
234                         SelectedInstance: "",
235                 },
236                 GPUTestCase{
237                         CUDA: arvados.CUDARuntimeConstraints{
238                                 DeviceCount:        0,
239                                 HardwareCapability: "9.0",
240                                 DriverVersion:      "11.0",
241                         },
242                         SelectedInstance: "non_gpu",
243                 },
244         }
245
246         for _, tc := range cases {
247                 best, err := ChooseInstanceType(&arvados.Cluster{InstanceTypes: menu}, &arvados.Container{
248                         Mounts: map[string]arvados.Mount{
249                                 "/tmp": {Kind: "tmp", Capacity: 2 * int64(GiB)},
250                         },
251                         RuntimeConstraints: arvados.RuntimeConstraints{
252                                 VCPUs:        2,
253                                 RAM:          987654321,
254                                 KeepCacheRAM: 123456789,
255                                 CUDA:         tc.CUDA,
256                         },
257                 })
258                 if len(best) > 0 {
259                         c.Check(err, check.IsNil)
260                         c.Assert(best, check.HasLen, 1)
261                         c.Check(best[0].Name, check.Equals, tc.SelectedInstance)
262                 } else {
263                         c.Check(err, check.Not(check.IsNil))
264                 }
265         }
266 }