1
16
17 package noderesources
18
19 import (
20 "context"
21 "testing"
22
23 "github.com/google/go-cmp/cmp"
24 v1 "k8s.io/api/core/v1"
25 "k8s.io/apimachinery/pkg/util/validation/field"
26 "k8s.io/klog/v2/ktesting"
27 "k8s.io/kubernetes/pkg/scheduler/apis/config"
28 "k8s.io/kubernetes/pkg/scheduler/framework"
29 plfeature "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
30 "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
31 "k8s.io/kubernetes/pkg/scheduler/internal/cache"
32 st "k8s.io/kubernetes/pkg/scheduler/testing"
33 tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
34 )
35
36 func TestLeastAllocatedScoringStrategy(t *testing.T) {
37 tests := []struct {
38 name string
39 requestedPod *v1.Pod
40 nodes []*v1.Node
41 existingPods []*v1.Pod
42 expectedScores framework.NodeScoreList
43 resources []config.ResourceSpec
44 wantErrs field.ErrorList
45 wantStatusCode framework.Code
46 }{
47 {
48
49
50
51
52
53
54
55
56 name: "nothing scheduled, nothing requested",
57 requestedPod: st.MakePod().Obj(),
58 nodes: []*v1.Node{
59 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
60 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
61 },
62 existingPods: nil,
63 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}},
64 resources: defaultResources,
65 },
66 {
67
68
69
70
71
72
73
74
75 name: "nothing scheduled, resources requested, differently sized nodes",
76 requestedPod: st.MakePod().
77 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
78 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
79 Obj(),
80 nodes: []*v1.Node{
81 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
82 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
83 },
84 existingPods: nil,
85 expectedScores: []framework.NodeScore{{Name: "node1", Score: 37}, {Name: "node2", Score: 50}},
86 resources: defaultResources,
87 },
88 {
89 name: "Resources not set, pods scheduled with error",
90 requestedPod: st.MakePod().
91 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
92 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
93 Obj(),
94 nodes: []*v1.Node{
95 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
96 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
97 },
98 existingPods: nil,
99 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}},
100 resources: nil,
101 wantStatusCode: framework.Error,
102 },
103 {
104
105
106
107
108
109
110
111
112 name: "no resources requested, pods scheduled",
113 requestedPod: st.MakePod().Obj(),
114 nodes: []*v1.Node{
115 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
116 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
117 },
118 existingPods: []*v1.Pod{
119 st.MakePod().Node("node1").Obj(),
120 st.MakePod().Node("node1").Obj(),
121 st.MakePod().Node("node2").Obj(),
122 st.MakePod().Node("node2").Obj(),
123 },
124 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}},
125 resources: defaultResources,
126 },
127 {
128
129
130
131
132
133
134
135
136 name: "no resources requested, pods scheduled with resources",
137 requestedPod: st.MakePod().Obj(),
138 nodes: []*v1.Node{
139 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
140 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
141 },
142 existingPods: []*v1.Pod{
143 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
144 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
145 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
146 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
147 },
148 expectedScores: []framework.NodeScore{{Name: "node1", Score: 70}, {Name: "node2", Score: 57}},
149 resources: defaultResources,
150 },
151 {
152
153
154
155
156
157
158
159
160 name: "resources requested, pods scheduled with resources",
161 requestedPod: st.MakePod().
162 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
163 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
164 Obj(),
165 nodes: []*v1.Node{
166 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
167 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
168 },
169 existingPods: []*v1.Pod{
170 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
171 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
172 },
173 expectedScores: []framework.NodeScore{{Name: "node1", Score: 57}, {Name: "node2", Score: 45}},
174 resources: defaultResources,
175 },
176 {
177
178
179
180
181
182
183
184
185 name: "resources requested, pods scheduled with resources, differently sized nodes",
186 requestedPod: st.MakePod().
187 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
188 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
189 Obj(),
190 nodes: []*v1.Node{
191 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
192 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "50000"}).Obj(),
193 },
194 existingPods: []*v1.Pod{
195 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
196 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
197 },
198 expectedScores: []framework.NodeScore{{Name: "node1", Score: 57}, {Name: "node2", Score: 60}},
199 resources: defaultResources,
200 },
201 {
202
203
204
205
206
207
208
209
210 name: "requested resources exceed node capacity",
211 requestedPod: st.MakePod().Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
212 nodes: []*v1.Node{
213 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
214 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
215 },
216 existingPods: []*v1.Pod{
217 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
218 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
219 },
220 expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 25}},
221 resources: defaultResources,
222 },
223 {
224 name: "zero node resources, pods scheduled with resources",
225 requestedPod: st.MakePod().Obj(),
226 nodes: []*v1.Node{
227 st.MakeNode().Name("node1").Obj(),
228 st.MakeNode().Name("node2").Obj(),
229 },
230 existingPods: []*v1.Pod{
231 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
232 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
233 },
234 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}},
235 resources: defaultResources,
236 },
237 {
238
239
240
241
242
243
244 name: "nothing scheduled, resources requested with different weight on CPU and memory, differently sized nodes",
245 requestedPod: st.MakePod().Node("node1").
246 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
247 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
248 Obj(),
249 nodes: []*v1.Node{
250 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
251 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
252 },
253 existingPods: nil,
254 expectedScores: []framework.NodeScore{{Name: "node1", Score: 41}, {Name: "node2", Score: 50}},
255 resources: []config.ResourceSpec{
256 {Name: "memory", Weight: 2},
257 {Name: "cpu", Weight: 1},
258 },
259 },
260 {
261
262 name: "resource with negative weight",
263 requestedPod: st.MakePod().Node("node1").
264 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
265 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
266 Obj(),
267 nodes: []*v1.Node{
268 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
269 },
270 resources: []config.ResourceSpec{
271 {Name: "memory", Weight: -1},
272 {Name: "cpu", Weight: 1},
273 },
274 wantErrs: field.ErrorList{
275 &field.Error{
276 Type: field.ErrorTypeInvalid,
277 Field: "scoringStrategy.resources[0].weight",
278 },
279 },
280 },
281 {
282
283 name: "resource with zero weight",
284 requestedPod: st.MakePod().Node("node1").
285 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
286 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
287 Obj(),
288 nodes: []*v1.Node{
289 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
290 },
291 existingPods: nil,
292 expectedScores: []framework.NodeScore{{Name: "node1", Score: 41}, {Name: "node2", Score: 50}},
293 resources: []config.ResourceSpec{
294 {Name: "memory", Weight: 1},
295 {Name: "cpu", Weight: 0},
296 },
297 wantErrs: field.ErrorList{
298 &field.Error{
299 Type: field.ErrorTypeInvalid,
300 Field: "scoringStrategy.resources[1].weight",
301 },
302 },
303 },
304 {
305
306 name: "resource weight larger than MaxNodeScore",
307 requestedPod: st.MakePod().Node("node1").
308 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
309 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
310 Obj(),
311 nodes: []*v1.Node{
312 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
313 },
314 resources: []config.ResourceSpec{
315 {Name: "memory", Weight: 1},
316 {Name: "cpu", Weight: 101},
317 },
318 wantErrs: field.ErrorList{
319 &field.Error{
320 Type: field.ErrorTypeInvalid,
321 Field: "scoringStrategy.resources[1].weight",
322 },
323 },
324 },
325 {
326
327
328
329
330
331
332 name: "bypass extended resource if the pod does not request",
333 requestedPod: st.MakePod().Node("node1").
334 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
335 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
336 Obj(),
337 nodes: []*v1.Node{
338 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
339 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
340 },
341 expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 50}},
342 resources: extendedResourceSet,
343 },
344 {
345
346
347
348
349
350
351
352
353 name: "honor extended resource if the pod requests",
354 requestedPod: st.MakePod().Node("node1").
355 Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000", v1.ResourceName(extendedRes): "2"}).
356 Obj(),
357 nodes: []*v1.Node{
358 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
359 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "10"}).Obj(),
360 },
361 existingPods: nil,
362 expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 60}},
363 resources: extendedResourceSet,
364 },
365 {
366
367
368
369
370
371 name: "if the node doesn't have a resource",
372 requestedPod: st.MakePod().Node("node1").
373 Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "4000"}).
374 Obj(),
375 nodes: []*v1.Node{
376 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
377 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
378 },
379 expectedScores: []framework.NodeScore{{Name: "node1", Score: 55}, {Name: "node2", Score: 55}},
380 resources: []config.ResourceSpec{
381 {Name: extendedRes, Weight: 2},
382 {Name: string(v1.ResourceCPU), Weight: 1},
383 {Name: string(v1.ResourceMemory), Weight: 1},
384 },
385 },
386 }
387
388 for _, test := range tests {
389 t.Run(test.name, func(t *testing.T) {
390 _, ctx := ktesting.NewTestContext(t)
391 ctx, cancel := context.WithCancel(ctx)
392 defer cancel()
393
394 state := framework.NewCycleState()
395 snapshot := cache.NewSnapshot(test.existingPods, test.nodes)
396 fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
397
398 p, err := NewFit(
399 ctx,
400 &config.NodeResourcesFitArgs{
401 ScoringStrategy: &config.ScoringStrategy{
402 Type: config.LeastAllocated,
403 Resources: test.resources,
404 },
405 }, fh, plfeature.Features{})
406
407 if diff := cmp.Diff(test.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
408 t.Fatalf("got err (-want,+got):\n%s", diff)
409 }
410 if err != nil {
411 return
412 }
413
414 status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes))
415 if !status.IsSuccess() {
416 t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status)
417 }
418
419 var gotScores framework.NodeScoreList
420 for _, n := range test.nodes {
421 score, status := p.(framework.ScorePlugin).Score(ctx, state, test.requestedPod, n.Name)
422 if status.Code() != test.wantStatusCode {
423 t.Errorf("unexpected status code, want: %v, got: %v", test.wantStatusCode, status.Code())
424 }
425 gotScores = append(gotScores, framework.NodeScore{Name: n.Name, Score: score})
426 }
427
428 if diff := cmp.Diff(test.expectedScores, gotScores); diff != "" {
429 t.Errorf("Unexpected scores (-want,+got):\n%s", diff)
430 }
431 })
432 }
433 }
434
View as plain text