1
16
17 package noderesources
18
19 import (
20 "context"
21 "fmt"
22 "testing"
23
24 "github.com/google/go-cmp/cmp"
25 "github.com/stretchr/testify/assert"
26 v1 "k8s.io/api/core/v1"
27 "k8s.io/apimachinery/pkg/util/validation/field"
28 "k8s.io/klog/v2/ktesting"
29 "k8s.io/kubernetes/pkg/scheduler/apis/config"
30 "k8s.io/kubernetes/pkg/scheduler/framework"
31 plfeature "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
32 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper"
33 "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
34 "k8s.io/kubernetes/pkg/scheduler/internal/cache"
35 st "k8s.io/kubernetes/pkg/scheduler/testing"
36 tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
37 )
38
39 func TestRequestedToCapacityRatioScoringStrategy(t *testing.T) {
40 shape := []config.UtilizationShapePoint{
41 {Utilization: 0, Score: 10},
42 {Utilization: 100, Score: 0},
43 }
44
45 tests := []struct {
46 name string
47 requestedPod *v1.Pod
48 nodes []*v1.Node
49 existingPods []*v1.Pod
50 expectedScores framework.NodeScoreList
51 resources []config.ResourceSpec
52 shape []config.UtilizationShapePoint
53 wantErrs field.ErrorList
54 }{
55 {
56 name: "nothing scheduled, nothing requested (default - least requested nodes have priority)",
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: []*v1.Pod{
63 st.MakePod().Node("node1").Obj(),
64 st.MakePod().Node("node1").Obj(),
65 },
66 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}},
67 resources: defaultResources,
68 shape: shape,
69 },
70 {
71 name: "nothing scheduled, resources requested, differently sized nodes (default - least requested nodes have priority)",
72 requestedPod: st.MakePod().
73 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
74 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
75 Obj(),
76 nodes: []*v1.Node{
77 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
78 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
79 },
80 existingPods: []*v1.Pod{
81 st.MakePod().Node("node1").Obj(),
82 st.MakePod().Node("node1").Obj(),
83 },
84 expectedScores: []framework.NodeScore{{Name: "node1", Score: 38}, {Name: "node2", Score: 50}},
85 resources: defaultResources,
86 shape: shape,
87 },
88 {
89 name: "no resources requested, pods scheduled with resources (default - least requested nodes have priority)",
90 requestedPod: st.MakePod().Obj(),
91 nodes: []*v1.Node{
92 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
93 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
94 },
95 existingPods: []*v1.Pod{
96 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
97 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
98 },
99 expectedScores: []framework.NodeScore{{Name: "node1", Score: 38}, {Name: "node2", Score: 50}},
100 resources: defaultResources,
101 shape: shape,
102 },
103 }
104
105 for _, test := range tests {
106 t.Run(test.name, func(t *testing.T) {
107 _, ctx := ktesting.NewTestContext(t)
108 ctx, cancel := context.WithCancel(ctx)
109 defer cancel()
110
111 state := framework.NewCycleState()
112 snapshot := cache.NewSnapshot(test.existingPods, test.nodes)
113 fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
114
115 p, err := NewFit(ctx, &config.NodeResourcesFitArgs{
116 ScoringStrategy: &config.ScoringStrategy{
117 Type: config.RequestedToCapacityRatio,
118 Resources: test.resources,
119 RequestedToCapacityRatio: &config.RequestedToCapacityRatioParam{
120 Shape: shape,
121 },
122 },
123 }, fh, plfeature.Features{})
124
125 if diff := cmp.Diff(test.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
126 t.Fatalf("got err (-want,+got):\n%s", diff)
127 }
128 if err != nil {
129 return
130 }
131
132 var gotScores framework.NodeScoreList
133 for _, n := range test.nodes {
134 status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes))
135 if !status.IsSuccess() {
136 t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status)
137 }
138 score, status := p.(framework.ScorePlugin).Score(ctx, state, test.requestedPod, n.Name)
139 if !status.IsSuccess() {
140 t.Errorf("Score is expected to return success, but didn't. Got status: %v", status)
141 }
142 gotScores = append(gotScores, framework.NodeScore{Name: n.Name, Score: score})
143 }
144
145 if diff := cmp.Diff(test.expectedScores, gotScores); diff != "" {
146 t.Errorf("Unexpected nodes (-want,+got):\n%s", diff)
147 }
148 })
149 }
150 }
151
152 func TestBrokenLinearFunction(t *testing.T) {
153 type Assertion struct {
154 p int64
155 expected int64
156 }
157 type Test struct {
158 points []helper.FunctionShapePoint
159 assertions []Assertion
160 }
161
162 tests := []Test{
163 {
164 points: []helper.FunctionShapePoint{{Utilization: 10, Score: 1}, {Utilization: 90, Score: 9}},
165 assertions: []Assertion{
166 {p: -10, expected: 1},
167 {p: 0, expected: 1},
168 {p: 9, expected: 1},
169 {p: 10, expected: 1},
170 {p: 15, expected: 1},
171 {p: 19, expected: 1},
172 {p: 20, expected: 2},
173 {p: 89, expected: 8},
174 {p: 90, expected: 9},
175 {p: 99, expected: 9},
176 {p: 100, expected: 9},
177 {p: 110, expected: 9},
178 },
179 },
180 {
181 points: []helper.FunctionShapePoint{{Utilization: 0, Score: 2}, {Utilization: 40, Score: 10}, {Utilization: 100, Score: 0}},
182 assertions: []Assertion{
183 {p: -10, expected: 2},
184 {p: 0, expected: 2},
185 {p: 20, expected: 6},
186 {p: 30, expected: 8},
187 {p: 40, expected: 10},
188 {p: 70, expected: 5},
189 {p: 100, expected: 0},
190 {p: 110, expected: 0},
191 },
192 },
193 {
194 points: []helper.FunctionShapePoint{{Utilization: 0, Score: 2}, {Utilization: 40, Score: 2}, {Utilization: 100, Score: 2}},
195 assertions: []Assertion{
196 {p: -10, expected: 2},
197 {p: 0, expected: 2},
198 {p: 20, expected: 2},
199 {p: 30, expected: 2},
200 {p: 40, expected: 2},
201 {p: 70, expected: 2},
202 {p: 100, expected: 2},
203 {p: 110, expected: 2},
204 },
205 },
206 }
207
208 for i, test := range tests {
209 t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
210 function := helper.BuildBrokenLinearFunction(test.points)
211 for _, assertion := range test.assertions {
212 assert.InDelta(t, assertion.expected, function(assertion.p), 0.1, "points=%v, p=%d", test.points, assertion.p)
213 }
214 })
215 }
216 }
217
218 func TestResourceBinPackingSingleExtended(t *testing.T) {
219 extendedResource1 := map[string]int64{
220 "intel.com/foo": 4,
221 }
222 extendedResource2 := map[string]int64{
223 "intel.com/foo": 8,
224 }
225 extendedResource3 := map[v1.ResourceName]string{
226 "intel.com/foo": "2",
227 }
228 extendedResource4 := map[v1.ResourceName]string{
229 "intel.com/foo": "4",
230 }
231
232 tests := []struct {
233 pod *v1.Pod
234 pods []*v1.Pod
235 nodes []*v1.Node
236 expectedScores framework.NodeScoreList
237 name string
238 }{
239 {
240
241 pod: st.MakePod().Obj(),
242 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResource2), makeNode("node2", 4000, 10000*1024*1024, extendedResource1)},
243 expectedScores: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 0}},
244 name: "nothing scheduled, nothing requested",
245 },
246 {
247
248
249
250
251
252
253
254
255
256
257
258 pod: st.MakePod().Req(extendedResource3).Obj(),
259 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResource2), makeNode("node2", 4000, 10000*1024*1024, extendedResource1)},
260 expectedScores: []framework.NodeScore{{Name: "node1", Score: 2}, {Name: "node2", Score: 5}},
261 name: "resources requested, pods scheduled with less resources",
262 pods: []*v1.Pod{st.MakePod().Obj()},
263 },
264 {
265
266
267
268
269
270
271
272
273
274
275
276 pod: st.MakePod().Req(extendedResource3).Obj(),
277 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResource2), makeNode("node2", 4000, 10000*1024*1024, extendedResource1)},
278 expectedScores: []framework.NodeScore{{Name: "node1", Score: 2}, {Name: "node2", Score: 10}},
279 name: "resources requested, pods scheduled with resources, on node with existing pod running ",
280 pods: []*v1.Pod{st.MakePod().Req(extendedResource3).Node("node2").Obj()},
281 },
282 {
283
284
285
286
287
288
289
290
291
292
293
294 pod: st.MakePod().Req(extendedResource4).Obj(),
295 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResource2), makeNode("node2", 4000, 10000*1024*1024, extendedResource1)},
296 expectedScores: []framework.NodeScore{{Name: "node1", Score: 5}, {Name: "node2", Score: 10}},
297 name: "resources requested, pods scheduled with more resources",
298 pods: []*v1.Pod{
299 st.MakePod().Obj(),
300 },
301 },
302 }
303
304 for _, test := range tests {
305 t.Run(test.name, func(t *testing.T) {
306 state := framework.NewCycleState()
307 snapshot := cache.NewSnapshot(test.pods, test.nodes)
308 _, ctx := ktesting.NewTestContext(t)
309 fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
310 args := config.NodeResourcesFitArgs{
311 ScoringStrategy: &config.ScoringStrategy{
312 Type: config.RequestedToCapacityRatio,
313 Resources: []config.ResourceSpec{
314 {Name: "intel.com/foo", Weight: 1},
315 },
316 RequestedToCapacityRatio: &config.RequestedToCapacityRatioParam{
317 Shape: []config.UtilizationShapePoint{
318 {Utilization: 0, Score: 0},
319 {Utilization: 100, Score: 1},
320 },
321 },
322 },
323 }
324 p, err := NewFit(ctx, &args, fh, plfeature.Features{})
325 if err != nil {
326 t.Fatalf("unexpected error: %v", err)
327 }
328
329 var gotList framework.NodeScoreList
330 for _, n := range test.nodes {
331 status := p.(framework.PreScorePlugin).PreScore(context.Background(), state, test.pod, tf.BuildNodeInfos(test.nodes))
332 if !status.IsSuccess() {
333 t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status)
334 }
335 score, status := p.(framework.ScorePlugin).Score(context.Background(), state, test.pod, n.Name)
336 if !status.IsSuccess() {
337 t.Errorf("Score is expected to return success, but didn't. Got status: %v", status)
338 }
339 gotList = append(gotList, framework.NodeScore{Name: n.Name, Score: score})
340 }
341
342 if diff := cmp.Diff(test.expectedScores, gotList); diff != "" {
343 t.Errorf("Unexpected nodescore list (-want,+got):\n%s", diff)
344 }
345 })
346 }
347 }
348
349 func TestResourceBinPackingMultipleExtended(t *testing.T) {
350 extendedResources1 := map[string]int64{
351 "intel.com/foo": 4,
352 "intel.com/bar": 8,
353 }
354 extendedResources2 := map[string]int64{
355 "intel.com/foo": 8,
356 "intel.com/bar": 4,
357 }
358
359 extendedResourcePod1 := map[v1.ResourceName]string{
360 "intel.com/foo": "2",
361 "intel.com/bar": "2",
362 }
363 extendedResourcePod2 := map[v1.ResourceName]string{
364 "intel.com/foo": "4",
365 "intel.com/bar": "2",
366 }
367
368 tests := []struct {
369 pod *v1.Pod
370 pods []*v1.Pod
371 nodes []*v1.Node
372 expectedScores framework.NodeScoreList
373 name string
374 }{
375 {
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403 pod: st.MakePod().Obj(),
404 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResources2), makeNode("node2", 4000, 10000*1024*1024, extendedResources1)},
405 expectedScores: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 0}},
406 name: "nothing scheduled, nothing requested",
407 },
408 {
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436 pod: st.MakePod().Req(extendedResourcePod1).Obj(),
437 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResources2), makeNode("node2", 4000, 10000*1024*1024, extendedResources1)},
438 expectedScores: []framework.NodeScore{{Name: "node1", Score: 4}, {Name: "node2", Score: 3}},
439 name: "resources requested, pods scheduled with less resources",
440 pods: []*v1.Pod{
441 st.MakePod().Obj(),
442 },
443 },
444 {
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471 pod: st.MakePod().Req(extendedResourcePod1).Obj(),
472 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResources2), makeNode("node2", 4000, 10000*1024*1024, extendedResources1)},
473 expectedScores: []framework.NodeScore{{Name: "node1", Score: 4}, {Name: "node2", Score: 7}},
474 name: "resources requested, pods scheduled with resources, on node with existing pod running ",
475 pods: []*v1.Pod{st.MakePod().Req(extendedResourcePod2).Node("node2").Obj()},
476 },
477 {
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519 pod: st.MakePod().Req(extendedResourcePod2).Obj(),
520 nodes: []*v1.Node{makeNode("node1", 4000, 10000*1024*1024, extendedResources2), makeNode("node2", 4000, 10000*1024*1024, extendedResources1)},
521 expectedScores: []framework.NodeScore{{Name: "node1", Score: 5}, {Name: "node2", Score: 5}},
522 name: "resources requested, pods scheduled with more resources",
523 pods: []*v1.Pod{
524 st.MakePod().Obj(),
525 },
526 },
527 }
528
529 for _, test := range tests {
530 t.Run(test.name, func(t *testing.T) {
531 state := framework.NewCycleState()
532 snapshot := cache.NewSnapshot(test.pods, test.nodes)
533 _, ctx := ktesting.NewTestContext(t)
534 fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
535
536 args := config.NodeResourcesFitArgs{
537 ScoringStrategy: &config.ScoringStrategy{
538 Type: config.RequestedToCapacityRatio,
539 Resources: []config.ResourceSpec{
540 {Name: "intel.com/foo", Weight: 3},
541 {Name: "intel.com/bar", Weight: 5},
542 },
543 RequestedToCapacityRatio: &config.RequestedToCapacityRatioParam{
544 Shape: []config.UtilizationShapePoint{
545 {Utilization: 0, Score: 0},
546 {Utilization: 100, Score: 1},
547 },
548 },
549 },
550 }
551
552 p, err := NewFit(ctx, &args, fh, plfeature.Features{})
553 if err != nil {
554 t.Fatalf("unexpected error: %v", err)
555 }
556
557 status := p.(framework.PreScorePlugin).PreScore(context.Background(), state, test.pod, tf.BuildNodeInfos(test.nodes))
558 if !status.IsSuccess() {
559 t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status)
560 }
561
562 var gotScores framework.NodeScoreList
563 for _, n := range test.nodes {
564 score, status := p.(framework.ScorePlugin).Score(context.Background(), state, test.pod, n.Name)
565 if !status.IsSuccess() {
566 t.Errorf("Score is expected to return success, but didn't. Got status: %v", status)
567 }
568 gotScores = append(gotScores, framework.NodeScore{Name: n.Name, Score: score})
569 }
570
571 if diff := cmp.Diff(test.expectedScores, gotScores); diff != "" {
572 t.Errorf("Unexpected nodescore list (-want,+got):\n%s", diff)
573 }
574 })
575 }
576 }
577
View as plain text