1
16
17 package priority
18
19 import (
20 "context"
21 "testing"
22
23 "k8s.io/klog/v2"
24
25 schedulingv1 "k8s.io/api/scheduling/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apiserver/pkg/admission"
28 admissiontesting "k8s.io/apiserver/pkg/admission/testing"
29 "k8s.io/apiserver/pkg/authentication/user"
30 "k8s.io/client-go/informers"
31 api "k8s.io/kubernetes/pkg/apis/core"
32 "k8s.io/kubernetes/pkg/apis/scheduling"
33 v1 "k8s.io/kubernetes/pkg/apis/scheduling/v1"
34 "k8s.io/kubernetes/pkg/controller"
35 )
36
37 func addPriorityClasses(ctrl *Plugin, priorityClasses []*scheduling.PriorityClass) error {
38 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
39 ctrl.SetExternalKubeInformerFactory(informerFactory)
40
41 for _, c := range priorityClasses {
42 s := &schedulingv1.PriorityClass{}
43 if err := v1.Convert_scheduling_PriorityClass_To_v1_PriorityClass(c, s, nil); err != nil {
44 return err
45 }
46 informerFactory.Scheduling().V1().PriorityClasses().Informer().GetStore().Add(s)
47 }
48 return nil
49 }
50
51 var (
52 preemptNever = api.PreemptNever
53 preemptLowerPriority = api.PreemptLowerPriority
54 )
55
56 var defaultClass1 = &scheduling.PriorityClass{
57 TypeMeta: metav1.TypeMeta{
58 Kind: "PriorityClass",
59 },
60 ObjectMeta: metav1.ObjectMeta{
61 Name: "default1",
62 },
63 Value: 1000,
64 GlobalDefault: true,
65 }
66
67 var defaultClass2 = &scheduling.PriorityClass{
68 TypeMeta: metav1.TypeMeta{
69 Kind: "PriorityClass",
70 },
71 ObjectMeta: metav1.ObjectMeta{
72 Name: "default2",
73 },
74 Value: 2000,
75 GlobalDefault: true,
76 }
77
78 var nondefaultClass1 = &scheduling.PriorityClass{
79 TypeMeta: metav1.TypeMeta{
80 Kind: "PriorityClass",
81 },
82 ObjectMeta: metav1.ObjectMeta{
83 Name: "nondefault1",
84 },
85 Value: 2000,
86 Description: "Just a test priority class",
87 }
88
89 var systemClusterCritical = &scheduling.PriorityClass{
90 TypeMeta: metav1.TypeMeta{
91 Kind: "PriorityClass",
92 },
93 ObjectMeta: metav1.ObjectMeta{
94 Name: scheduling.SystemClusterCritical,
95 },
96 Value: scheduling.SystemCriticalPriority,
97 GlobalDefault: true,
98 }
99
100 var neverPreemptionPolicyClass = &scheduling.PriorityClass{
101 TypeMeta: metav1.TypeMeta{
102 Kind: "PriorityClass",
103 },
104 ObjectMeta: metav1.ObjectMeta{
105 Name: "nopreemptionpolicy",
106 },
107 Value: 2000,
108 Description: "Just a test priority class",
109 GlobalDefault: true,
110 PreemptionPolicy: &preemptNever,
111 }
112
113 var preemptionPolicyClass = &scheduling.PriorityClass{
114 TypeMeta: metav1.TypeMeta{
115 Kind: "PriorityClass",
116 },
117 ObjectMeta: metav1.ObjectMeta{
118 Name: "nopreemptionpolicy",
119 },
120 Value: 2000,
121 Description: "Just a test priority class",
122 GlobalDefault: true,
123 PreemptionPolicy: &preemptLowerPriority,
124 }
125
126 func TestPriorityClassAdmission(t *testing.T) {
127 var systemClass = &scheduling.PriorityClass{
128 TypeMeta: metav1.TypeMeta{
129 Kind: "PriorityClass",
130 },
131 ObjectMeta: metav1.ObjectMeta{
132 Name: scheduling.SystemPriorityClassPrefix + "test",
133 },
134 Value: scheduling.HighestUserDefinablePriority + 1,
135 Description: "Name has system critical prefix",
136 }
137
138 tests := []struct {
139 name string
140 existingClasses []*scheduling.PriorityClass
141 newClass *scheduling.PriorityClass
142 userInfo user.Info
143 operation admission.Operation
144 expectError bool
145 }{
146 {
147 "create operator with default class",
148 []*scheduling.PriorityClass{},
149 defaultClass1,
150 nil,
151 admission.Create,
152 false,
153 },
154 {
155 "create operator with one existing default class",
156 []*scheduling.PriorityClass{defaultClass1},
157 defaultClass2,
158 nil,
159 admission.Create,
160 true,
161 },
162 {
163 "create operator with system name and value allowed by admission controller",
164 []*scheduling.PriorityClass{},
165 systemClass,
166 &user.DefaultInfo{
167 Name: user.APIServerUser,
168 },
169 admission.Create,
170 false,
171 },
172 {
173 "update operator with default class",
174 []*scheduling.PriorityClass{},
175 defaultClass1,
176 nil,
177 admission.Update,
178 false,
179 },
180 {
181 "update operator with one existing default class",
182 []*scheduling.PriorityClass{defaultClass1},
183 defaultClass2,
184 nil,
185 admission.Update,
186 true,
187 },
188 {
189 "update operator with system name and value allowed by admission controller",
190 []*scheduling.PriorityClass{},
191 systemClass,
192 &user.DefaultInfo{
193 Name: user.APIServerUser,
194 },
195 admission.Update,
196 false,
197 },
198 {
199 "update operator with different default classes",
200 []*scheduling.PriorityClass{defaultClass1},
201 defaultClass2,
202 nil,
203 admission.Update,
204 true,
205 },
206 {
207 "delete operation with default class",
208 []*scheduling.PriorityClass{},
209 defaultClass1,
210 nil,
211 admission.Delete,
212 false,
213 },
214 }
215
216 for _, test := range tests {
217 klog.V(4).Infof("starting test %q", test.name)
218
219 ctrl := NewPlugin()
220
221 if err := addPriorityClasses(ctrl, test.existingClasses); err != nil {
222 t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
223 }
224
225 attrs := admission.NewAttributesRecord(
226 test.newClass,
227 nil,
228 scheduling.Kind("PriorityClass").WithVersion("version"),
229 "",
230 "",
231 scheduling.Resource("priorityclasses").WithVersion("version"),
232 "",
233 test.operation,
234 &metav1.CreateOptions{},
235 false,
236 test.userInfo,
237 )
238 err := ctrl.Validate(context.TODO(), attrs, nil)
239 klog.Infof("Got %v", err)
240 if err != nil && !test.expectError {
241 t.Errorf("Test %q: unexpected error received: %v", test.name, err)
242 }
243 if err == nil && test.expectError {
244 t.Errorf("Test %q: expected error and no error recevied", test.name)
245 }
246 }
247 }
248
249
250 func TestDefaultPriority(t *testing.T) {
251 pcResource := scheduling.Resource("priorityclasses").WithVersion("version")
252 pcKind := scheduling.Kind("PriorityClass").WithVersion("version")
253 updatedDefaultClass1 := *defaultClass1
254 updatedDefaultClass1.GlobalDefault = false
255
256 tests := []struct {
257 name string
258 classesBefore []*scheduling.PriorityClass
259 classesAfter []*scheduling.PriorityClass
260 attributes admission.Attributes
261 expectedDefaultBefore int32
262 expectedDefaultNameBefore string
263 expectedDefaultAfter int32
264 expectedDefaultNameAfter string
265 }{
266 {
267 name: "simple resolution with a default class",
268 classesBefore: []*scheduling.PriorityClass{defaultClass1},
269 classesAfter: []*scheduling.PriorityClass{defaultClass1},
270 attributes: nil,
271 expectedDefaultBefore: defaultClass1.Value,
272 expectedDefaultNameBefore: defaultClass1.Name,
273 expectedDefaultAfter: defaultClass1.Value,
274 expectedDefaultNameAfter: defaultClass1.Name,
275 },
276 {
277 name: "add a default class",
278 classesBefore: []*scheduling.PriorityClass{nondefaultClass1},
279 classesAfter: []*scheduling.PriorityClass{nondefaultClass1, defaultClass1},
280 attributes: admission.NewAttributesRecord(defaultClass1, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Create, &metav1.CreateOptions{}, false, nil),
281 expectedDefaultBefore: scheduling.DefaultPriorityWhenNoDefaultClassExists,
282 expectedDefaultNameBefore: "",
283 expectedDefaultAfter: defaultClass1.Value,
284 expectedDefaultNameAfter: defaultClass1.Name,
285 },
286 {
287 name: "multiple default classes resolves to the minimum value among them",
288 classesBefore: []*scheduling.PriorityClass{defaultClass1, defaultClass2},
289 classesAfter: []*scheduling.PriorityClass{defaultClass2},
290 attributes: admission.NewAttributesRecord(nil, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Delete, &metav1.DeleteOptions{}, false, nil),
291 expectedDefaultBefore: defaultClass1.Value,
292 expectedDefaultNameBefore: defaultClass1.Name,
293 expectedDefaultAfter: defaultClass2.Value,
294 expectedDefaultNameAfter: defaultClass2.Name,
295 },
296 {
297 name: "delete default priority class",
298 classesBefore: []*scheduling.PriorityClass{defaultClass1},
299 classesAfter: []*scheduling.PriorityClass{},
300 attributes: admission.NewAttributesRecord(nil, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Delete, &metav1.DeleteOptions{}, false, nil),
301 expectedDefaultBefore: defaultClass1.Value,
302 expectedDefaultNameBefore: defaultClass1.Name,
303 expectedDefaultAfter: scheduling.DefaultPriorityWhenNoDefaultClassExists,
304 expectedDefaultNameAfter: "",
305 },
306 {
307 name: "update default class and remove its global default",
308 classesBefore: []*scheduling.PriorityClass{defaultClass1},
309 classesAfter: []*scheduling.PriorityClass{&updatedDefaultClass1},
310 attributes: admission.NewAttributesRecord(&updatedDefaultClass1, defaultClass1, pcKind, "", defaultClass1.Name, pcResource, "", admission.Update, &metav1.UpdateOptions{}, false, nil),
311 expectedDefaultBefore: defaultClass1.Value,
312 expectedDefaultNameBefore: defaultClass1.Name,
313 expectedDefaultAfter: scheduling.DefaultPriorityWhenNoDefaultClassExists,
314 expectedDefaultNameAfter: "",
315 },
316 }
317
318 for _, test := range tests {
319 klog.V(4).Infof("starting test %q", test.name)
320 ctrl := NewPlugin()
321 if err := addPriorityClasses(ctrl, test.classesBefore); err != nil {
322 t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
323 }
324 pcName, defaultPriority, _, err := ctrl.getDefaultPriority()
325 if err != nil {
326 t.Errorf("Test %q: unexpected error while getting default priority: %v", test.name, err)
327 }
328 if err == nil &&
329 (defaultPriority != test.expectedDefaultBefore || pcName != test.expectedDefaultNameBefore) {
330 t.Errorf("Test %q: expected default priority %s(%d), but got %s(%d)",
331 test.name, test.expectedDefaultNameBefore, test.expectedDefaultBefore, pcName, defaultPriority)
332 }
333 if test.attributes != nil {
334 err := ctrl.Validate(context.TODO(), test.attributes, nil)
335 if err != nil {
336 t.Errorf("Test %q: unexpected error received: %v", test.name, err)
337 }
338 }
339 if err := addPriorityClasses(ctrl, test.classesAfter); err != nil {
340 t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
341 }
342 pcName, defaultPriority, _, err = ctrl.getDefaultPriority()
343 if err != nil {
344 t.Errorf("Test %q: unexpected error while getting default priority: %v", test.name, err)
345 }
346 if err == nil &&
347 (defaultPriority != test.expectedDefaultAfter || pcName != test.expectedDefaultNameAfter) {
348 t.Errorf("Test %q: expected default priority %s(%d), but got %s(%d)",
349 test.name, test.expectedDefaultNameAfter, test.expectedDefaultAfter, pcName, defaultPriority)
350 }
351 }
352 }
353
354 var zeroPriority = int32(0)
355 var intPriority = int32(1000)
356
357 func TestPodAdmission(t *testing.T) {
358 containerName := "container"
359
360 pods := []*api.Pod{
361
362 {
363 ObjectMeta: metav1.ObjectMeta{
364 Name: "pod-w-priorityclass",
365 Namespace: "namespace",
366 },
367 Spec: api.PodSpec{
368 Containers: []api.Container{
369 {
370 Name: containerName,
371 },
372 },
373 PriorityClassName: "default1",
374 },
375 },
376
377 {
378 ObjectMeta: metav1.ObjectMeta{
379 Name: "pod-wo-priorityclass",
380 Namespace: "namespace",
381 },
382 Spec: api.PodSpec{
383 Containers: []api.Container{
384 {
385 Name: containerName,
386 },
387 },
388 },
389 },
390
391 {
392 ObjectMeta: metav1.ObjectMeta{
393 Name: "pod-w-non-existing-priorityclass",
394 Namespace: "namespace",
395 },
396 Spec: api.PodSpec{
397 Containers: []api.Container{
398 {
399 Name: containerName,
400 },
401 },
402 PriorityClassName: "non-existing",
403 },
404 },
405
406 {
407 ObjectMeta: metav1.ObjectMeta{
408 Name: "pod-w-integer-priority",
409 Namespace: "namespace",
410 },
411 Spec: api.PodSpec{
412 Containers: []api.Container{
413 {
414 Name: containerName,
415 },
416 },
417 PriorityClassName: "default1",
418 Priority: &intPriority,
419 },
420 },
421
422 {
423 ObjectMeta: metav1.ObjectMeta{
424 Name: "pod-w-system-priority",
425 Namespace: metav1.NamespaceSystem,
426 },
427 Spec: api.PodSpec{
428 Containers: []api.Container{
429 {
430 Name: containerName,
431 },
432 },
433 PriorityClassName: scheduling.SystemClusterCritical,
434 },
435 },
436
437 {
438 ObjectMeta: metav1.ObjectMeta{
439 Name: "mirror-pod-w-system-priority",
440 Namespace: metav1.NamespaceSystem,
441 Annotations: map[string]string{api.MirrorPodAnnotationKey: ""},
442 },
443 Spec: api.PodSpec{
444 Containers: []api.Container{
445 {
446 Name: containerName,
447 },
448 },
449 PriorityClassName: "system-cluster-critical",
450 },
451 },
452
453 {
454 ObjectMeta: metav1.ObjectMeta{
455 Name: "mirror-pod-w-integer-priority",
456 Namespace: "namespace",
457 Annotations: map[string]string{api.MirrorPodAnnotationKey: ""},
458 },
459 Spec: api.PodSpec{
460 Containers: []api.Container{
461 {
462 Name: containerName,
463 },
464 },
465 PriorityClassName: "default1",
466 Priority: &intPriority,
467 },
468 },
469
470 {
471 ObjectMeta: metav1.ObjectMeta{
472 Name: "pod-w-system-priority-in-nonsystem-namespace",
473 Namespace: "non-system-namespace",
474 },
475 Spec: api.PodSpec{
476 Containers: []api.Container{
477 {
478 Name: containerName,
479 },
480 },
481 PriorityClassName: scheduling.SystemClusterCritical,
482 },
483 },
484
485 {
486 ObjectMeta: metav1.ObjectMeta{
487 Name: "pod-w-zero-priority-in-nonsystem-namespace",
488 Namespace: "non-system-namespace",
489 },
490 Spec: api.PodSpec{
491 Containers: []api.Container{
492 {
493 Name: containerName,
494 },
495 },
496 Priority: &zeroPriority,
497 },
498 },
499
500 {
501 ObjectMeta: metav1.ObjectMeta{
502 Name: "pod-w-priority-matching-default-priority",
503 Namespace: "non-system-namespace",
504 },
505 Spec: api.PodSpec{
506 Containers: []api.Container{
507 {
508 Name: containerName,
509 },
510 },
511 Priority: &defaultClass2.Value,
512 },
513 },
514
515 {
516 ObjectMeta: metav1.ObjectMeta{
517 Name: "pod-w-priority-matching-resolved-default-priority",
518 Namespace: metav1.NamespaceSystem,
519 },
520 Spec: api.PodSpec{
521 Containers: []api.Container{
522 {
523 Name: containerName,
524 },
525 },
526 PriorityClassName: systemClusterCritical.Name,
527 Priority: &systemClusterCritical.Value,
528 },
529 },
530
531 {
532 ObjectMeta: metav1.ObjectMeta{
533 Name: "pod-never-preemption-policy-matching-resolved-preemption-policy",
534 Namespace: metav1.NamespaceSystem,
535 },
536 Spec: api.PodSpec{
537 Containers: []api.Container{
538 {
539 Name: containerName,
540 },
541 },
542 PriorityClassName: neverPreemptionPolicyClass.Name,
543 Priority: &neverPreemptionPolicyClass.Value,
544 PreemptionPolicy: nil,
545 },
546 },
547
548 {
549 ObjectMeta: metav1.ObjectMeta{
550 Name: "pod-preemption-policy-matching-resolved-preemption-policy",
551 Namespace: metav1.NamespaceSystem,
552 },
553 Spec: api.PodSpec{
554 Containers: []api.Container{
555 {
556 Name: containerName,
557 },
558 },
559 PriorityClassName: preemptionPolicyClass.Name,
560 Priority: &preemptionPolicyClass.Value,
561 PreemptionPolicy: &preemptLowerPriority,
562 },
563 },
564
565 {
566 ObjectMeta: metav1.ObjectMeta{
567 Name: "pod-preemption-policy-not-matching-resolved-preemption-policy",
568 Namespace: metav1.NamespaceSystem,
569 },
570 Spec: api.PodSpec{
571 Containers: []api.Container{
572 {
573 Name: containerName,
574 },
575 },
576 PriorityClassName: preemptionPolicyClass.Name,
577 Priority: &preemptionPolicyClass.Value,
578 PreemptionPolicy: &preemptNever,
579 },
580 },
581 }
582
583 tests := []struct {
584 name string
585 existingClasses []*scheduling.PriorityClass
586
587
588 pod api.Pod
589 expectedPriority int32
590 expectError bool
591 expectPreemptionPolicy *api.PreemptionPolicy
592 }{
593 {
594 "Pod with priority class",
595 []*scheduling.PriorityClass{defaultClass1, nondefaultClass1},
596 *pods[0],
597 1000,
598 false,
599 nil,
600 },
601
602 {
603 "Pod without priority class",
604 []*scheduling.PriorityClass{defaultClass1},
605 *pods[1],
606 1000,
607 false,
608 nil,
609 },
610 {
611 "pod without priority class and no existing priority class",
612 []*scheduling.PriorityClass{},
613 *pods[1],
614 scheduling.DefaultPriorityWhenNoDefaultClassExists,
615 false,
616 nil,
617 },
618 {
619 "pod without priority class and no default class",
620 []*scheduling.PriorityClass{nondefaultClass1},
621 *pods[1],
622 scheduling.DefaultPriorityWhenNoDefaultClassExists,
623 false,
624 nil,
625 },
626 {
627 "pod with a system priority class",
628 []*scheduling.PriorityClass{systemClusterCritical},
629 *pods[4],
630 scheduling.SystemCriticalPriority,
631 false,
632 nil,
633 },
634 {
635 "Pod with non-existing priority class",
636 []*scheduling.PriorityClass{defaultClass1, nondefaultClass1},
637 *pods[2],
638 0,
639 true,
640 nil,
641 },
642 {
643 "pod with integer priority",
644 []*scheduling.PriorityClass{},
645 *pods[3],
646 0,
647 true,
648 nil,
649 },
650 {
651 "mirror pod with system priority class",
652 []*scheduling.PriorityClass{systemClusterCritical},
653 *pods[5],
654 scheduling.SystemCriticalPriority,
655 false,
656 nil,
657 },
658 {
659 "mirror pod with integer priority",
660 []*scheduling.PriorityClass{},
661 *pods[6],
662 0,
663 true,
664 nil,
665 },
666 {
667 "pod with system critical priority in non-system namespace",
668 []*scheduling.PriorityClass{systemClusterCritical},
669 *pods[7],
670 scheduling.SystemCriticalPriority,
671 false,
672 nil,
673 },
674 {
675 "pod with priority that matches computed priority",
676 []*scheduling.PriorityClass{nondefaultClass1},
677 *pods[8],
678 0,
679 false,
680 nil,
681 },
682 {
683 "pod with priority that matches default priority",
684 []*scheduling.PriorityClass{defaultClass2},
685 *pods[9],
686 defaultClass2.Value,
687 false,
688 nil,
689 },
690 {
691 "pod with priority that matches resolved priority",
692 []*scheduling.PriorityClass{systemClusterCritical},
693 *pods[10],
694 systemClusterCritical.Value,
695 false,
696 nil,
697 },
698 {
699 "pod with nil preemtpion policy",
700 []*scheduling.PriorityClass{preemptionPolicyClass},
701 *pods[11],
702 preemptionPolicyClass.Value,
703 false,
704 nil,
705 },
706 {
707 "pod with preemtpion policy that matches resolved preemtpion policy",
708 []*scheduling.PriorityClass{preemptionPolicyClass},
709 *pods[12],
710 preemptionPolicyClass.Value,
711 false,
712 &preemptLowerPriority,
713 },
714 {
715 "pod with preemtpion policy that does't matches resolved preemtpion policy",
716 []*scheduling.PriorityClass{preemptionPolicyClass},
717 *pods[13],
718 preemptionPolicyClass.Value,
719 true,
720 &preemptLowerPriority,
721 },
722 }
723
724 for _, test := range tests {
725 klog.V(4).Infof("starting test %q", test.name)
726 ctrl := NewPlugin()
727
728 if err := addPriorityClasses(ctrl, test.existingClasses); err != nil {
729 t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
730 }
731
732
733 attrs := admission.NewAttributesRecord(
734 &test.pod,
735 nil,
736 api.Kind("Pod").WithVersion("version"),
737 test.pod.ObjectMeta.Namespace,
738 "",
739 api.Resource("pods").WithVersion("version"),
740 "",
741 admission.Create,
742 &metav1.CreateOptions{},
743 false,
744 nil,
745 )
746 err := admissiontesting.WithReinvocationTesting(t, ctrl).Admit(context.TODO(), attrs, nil)
747 klog.Infof("Got %v", err)
748
749 if !test.expectError {
750 if err != nil {
751 t.Errorf("Test %q: unexpected error received: %v", test.name, err)
752 } else if *test.pod.Spec.Priority != test.expectedPriority {
753 t.Errorf("Test %q: expected priority is %d, but got %d.", test.name, test.expectedPriority, *test.pod.Spec.Priority)
754 } else if test.pod.Spec.PreemptionPolicy != nil && test.expectPreemptionPolicy != nil && *test.pod.Spec.PreemptionPolicy != *test.expectPreemptionPolicy {
755 t.Errorf("Test %q: expected preemption policy is %s, but got %s.", test.name, *test.expectPreemptionPolicy, *test.pod.Spec.PreemptionPolicy)
756 }
757 }
758 if err == nil && test.expectError {
759 t.Errorf("Test %q: expected error and no error recevied", test.name)
760 }
761 }
762 }
763
View as plain text