1
16
17 package eviction
18
19 import (
20 "context"
21 "fmt"
22 "reflect"
23 "sort"
24 "strings"
25 "testing"
26 "time"
27
28 "github.com/google/go-cmp/cmp"
29 v1 "k8s.io/api/core/v1"
30 "k8s.io/apimachinery/pkg/api/resource"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/types"
33 utilfeature "k8s.io/apiserver/pkg/util/feature"
34 featuregatetesting "k8s.io/component-base/featuregate/testing"
35 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
36
37 "k8s.io/kubernetes/pkg/features"
38 evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
39 kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
40 )
41
42 func quantityMustParse(value string) *resource.Quantity {
43 q := resource.MustParse(value)
44 return &q
45 }
46
47 func TestGetReclaimableThreshold(t *testing.T) {
48 testCases := map[string]struct {
49 thresholds []evictionapi.Threshold
50 }{
51 "": {
52 thresholds: []evictionapi.Threshold{
53 {
54 Signal: evictionapi.SignalAllocatableMemoryAvailable,
55 Operator: evictionapi.OpLessThan,
56 Value: evictionapi.ThresholdValue{
57 Quantity: quantityMustParse("150Mi"),
58 },
59 MinReclaim: &evictionapi.ThresholdValue{
60 Quantity: quantityMustParse("0"),
61 },
62 },
63 {
64 Signal: evictionapi.SignalMemoryAvailable,
65 Operator: evictionapi.OpLessThan,
66 Value: evictionapi.ThresholdValue{
67 Quantity: quantityMustParse("150Mi"),
68 },
69 MinReclaim: &evictionapi.ThresholdValue{
70 Quantity: quantityMustParse("0"),
71 },
72 },
73 {
74 Signal: evictionapi.SignalImageFsAvailable,
75 Operator: evictionapi.OpLessThan,
76 Value: evictionapi.ThresholdValue{
77 Quantity: quantityMustParse("150Mi"),
78 },
79 MinReclaim: &evictionapi.ThresholdValue{
80 Quantity: quantityMustParse("2Gi"),
81 },
82 },
83 {
84 Signal: evictionapi.SignalNodeFsAvailable,
85 Operator: evictionapi.OpLessThan,
86 Value: evictionapi.ThresholdValue{
87 Quantity: quantityMustParse("100Mi"),
88 },
89 MinReclaim: &evictionapi.ThresholdValue{
90 Quantity: quantityMustParse("1Gi"),
91 },
92 },
93 {
94 Signal: evictionapi.SignalContainerFsAvailable,
95 Operator: evictionapi.OpLessThan,
96 Value: evictionapi.ThresholdValue{
97 Quantity: quantityMustParse("100Mi"),
98 },
99 MinReclaim: &evictionapi.ThresholdValue{
100 Quantity: quantityMustParse("1Gi"),
101 },
102 },
103 },
104 },
105 }
106 for testName, testCase := range testCases {
107 sort.Sort(byEvictionPriority(testCase.thresholds))
108 _, _, ok := getReclaimableThreshold(testCase.thresholds)
109 if !ok {
110 t.Errorf("Didn't find reclaimable threshold, test: %v", testName)
111 }
112 }
113 }
114
115 func TestParseThresholdConfig(t *testing.T) {
116 gracePeriod, _ := time.ParseDuration("30s")
117 testCases := map[string]struct {
118 allocatableConfig []string
119 evictionHard map[string]string
120 evictionSoft map[string]string
121 evictionSoftGracePeriod map[string]string
122 evictionMinReclaim map[string]string
123 expectErr bool
124 expectThresholds []evictionapi.Threshold
125 }{
126 "no values": {
127 allocatableConfig: []string{},
128 evictionHard: map[string]string{},
129 evictionSoft: map[string]string{},
130 evictionSoftGracePeriod: map[string]string{},
131 evictionMinReclaim: map[string]string{},
132 expectErr: false,
133 expectThresholds: []evictionapi.Threshold{},
134 },
135 "all memory eviction values": {
136 allocatableConfig: []string{kubetypes.NodeAllocatableEnforcementKey},
137 evictionHard: map[string]string{"memory.available": "150Mi"},
138 evictionSoft: map[string]string{"memory.available": "300Mi"},
139 evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
140 evictionMinReclaim: map[string]string{"memory.available": "0"},
141 expectErr: false,
142 expectThresholds: []evictionapi.Threshold{
143 {
144 Signal: evictionapi.SignalAllocatableMemoryAvailable,
145 Operator: evictionapi.OpLessThan,
146 Value: evictionapi.ThresholdValue{
147 Quantity: quantityMustParse("150Mi"),
148 },
149 MinReclaim: &evictionapi.ThresholdValue{
150 Quantity: quantityMustParse("0"),
151 },
152 },
153 {
154 Signal: evictionapi.SignalMemoryAvailable,
155 Operator: evictionapi.OpLessThan,
156 Value: evictionapi.ThresholdValue{
157 Quantity: quantityMustParse("150Mi"),
158 },
159 MinReclaim: &evictionapi.ThresholdValue{
160 Quantity: quantityMustParse("0"),
161 },
162 },
163 {
164 Signal: evictionapi.SignalMemoryAvailable,
165 Operator: evictionapi.OpLessThan,
166 Value: evictionapi.ThresholdValue{
167 Quantity: quantityMustParse("300Mi"),
168 },
169 GracePeriod: gracePeriod,
170 MinReclaim: &evictionapi.ThresholdValue{
171 Quantity: quantityMustParse("0"),
172 },
173 },
174 },
175 },
176 "all memory eviction values in percentages": {
177 allocatableConfig: []string{},
178 evictionHard: map[string]string{"memory.available": "10%"},
179 evictionSoft: map[string]string{"memory.available": "30%"},
180 evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
181 evictionMinReclaim: map[string]string{"memory.available": "5%"},
182 expectErr: false,
183 expectThresholds: []evictionapi.Threshold{
184 {
185 Signal: evictionapi.SignalMemoryAvailable,
186 Operator: evictionapi.OpLessThan,
187 Value: evictionapi.ThresholdValue{
188 Percentage: 0.1,
189 },
190 MinReclaim: &evictionapi.ThresholdValue{
191 Percentage: 0.05,
192 },
193 },
194 {
195 Signal: evictionapi.SignalMemoryAvailable,
196 Operator: evictionapi.OpLessThan,
197 Value: evictionapi.ThresholdValue{
198 Percentage: 0.3,
199 },
200 GracePeriod: gracePeriod,
201 MinReclaim: &evictionapi.ThresholdValue{
202 Percentage: 0.05,
203 },
204 },
205 },
206 },
207 "disk eviction values": {
208 allocatableConfig: []string{},
209 evictionHard: map[string]string{"imagefs.available": "150Mi", "nodefs.available": "100Mi"},
210 evictionSoft: map[string]string{"imagefs.available": "300Mi", "nodefs.available": "200Mi"},
211 evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
212 evictionMinReclaim: map[string]string{"imagefs.available": "2Gi", "nodefs.available": "1Gi"},
213 expectErr: false,
214 expectThresholds: []evictionapi.Threshold{
215 {
216 Signal: evictionapi.SignalImageFsAvailable,
217 Operator: evictionapi.OpLessThan,
218 Value: evictionapi.ThresholdValue{
219 Quantity: quantityMustParse("150Mi"),
220 },
221 MinReclaim: &evictionapi.ThresholdValue{
222 Quantity: quantityMustParse("2Gi"),
223 },
224 },
225 {
226 Signal: evictionapi.SignalNodeFsAvailable,
227 Operator: evictionapi.OpLessThan,
228 Value: evictionapi.ThresholdValue{
229 Quantity: quantityMustParse("100Mi"),
230 },
231 MinReclaim: &evictionapi.ThresholdValue{
232 Quantity: quantityMustParse("1Gi"),
233 },
234 },
235 {
236 Signal: evictionapi.SignalImageFsAvailable,
237 Operator: evictionapi.OpLessThan,
238 Value: evictionapi.ThresholdValue{
239 Quantity: quantityMustParse("300Mi"),
240 },
241 GracePeriod: gracePeriod,
242 MinReclaim: &evictionapi.ThresholdValue{
243 Quantity: quantityMustParse("2Gi"),
244 },
245 },
246 {
247 Signal: evictionapi.SignalNodeFsAvailable,
248 Operator: evictionapi.OpLessThan,
249 Value: evictionapi.ThresholdValue{
250 Quantity: quantityMustParse("200Mi"),
251 },
252 GracePeriod: gracePeriod,
253 MinReclaim: &evictionapi.ThresholdValue{
254 Quantity: quantityMustParse("1Gi"),
255 },
256 },
257 },
258 },
259 "disk eviction values in percentages": {
260 allocatableConfig: []string{},
261 evictionHard: map[string]string{"imagefs.available": "15%", "nodefs.available": "10.5%"},
262 evictionSoft: map[string]string{"imagefs.available": "30%", "nodefs.available": "20.5%"},
263 evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
264 evictionMinReclaim: map[string]string{"imagefs.available": "10%", "nodefs.available": "5%"},
265 expectErr: false,
266 expectThresholds: []evictionapi.Threshold{
267 {
268 Signal: evictionapi.SignalImageFsAvailable,
269 Operator: evictionapi.OpLessThan,
270 Value: evictionapi.ThresholdValue{
271 Percentage: 0.15,
272 },
273 MinReclaim: &evictionapi.ThresholdValue{
274 Percentage: 0.1,
275 },
276 },
277 {
278 Signal: evictionapi.SignalNodeFsAvailable,
279 Operator: evictionapi.OpLessThan,
280 Value: evictionapi.ThresholdValue{
281 Percentage: 0.105,
282 },
283 MinReclaim: &evictionapi.ThresholdValue{
284 Percentage: 0.05,
285 },
286 },
287 {
288 Signal: evictionapi.SignalImageFsAvailable,
289 Operator: evictionapi.OpLessThan,
290 Value: evictionapi.ThresholdValue{
291 Percentage: 0.3,
292 },
293 GracePeriod: gracePeriod,
294 MinReclaim: &evictionapi.ThresholdValue{
295 Percentage: 0.1,
296 },
297 },
298 {
299 Signal: evictionapi.SignalNodeFsAvailable,
300 Operator: evictionapi.OpLessThan,
301 Value: evictionapi.ThresholdValue{
302 Percentage: 0.205,
303 },
304 GracePeriod: gracePeriod,
305 MinReclaim: &evictionapi.ThresholdValue{
306 Percentage: 0.05,
307 },
308 },
309 },
310 },
311 "inode eviction values": {
312 allocatableConfig: []string{},
313 evictionHard: map[string]string{"imagefs.inodesFree": "150Mi", "nodefs.inodesFree": "100Mi"},
314 evictionSoft: map[string]string{"imagefs.inodesFree": "300Mi", "nodefs.inodesFree": "200Mi"},
315 evictionSoftGracePeriod: map[string]string{"imagefs.inodesFree": "30s", "nodefs.inodesFree": "30s"},
316 evictionMinReclaim: map[string]string{"imagefs.inodesFree": "2Gi", "nodefs.inodesFree": "1Gi"},
317 expectErr: false,
318 expectThresholds: []evictionapi.Threshold{
319 {
320 Signal: evictionapi.SignalImageFsInodesFree,
321 Operator: evictionapi.OpLessThan,
322 Value: evictionapi.ThresholdValue{
323 Quantity: quantityMustParse("150Mi"),
324 },
325 MinReclaim: &evictionapi.ThresholdValue{
326 Quantity: quantityMustParse("2Gi"),
327 },
328 },
329 {
330 Signal: evictionapi.SignalNodeFsInodesFree,
331 Operator: evictionapi.OpLessThan,
332 Value: evictionapi.ThresholdValue{
333 Quantity: quantityMustParse("100Mi"),
334 },
335 MinReclaim: &evictionapi.ThresholdValue{
336 Quantity: quantityMustParse("1Gi"),
337 },
338 },
339 {
340 Signal: evictionapi.SignalImageFsInodesFree,
341 Operator: evictionapi.OpLessThan,
342 Value: evictionapi.ThresholdValue{
343 Quantity: quantityMustParse("300Mi"),
344 },
345 GracePeriod: gracePeriod,
346 MinReclaim: &evictionapi.ThresholdValue{
347 Quantity: quantityMustParse("2Gi"),
348 },
349 },
350 {
351 Signal: evictionapi.SignalNodeFsInodesFree,
352 Operator: evictionapi.OpLessThan,
353 Value: evictionapi.ThresholdValue{
354 Quantity: quantityMustParse("200Mi"),
355 },
356 GracePeriod: gracePeriod,
357 MinReclaim: &evictionapi.ThresholdValue{
358 Quantity: quantityMustParse("1Gi"),
359 },
360 },
361 },
362 },
363 "disable via 0%": {
364 allocatableConfig: []string{},
365 evictionHard: map[string]string{"memory.available": "0%"},
366 evictionSoft: map[string]string{"memory.available": "0%"},
367 expectErr: false,
368 expectThresholds: []evictionapi.Threshold{},
369 },
370 "disable via 100%": {
371 allocatableConfig: []string{},
372 evictionHard: map[string]string{"memory.available": "100%"},
373 evictionSoft: map[string]string{"memory.available": "100%"},
374 expectErr: false,
375 expectThresholds: []evictionapi.Threshold{},
376 },
377 "invalid-signal": {
378 allocatableConfig: []string{},
379 evictionHard: map[string]string{"mem.available": "150Mi"},
380 evictionSoft: map[string]string{},
381 evictionSoftGracePeriod: map[string]string{},
382 evictionMinReclaim: map[string]string{},
383 expectErr: true,
384 expectThresholds: []evictionapi.Threshold{},
385 },
386 "hard-signal-negative": {
387 allocatableConfig: []string{},
388 evictionHard: map[string]string{"memory.available": "-150Mi"},
389 evictionSoft: map[string]string{},
390 evictionSoftGracePeriod: map[string]string{},
391 evictionMinReclaim: map[string]string{},
392 expectErr: true,
393 expectThresholds: []evictionapi.Threshold{},
394 },
395 "hard-signal-negative-percentage": {
396 allocatableConfig: []string{},
397 evictionHard: map[string]string{"memory.available": "-15%"},
398 evictionSoft: map[string]string{},
399 evictionSoftGracePeriod: map[string]string{},
400 evictionMinReclaim: map[string]string{},
401 expectErr: true,
402 expectThresholds: []evictionapi.Threshold{},
403 },
404 "hard-signal-percentage-greater-than-100%": {
405 allocatableConfig: []string{},
406 evictionHard: map[string]string{"memory.available": "150%"},
407 evictionSoft: map[string]string{},
408 evictionSoftGracePeriod: map[string]string{},
409 evictionMinReclaim: map[string]string{},
410 expectErr: true,
411 expectThresholds: []evictionapi.Threshold{},
412 },
413 "soft-signal-negative": {
414 allocatableConfig: []string{},
415 evictionHard: map[string]string{},
416 evictionSoft: map[string]string{"memory.available": "-150Mi"},
417 evictionSoftGracePeriod: map[string]string{},
418 evictionMinReclaim: map[string]string{},
419 expectErr: true,
420 expectThresholds: []evictionapi.Threshold{},
421 },
422 "valid-and-invalid-signal": {
423 allocatableConfig: []string{},
424 evictionHard: map[string]string{"memory.available": "150Mi", "invalid.foo": "150Mi"},
425 evictionSoft: map[string]string{},
426 evictionSoftGracePeriod: map[string]string{},
427 evictionMinReclaim: map[string]string{},
428 expectErr: true,
429 expectThresholds: []evictionapi.Threshold{},
430 },
431 "soft-no-grace-period": {
432 allocatableConfig: []string{},
433 evictionHard: map[string]string{},
434 evictionSoft: map[string]string{"memory.available": "150Mi"},
435 evictionSoftGracePeriod: map[string]string{},
436 evictionMinReclaim: map[string]string{},
437 expectErr: true,
438 expectThresholds: []evictionapi.Threshold{},
439 },
440 "soft-negative-grace-period": {
441 allocatableConfig: []string{},
442 evictionHard: map[string]string{},
443 evictionSoft: map[string]string{"memory.available": "150Mi"},
444 evictionSoftGracePeriod: map[string]string{"memory.available": "-30s"},
445 evictionMinReclaim: map[string]string{},
446 expectErr: true,
447 expectThresholds: []evictionapi.Threshold{},
448 },
449 "negative-reclaim": {
450 allocatableConfig: []string{},
451 evictionHard: map[string]string{},
452 evictionSoft: map[string]string{},
453 evictionSoftGracePeriod: map[string]string{},
454 evictionMinReclaim: map[string]string{"memory.available": "-300Mi"},
455 expectErr: true,
456 expectThresholds: []evictionapi.Threshold{},
457 },
458 }
459 for testName, testCase := range testCases {
460 thresholds, err := ParseThresholdConfig(testCase.allocatableConfig, testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
461 if testCase.expectErr != (err != nil) {
462 t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err)
463 }
464 if !thresholdsEqual(testCase.expectThresholds, thresholds) {
465 t.Errorf("thresholds not as expected, test: %v, expected: %v, actual: %v", testName, testCase.expectThresholds, thresholds)
466 }
467 }
468 }
469
470 func TestAddAllocatableThresholds(t *testing.T) {
471
472
473 testCases := map[string]struct {
474 thresholds []evictionapi.Threshold
475 expected []evictionapi.Threshold
476 }{
477 "non-memory-signal": {
478 thresholds: []evictionapi.Threshold{
479 {
480 Signal: evictionapi.SignalImageFsAvailable,
481 Operator: evictionapi.OpLessThan,
482 Value: evictionapi.ThresholdValue{
483 Quantity: quantityMustParse("150Mi"),
484 },
485 GracePeriod: 0,
486 MinReclaim: &evictionapi.ThresholdValue{
487 Quantity: quantityMustParse("0"),
488 },
489 },
490 },
491 expected: []evictionapi.Threshold{
492 {
493 Signal: evictionapi.SignalImageFsAvailable,
494 Operator: evictionapi.OpLessThan,
495 Value: evictionapi.ThresholdValue{
496 Quantity: quantityMustParse("150Mi"),
497 },
498 GracePeriod: 0,
499 MinReclaim: &evictionapi.ThresholdValue{
500 Quantity: quantityMustParse("0"),
501 },
502 },
503 },
504 },
505 "memory-signal-with-grace": {
506 thresholds: []evictionapi.Threshold{
507 {
508 Signal: evictionapi.SignalMemoryAvailable,
509 Operator: evictionapi.OpLessThan,
510 Value: evictionapi.ThresholdValue{
511 Quantity: quantityMustParse("150Mi"),
512 },
513 GracePeriod: 10,
514 MinReclaim: &evictionapi.ThresholdValue{
515 Quantity: quantityMustParse("0"),
516 },
517 },
518 },
519 expected: []evictionapi.Threshold{
520 {
521 Signal: evictionapi.SignalMemoryAvailable,
522 Operator: evictionapi.OpLessThan,
523 Value: evictionapi.ThresholdValue{
524 Quantity: quantityMustParse("150Mi"),
525 },
526 GracePeriod: 10,
527 MinReclaim: &evictionapi.ThresholdValue{
528 Quantity: quantityMustParse("0"),
529 },
530 },
531 },
532 },
533 "memory-signal-without-grace": {
534 thresholds: []evictionapi.Threshold{
535 {
536 Signal: evictionapi.SignalMemoryAvailable,
537 Operator: evictionapi.OpLessThan,
538 Value: evictionapi.ThresholdValue{
539 Quantity: quantityMustParse("150Mi"),
540 },
541 GracePeriod: 0,
542 MinReclaim: &evictionapi.ThresholdValue{
543 Quantity: quantityMustParse("0"),
544 },
545 },
546 },
547 expected: []evictionapi.Threshold{
548 {
549 Signal: evictionapi.SignalAllocatableMemoryAvailable,
550 Operator: evictionapi.OpLessThan,
551 Value: evictionapi.ThresholdValue{
552 Quantity: quantityMustParse("150Mi"),
553 },
554 MinReclaim: &evictionapi.ThresholdValue{
555 Quantity: quantityMustParse("0"),
556 },
557 },
558 {
559 Signal: evictionapi.SignalMemoryAvailable,
560 Operator: evictionapi.OpLessThan,
561 Value: evictionapi.ThresholdValue{
562 Quantity: quantityMustParse("150Mi"),
563 },
564 GracePeriod: 0,
565 MinReclaim: &evictionapi.ThresholdValue{
566 Quantity: quantityMustParse("0"),
567 },
568 },
569 },
570 },
571 "memory-signal-without-grace-two-thresholds": {
572 thresholds: []evictionapi.Threshold{
573 {
574 Signal: evictionapi.SignalMemoryAvailable,
575 Operator: evictionapi.OpLessThan,
576 Value: evictionapi.ThresholdValue{
577 Quantity: quantityMustParse("150Mi"),
578 },
579 GracePeriod: 0,
580 MinReclaim: &evictionapi.ThresholdValue{
581 Quantity: quantityMustParse("0"),
582 },
583 },
584 {
585 Signal: evictionapi.SignalMemoryAvailable,
586 Operator: evictionapi.OpLessThan,
587 Value: evictionapi.ThresholdValue{
588 Quantity: quantityMustParse("200Mi"),
589 },
590 GracePeriod: 0,
591 MinReclaim: &evictionapi.ThresholdValue{
592 Quantity: quantityMustParse("1Gi"),
593 },
594 },
595 },
596 expected: []evictionapi.Threshold{
597 {
598 Signal: evictionapi.SignalAllocatableMemoryAvailable,
599 Operator: evictionapi.OpLessThan,
600 Value: evictionapi.ThresholdValue{
601 Quantity: quantityMustParse("150Mi"),
602 },
603 MinReclaim: &evictionapi.ThresholdValue{
604 Quantity: quantityMustParse("0"),
605 },
606 },
607 {
608 Signal: evictionapi.SignalMemoryAvailable,
609 Operator: evictionapi.OpLessThan,
610 Value: evictionapi.ThresholdValue{
611 Quantity: quantityMustParse("150Mi"),
612 },
613 GracePeriod: 0,
614 MinReclaim: &evictionapi.ThresholdValue{
615 Quantity: quantityMustParse("0"),
616 },
617 },
618 {
619 Signal: evictionapi.SignalAllocatableMemoryAvailable,
620 Operator: evictionapi.OpLessThan,
621 Value: evictionapi.ThresholdValue{
622 Quantity: quantityMustParse("200Mi"),
623 },
624 MinReclaim: &evictionapi.ThresholdValue{
625 Quantity: quantityMustParse("1Gi"),
626 },
627 },
628 {
629 Signal: evictionapi.SignalMemoryAvailable,
630 Operator: evictionapi.OpLessThan,
631 Value: evictionapi.ThresholdValue{
632 Quantity: quantityMustParse("200Mi"),
633 },
634 GracePeriod: 0,
635 MinReclaim: &evictionapi.ThresholdValue{
636 Quantity: quantityMustParse("1Gi"),
637 },
638 },
639 },
640 },
641 }
642 for testName, testCase := range testCases {
643 t.Run(testName, func(t *testing.T) {
644 if !thresholdsEqual(testCase.expected, addAllocatableThresholds(testCase.thresholds)) {
645 t.Errorf("Err not as expected, test: %v, Unexpected data: %s", testName, cmp.Diff(testCase.expected, addAllocatableThresholds(testCase.thresholds)))
646 }
647 })
648 }
649 }
650
651 func thresholdsEqual(expected []evictionapi.Threshold, actual []evictionapi.Threshold) bool {
652 if len(expected) != len(actual) {
653 return false
654 }
655 for _, aThreshold := range expected {
656 equal := false
657 for _, bThreshold := range actual {
658 if thresholdEqual(aThreshold, bThreshold) {
659 equal = true
660 }
661 }
662 if !equal {
663 return false
664 }
665 }
666 for _, aThreshold := range actual {
667 equal := false
668 for _, bThreshold := range expected {
669 if thresholdEqual(aThreshold, bThreshold) {
670 equal = true
671 }
672 }
673 if !equal {
674 return false
675 }
676 }
677 return true
678 }
679
680 func thresholdEqual(a evictionapi.Threshold, b evictionapi.Threshold) bool {
681 return a.GracePeriod == b.GracePeriod &&
682 a.Operator == b.Operator &&
683 a.Signal == b.Signal &&
684 compareThresholdValue(*a.MinReclaim, *b.MinReclaim) &&
685 compareThresholdValue(a.Value, b.Value)
686 }
687
688 func TestOrderedByExceedsRequestMemory(t *testing.T) {
689 below := newPod("below-requests", -1, []v1.Container{
690 newContainer("below-requests", newResourceList("", "200Mi", ""), newResourceList("", "", "")),
691 }, nil)
692 exceeds := newPod("exceeds-requests", 1, []v1.Container{
693 newContainer("exceeds-requests", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
694 }, nil)
695 stats := map[*v1.Pod]statsapi.PodStats{
696 below: newPodMemoryStats(below, resource.MustParse("199Mi")),
697 exceeds: newPodMemoryStats(exceeds, resource.MustParse("101Mi")),
698 }
699 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
700 result, found := stats[pod]
701 return result, found
702 }
703 pods := []*v1.Pod{below, exceeds}
704 orderedBy(exceedMemoryRequests(statsFn)).Sort(pods)
705
706 expected := []*v1.Pod{exceeds, below}
707 for i := range expected {
708 if pods[i] != expected[i] {
709 t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
710 }
711 }
712 }
713
714 func TestOrderedByExceedsRequestDisk(t *testing.T) {
715 below := newPod("below-requests", -1, []v1.Container{
716 newContainer("below-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("200Mi")}, newResourceList("", "", "")),
717 }, nil)
718 exceeds := newPod("exceeds-requests", 1, []v1.Container{
719 newContainer("exceeds-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("100Mi")}, newResourceList("", "", "")),
720 }, nil)
721 stats := map[*v1.Pod]statsapi.PodStats{
722 below: newPodDiskStats(below, resource.MustParse("100Mi"), resource.MustParse("99Mi"), resource.MustParse("0Mi")),
723 exceeds: newPodDiskStats(exceeds, resource.MustParse("90Mi"), resource.MustParse("11Mi"), resource.MustParse("0Mi")),
724 }
725 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
726 result, found := stats[pod]
727 return result, found
728 }
729 pods := []*v1.Pod{below, exceeds}
730 orderedBy(exceedDiskRequests(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
731
732 expected := []*v1.Pod{exceeds, below}
733 for i := range expected {
734 if pods[i] != expected[i] {
735 t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
736 }
737 }
738 }
739
740 func TestOrderedByPriority(t *testing.T) {
741 low := newPod("low-priority", -134, []v1.Container{
742 newContainer("low-priority", newResourceList("", "", ""), newResourceList("", "", "")),
743 }, nil)
744 medium := newPod("medium-priority", 1, []v1.Container{
745 newContainer("medium-priority", newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", "")),
746 }, nil)
747 high := newPod("high-priority", 12534, []v1.Container{
748 newContainer("high-priority", newResourceList("200m", "200Mi", ""), newResourceList("200m", "200Mi", "")),
749 }, nil)
750
751 pods := []*v1.Pod{high, medium, low}
752 orderedBy(priority).Sort(pods)
753
754 expected := []*v1.Pod{low, medium, high}
755 for i := range expected {
756 if pods[i] != expected[i] {
757 t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
758 }
759 }
760 }
761
762 func TestOrderedbyDisk(t *testing.T) {
763 pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
764 newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
765 }, []v1.Volume{
766 newVolume("local-volume", v1.VolumeSource{
767 EmptyDir: &v1.EmptyDirVolumeSource{},
768 }),
769 })
770 pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
771 newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
772 }, []v1.Volume{
773 newVolume("local-volume", v1.VolumeSource{
774 EmptyDir: &v1.EmptyDirVolumeSource{},
775 }),
776 })
777 pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
778 newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
779 }, []v1.Volume{
780 newVolume("local-volume", v1.VolumeSource{
781 EmptyDir: &v1.EmptyDirVolumeSource{},
782 }),
783 })
784 pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
785 newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
786 }, []v1.Volume{
787 newVolume("local-volume", v1.VolumeSource{
788 EmptyDir: &v1.EmptyDirVolumeSource{},
789 }),
790 })
791 pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
792 newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
793 }, []v1.Volume{
794 newVolume("local-volume", v1.VolumeSource{
795 EmptyDir: &v1.EmptyDirVolumeSource{},
796 }),
797 })
798 pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
799 newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
800 }, []v1.Volume{
801 newVolume("local-volume", v1.VolumeSource{
802 EmptyDir: &v1.EmptyDirVolumeSource{},
803 }),
804 })
805 stats := map[*v1.Pod]statsapi.PodStats{
806 pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")),
807 pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")),
808 pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")),
809 pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")),
810 pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")),
811 pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")),
812 }
813 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
814 result, found := stats[pod]
815 return result, found
816 }
817 pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
818 orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
819 expected := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6}
820 for i := range expected {
821 if pods[i] != expected[i] {
822 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
823 }
824 }
825 }
826
827 func TestOrderedbyInodes(t *testing.T) {
828 low := newPod("low", defaultPriority, []v1.Container{
829 newContainer("low", newResourceList("", "", ""), newResourceList("", "", "")),
830 }, []v1.Volume{
831 newVolume("local-volume", v1.VolumeSource{
832 EmptyDir: &v1.EmptyDirVolumeSource{},
833 }),
834 })
835 medium := newPod("medium", defaultPriority, []v1.Container{
836 newContainer("medium", newResourceList("", "", ""), newResourceList("", "", "")),
837 }, []v1.Volume{
838 newVolume("local-volume", v1.VolumeSource{
839 EmptyDir: &v1.EmptyDirVolumeSource{},
840 }),
841 })
842 high := newPod("high", defaultPriority, []v1.Container{
843 newContainer("high", newResourceList("", "", ""), newResourceList("", "", "")),
844 }, []v1.Volume{
845 newVolume("local-volume", v1.VolumeSource{
846 EmptyDir: &v1.EmptyDirVolumeSource{},
847 }),
848 })
849 stats := map[*v1.Pod]statsapi.PodStats{
850 low: newPodInodeStats(low, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("50000")),
851 medium: newPodInodeStats(medium, resource.MustParse("100000"), resource.MustParse("150000"), resource.MustParse("50000")),
852 high: newPodInodeStats(high, resource.MustParse("200000"), resource.MustParse("150000"), resource.MustParse("50000")),
853 }
854 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
855 result, found := stats[pod]
856 return result, found
857 }
858 pods := []*v1.Pod{low, medium, high}
859 orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
860 expected := []*v1.Pod{high, medium, low}
861 for i := range expected {
862 if pods[i] != expected[i] {
863 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
864 }
865 }
866 }
867
868
869 func TestOrderedByPriorityDisk(t *testing.T) {
870 pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
871 newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
872 }, []v1.Volume{
873 newVolume("local-volume", v1.VolumeSource{
874 EmptyDir: &v1.EmptyDirVolumeSource{},
875 }),
876 })
877 pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
878 newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
879 }, []v1.Volume{
880 newVolume("local-volume", v1.VolumeSource{
881 EmptyDir: &v1.EmptyDirVolumeSource{},
882 }),
883 })
884 pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
885 newContainer("above-requests-high-priority-high-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
886 }, []v1.Volume{
887 newVolume("local-volume", v1.VolumeSource{
888 EmptyDir: &v1.EmptyDirVolumeSource{},
889 }),
890 })
891 pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
892 newContainer("above-requests-high-priority-low-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
893 }, []v1.Volume{
894 newVolume("local-volume", v1.VolumeSource{
895 EmptyDir: &v1.EmptyDirVolumeSource{},
896 }),
897 })
898 pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
899 newContainer("below-requests-low-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
900 }, []v1.Volume{
901 newVolume("local-volume", v1.VolumeSource{
902 EmptyDir: &v1.EmptyDirVolumeSource{},
903 }),
904 })
905 pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
906 newContainer("below-requests-low-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
907 }, []v1.Volume{
908 newVolume("local-volume", v1.VolumeSource{
909 EmptyDir: &v1.EmptyDirVolumeSource{},
910 }),
911 })
912 pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
913 newContainer("below-requests-high-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
914 }, []v1.Volume{
915 newVolume("local-volume", v1.VolumeSource{
916 EmptyDir: &v1.EmptyDirVolumeSource{},
917 }),
918 })
919 pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
920 newContainer("below-requests-high-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
921 }, []v1.Volume{
922 newVolume("local-volume", v1.VolumeSource{
923 EmptyDir: &v1.EmptyDirVolumeSource{},
924 }),
925 })
926 stats := map[*v1.Pod]statsapi.PodStats{
927 pod1: newPodDiskStats(pod1, resource.MustParse("200Mi"), resource.MustParse("100Mi"), resource.MustParse("200Mi")),
928 pod2: newPodDiskStats(pod2, resource.MustParse("10Mi"), resource.MustParse("10Mi"), resource.MustParse("30Mi")),
929 pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("250Mi")),
930 pod4: newPodDiskStats(pod4, resource.MustParse("90Mi"), resource.MustParse("50Mi"), resource.MustParse("10Mi")),
931 pod5: newPodDiskStats(pod5, resource.MustParse("500Mi"), resource.MustParse("200Mi"), resource.MustParse("100Mi")),
932 pod6: newPodDiskStats(pod6, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")),
933 pod7: newPodDiskStats(pod7, resource.MustParse("250Mi"), resource.MustParse("500Mi"), resource.MustParse("50Mi")),
934 pod8: newPodDiskStats(pod8, resource.MustParse("100Mi"), resource.MustParse("60Mi"), resource.MustParse("40Mi")),
935 }
936 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
937 result, found := stats[pod]
938 return result, found
939 }
940 pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
941 expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
942 fsStatsToMeasure := []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}
943 orderedBy(exceedDiskRequests(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage), priority, disk(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage)).Sort(pods)
944 for i := range expected {
945 if pods[i] != expected[i] {
946 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
947 }
948 }
949 }
950
951
952 func TestOrderedByPriorityInodes(t *testing.T) {
953 pod1 := newPod("low-priority-high-usage", lowPriority, []v1.Container{
954 newContainer("low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
955 }, []v1.Volume{
956 newVolume("local-volume", v1.VolumeSource{
957 EmptyDir: &v1.EmptyDirVolumeSource{},
958 }),
959 })
960 pod2 := newPod("low-priority-low-usage", lowPriority, []v1.Container{
961 newContainer("low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
962 }, []v1.Volume{
963 newVolume("local-volume", v1.VolumeSource{
964 EmptyDir: &v1.EmptyDirVolumeSource{},
965 }),
966 })
967 pod3 := newPod("high-priority-high-usage", highPriority, []v1.Container{
968 newContainer("high-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
969 }, []v1.Volume{
970 newVolume("local-volume", v1.VolumeSource{
971 EmptyDir: &v1.EmptyDirVolumeSource{},
972 }),
973 })
974 pod4 := newPod("high-priority-low-usage", highPriority, []v1.Container{
975 newContainer("high-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
976 }, []v1.Volume{
977 newVolume("local-volume", v1.VolumeSource{
978 EmptyDir: &v1.EmptyDirVolumeSource{},
979 }),
980 })
981 stats := map[*v1.Pod]statsapi.PodStats{
982 pod1: newPodInodeStats(pod1, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("250000")),
983 pod2: newPodInodeStats(pod2, resource.MustParse("60000"), resource.MustParse("30000"), resource.MustParse("10000")),
984 pod3: newPodInodeStats(pod3, resource.MustParse("150000"), resource.MustParse("150000"), resource.MustParse("50000")),
985 pod4: newPodInodeStats(pod4, resource.MustParse("10000"), resource.MustParse("40000"), resource.MustParse("100000")),
986 }
987 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
988 result, found := stats[pod]
989 return result, found
990 }
991 pods := []*v1.Pod{pod4, pod3, pod2, pod1}
992 orderedBy(priority, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
993 expected := []*v1.Pod{pod1, pod2, pod3, pod4}
994 for i := range expected {
995 if pods[i] != expected[i] {
996 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
997 }
998 }
999 }
1000
1001
1002 func TestOrderedByMemory(t *testing.T) {
1003 pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
1004 newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
1005 }, nil)
1006 pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
1007 newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
1008 }, nil)
1009 pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
1010 newContainer("burstable-high", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
1011 }, nil)
1012 pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
1013 newContainer("burstable-low", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
1014 }, nil)
1015 pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
1016 newContainer("guaranteed-high", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
1017 }, nil)
1018 pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
1019 newContainer("guaranteed-low", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
1020 }, nil)
1021 stats := map[*v1.Pod]statsapi.PodStats{
1022 pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")),
1023 pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")),
1024 pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")),
1025 pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")),
1026 pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")),
1027 pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")),
1028 }
1029 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
1030 result, found := stats[pod]
1031 return result, found
1032 }
1033 pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
1034 orderedBy(memory(statsFn)).Sort(pods)
1035 expected := []*v1.Pod{pod3, pod1, pod2, pod4, pod5, pod6}
1036 for i := range expected {
1037 if pods[i] != expected[i] {
1038 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
1039 }
1040 }
1041 }
1042
1043
1044 func TestOrderedByPriorityMemory(t *testing.T) {
1045 pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
1046 newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
1047 }, nil)
1048 pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
1049 newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
1050 }, nil)
1051 pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
1052 newContainer("above-requests-high-priority-high-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
1053 }, nil)
1054 pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
1055 newContainer("above-requests-high-priority-low-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
1056 }, nil)
1057 pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
1058 newContainer("below-requests-low-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
1059 }, nil)
1060 pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
1061 newContainer("below-requests-low-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
1062 }, nil)
1063 pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
1064 newContainer("below-requests-high-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
1065 }, nil)
1066 pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
1067 newContainer("below-requests-high-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
1068 }, nil)
1069 stats := map[*v1.Pod]statsapi.PodStats{
1070 pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")),
1071 pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")),
1072 pod3: newPodMemoryStats(pod3, resource.MustParse("600Mi")),
1073 pod4: newPodMemoryStats(pod4, resource.MustParse("150Mi")),
1074 pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")),
1075 pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")),
1076 pod7: newPodMemoryStats(pod7, resource.MustParse("800Mi")),
1077 pod8: newPodMemoryStats(pod8, resource.MustParse("200Mi")),
1078 }
1079 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
1080 result, found := stats[pod]
1081 return result, found
1082 }
1083 pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
1084 expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
1085 orderedBy(exceedMemoryRequests(statsFn), priority, memory(statsFn)).Sort(pods)
1086 for i := range expected {
1087 if pods[i] != expected[i] {
1088 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
1089 }
1090 }
1091 }
1092
1093
1094 func TestOrderedByPriorityProcess(t *testing.T) {
1095 pod1 := newPod("low-priority-high-usage", lowPriority, nil, nil)
1096 pod2 := newPod("low-priority-low-usage", lowPriority, nil, nil)
1097 pod3 := newPod("high-priority-high-usage", highPriority, nil, nil)
1098 pod4 := newPod("high-priority-low-usage", highPriority, nil, nil)
1099 stats := map[*v1.Pod]statsapi.PodStats{
1100 pod1: newPodProcessStats(pod1, 20),
1101 pod2: newPodProcessStats(pod2, 6),
1102 pod3: newPodProcessStats(pod3, 20),
1103 pod4: newPodProcessStats(pod4, 5),
1104 }
1105 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
1106 result, found := stats[pod]
1107 return result, found
1108 }
1109 pods := []*v1.Pod{pod4, pod3, pod2, pod1}
1110 expected := []*v1.Pod{pod1, pod2, pod3, pod4}
1111 orderedBy(priority, process(statsFn)).Sort(pods)
1112 for i := range expected {
1113 if pods[i] != expected[i] {
1114 t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
1115 }
1116 }
1117 }
1118
1119 func TestSortByEvictionPriority(t *testing.T) {
1120 for _, tc := range []struct {
1121 name string
1122 thresholds []evictionapi.Threshold
1123 expected []evictionapi.Threshold
1124 }{
1125 {
1126 name: "empty threshold list",
1127 thresholds: []evictionapi.Threshold{},
1128 expected: []evictionapi.Threshold{},
1129 },
1130 {
1131 name: "memory first",
1132 thresholds: []evictionapi.Threshold{
1133 {
1134 Signal: evictionapi.SignalNodeFsAvailable,
1135 },
1136 {
1137 Signal: evictionapi.SignalPIDAvailable,
1138 },
1139 {
1140 Signal: evictionapi.SignalMemoryAvailable,
1141 },
1142 },
1143 expected: []evictionapi.Threshold{
1144 {
1145 Signal: evictionapi.SignalMemoryAvailable,
1146 },
1147 {
1148 Signal: evictionapi.SignalNodeFsAvailable,
1149 },
1150 {
1151 Signal: evictionapi.SignalPIDAvailable,
1152 },
1153 },
1154 },
1155 {
1156 name: "allocatable memory first",
1157 thresholds: []evictionapi.Threshold{
1158 {
1159 Signal: evictionapi.SignalNodeFsAvailable,
1160 },
1161 {
1162 Signal: evictionapi.SignalPIDAvailable,
1163 },
1164 {
1165 Signal: evictionapi.SignalAllocatableMemoryAvailable,
1166 },
1167 },
1168 expected: []evictionapi.Threshold{
1169 {
1170 Signal: evictionapi.SignalAllocatableMemoryAvailable,
1171 },
1172 {
1173 Signal: evictionapi.SignalNodeFsAvailable,
1174 },
1175 {
1176 Signal: evictionapi.SignalPIDAvailable,
1177 },
1178 },
1179 },
1180 } {
1181 t.Run(tc.name, func(t *testing.T) {
1182 sort.Sort(byEvictionPriority(tc.thresholds))
1183 for i := range tc.expected {
1184 if tc.thresholds[i].Signal != tc.expected[i].Signal {
1185 t.Errorf("At index %d, expected threshold with signal %s, but got %s", i, tc.expected[i].Signal, tc.thresholds[i].Signal)
1186 }
1187 }
1188
1189 })
1190 }
1191 }
1192
1193 type fakeSummaryProvider struct {
1194 result *statsapi.Summary
1195 }
1196
1197 func (f *fakeSummaryProvider) Get(ctx context.Context, updateStats bool) (*statsapi.Summary, error) {
1198 return f.result, nil
1199 }
1200
1201 func (f *fakeSummaryProvider) GetCPUAndMemoryStats(ctx context.Context) (*statsapi.Summary, error) {
1202 return f.result, nil
1203 }
1204
1205
1206
1207 func newPodStats(pod *v1.Pod, podWorkingSetBytes uint64) statsapi.PodStats {
1208 return statsapi.PodStats{
1209 PodRef: statsapi.PodReference{
1210 Name: pod.Name,
1211 Namespace: pod.Namespace,
1212 UID: string(pod.UID),
1213 },
1214 Memory: &statsapi.MemoryStats{
1215 WorkingSetBytes: &podWorkingSetBytes,
1216 },
1217 }
1218 }
1219
1220 func TestMakeSignalObservations(t *testing.T) {
1221 podMaker := func(name, namespace, uid string, numContainers int) *v1.Pod {
1222 pod := &v1.Pod{}
1223 pod.Name = name
1224 pod.Namespace = namespace
1225 pod.UID = types.UID(uid)
1226 pod.Spec = v1.PodSpec{}
1227 for i := 0; i < numContainers; i++ {
1228 pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
1229 Name: fmt.Sprintf("ctr%v", i),
1230 })
1231 }
1232 return pod
1233 }
1234 nodeAvailableBytes := uint64(1024 * 1024 * 1024)
1235 nodeWorkingSetBytes := uint64(1024 * 1024 * 1024)
1236 allocatableMemoryCapacity := uint64(5 * 1024 * 1024 * 1024)
1237 imageFsAvailableBytes := uint64(1024 * 1024)
1238 imageFsCapacityBytes := uint64(1024 * 1024 * 2)
1239 nodeFsAvailableBytes := uint64(1024)
1240 nodeFsCapacityBytes := uint64(1024 * 2)
1241 imageFsInodesFree := uint64(1024)
1242 imageFsInodes := uint64(1024 * 1024)
1243 nodeFsInodesFree := uint64(1024)
1244 nodeFsInodes := uint64(1024 * 1024)
1245 containerFsAvailableBytes := uint64(1024 * 1024 * 2)
1246 containerFsCapacityBytes := uint64(1024 * 1024 * 8)
1247 containerFsInodesFree := uint64(1024 * 2)
1248 containerFsInodes := uint64(1024 * 2)
1249 maxPID := int64(255816)
1250 numberOfRunningProcesses := int64(1000)
1251 fakeStats := &statsapi.Summary{
1252 Node: statsapi.NodeStats{
1253 Memory: &statsapi.MemoryStats{
1254 AvailableBytes: &nodeAvailableBytes,
1255 WorkingSetBytes: &nodeWorkingSetBytes,
1256 },
1257 Runtime: &statsapi.RuntimeStats{
1258 ImageFs: &statsapi.FsStats{
1259 AvailableBytes: &imageFsAvailableBytes,
1260 CapacityBytes: &imageFsCapacityBytes,
1261 InodesFree: &imageFsInodesFree,
1262 Inodes: &imageFsInodes,
1263 },
1264 ContainerFs: &statsapi.FsStats{
1265 AvailableBytes: &containerFsAvailableBytes,
1266 CapacityBytes: &containerFsCapacityBytes,
1267 InodesFree: &containerFsInodesFree,
1268 Inodes: &containerFsInodes,
1269 },
1270 },
1271 Rlimit: &statsapi.RlimitStats{
1272 MaxPID: &maxPID,
1273 NumOfRunningProcesses: &numberOfRunningProcesses,
1274 },
1275 Fs: &statsapi.FsStats{
1276 AvailableBytes: &nodeFsAvailableBytes,
1277 CapacityBytes: &nodeFsCapacityBytes,
1278 InodesFree: &nodeFsInodesFree,
1279 Inodes: &nodeFsInodes,
1280 },
1281 SystemContainers: []statsapi.ContainerStats{
1282 {
1283 Name: statsapi.SystemContainerPods,
1284 Memory: &statsapi.MemoryStats{
1285 AvailableBytes: &nodeAvailableBytes,
1286 WorkingSetBytes: &nodeWorkingSetBytes,
1287 },
1288 },
1289 },
1290 },
1291 Pods: []statsapi.PodStats{},
1292 }
1293 pods := []*v1.Pod{
1294 podMaker("pod1", "ns1", "uuid1", 1),
1295 podMaker("pod1", "ns2", "uuid2", 1),
1296 podMaker("pod3", "ns3", "uuid3", 1),
1297 }
1298 podWorkingSetBytes := uint64(1024 * 1024 * 1024)
1299 for _, pod := range pods {
1300 fakeStats.Pods = append(fakeStats.Pods, newPodStats(pod, podWorkingSetBytes))
1301 }
1302 res := quantityMustParse("5Gi")
1303
1304 if res.CmpInt64(int64(allocatableMemoryCapacity)) != 0 {
1305 t.Errorf("Expected Threshold %v to be equal to value %v", res.Value(), allocatableMemoryCapacity)
1306 }
1307 actualObservations, statsFunc := makeSignalObservations(fakeStats)
1308 allocatableMemQuantity, found := actualObservations[evictionapi.SignalAllocatableMemoryAvailable]
1309 if !found {
1310 t.Errorf("Expected allocatable memory observation, but didnt find one")
1311 }
1312 if expectedBytes := int64(nodeAvailableBytes); allocatableMemQuantity.available.Value() != expectedBytes {
1313 t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.available.Value())
1314 }
1315 if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); allocatableMemQuantity.capacity.Value() != expectedBytes {
1316 t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.capacity.Value())
1317 }
1318 memQuantity, found := actualObservations[evictionapi.SignalMemoryAvailable]
1319 if !found {
1320 t.Error("Expected available memory observation")
1321 }
1322 if expectedBytes := int64(nodeAvailableBytes); memQuantity.available.Value() != expectedBytes {
1323 t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.available.Value())
1324 }
1325 if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); memQuantity.capacity.Value() != expectedBytes {
1326 t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.capacity.Value())
1327 }
1328 nodeFsQuantity, found := actualObservations[evictionapi.SignalNodeFsAvailable]
1329 if !found {
1330 t.Error("Expected available nodefs observation")
1331 }
1332 if expectedBytes := int64(nodeFsAvailableBytes); nodeFsQuantity.available.Value() != expectedBytes {
1333 t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.available.Value())
1334 }
1335 if expectedBytes := int64(nodeFsCapacityBytes); nodeFsQuantity.capacity.Value() != expectedBytes {
1336 t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.capacity.Value())
1337 }
1338 nodeFsInodesQuantity, found := actualObservations[evictionapi.SignalNodeFsInodesFree]
1339 if !found {
1340 t.Error("Expected inodes free nodefs observation")
1341 }
1342 if expected := int64(nodeFsInodesFree); nodeFsInodesQuantity.available.Value() != expected {
1343 t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.available.Value())
1344 }
1345 if expected := int64(nodeFsInodes); nodeFsInodesQuantity.capacity.Value() != expected {
1346 t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.capacity.Value())
1347 }
1348 imageFsQuantity, found := actualObservations[evictionapi.SignalImageFsAvailable]
1349 if !found {
1350 t.Error("Expected available imagefs observation")
1351 }
1352 if expectedBytes := int64(imageFsAvailableBytes); imageFsQuantity.available.Value() != expectedBytes {
1353 t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.available.Value())
1354 }
1355 if expectedBytes := int64(imageFsCapacityBytes); imageFsQuantity.capacity.Value() != expectedBytes {
1356 t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.capacity.Value())
1357 }
1358 containerFsQuantity, found := actualObservations[evictionapi.SignalContainerFsAvailable]
1359 if !found {
1360 t.Error("Expected available containerfs observation")
1361 }
1362 if expectedBytes := int64(containerFsAvailableBytes); containerFsQuantity.available.Value() != expectedBytes {
1363 t.Errorf("Expected %v, actual: %v", expectedBytes, containerFsQuantity.available.Value())
1364 }
1365 if expectedBytes := int64(containerFsCapacityBytes); containerFsQuantity.capacity.Value() != expectedBytes {
1366 t.Errorf("Expected %v, actual: %v", expectedBytes, containerFsQuantity.capacity.Value())
1367 }
1368 imageFsInodesQuantity, found := actualObservations[evictionapi.SignalImageFsInodesFree]
1369 if !found {
1370 t.Error("Expected inodes free imagefs observation")
1371 }
1372 if expected := int64(imageFsInodesFree); imageFsInodesQuantity.available.Value() != expected {
1373 t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.available.Value())
1374 }
1375 if expected := int64(imageFsInodes); imageFsInodesQuantity.capacity.Value() != expected {
1376 t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.capacity.Value())
1377 }
1378 containerFsInodesQuantity, found := actualObservations[evictionapi.SignalContainerFsInodesFree]
1379 if !found {
1380 t.Error("Expected indoes free containerfs observation")
1381 }
1382 if expected := int64(containerFsInodesFree); containerFsInodesQuantity.available.Value() != expected {
1383 t.Errorf("Expected %v, actual: %v", expected, containerFsInodesQuantity.available.Value())
1384 }
1385 if expected := int64(containerFsInodes); containerFsInodesQuantity.capacity.Value() != expected {
1386 t.Errorf("Expected %v, actual: %v", expected, containerFsInodesQuantity.capacity.Value())
1387 }
1388
1389 pidQuantity, found := actualObservations[evictionapi.SignalPIDAvailable]
1390 if !found {
1391 t.Error("Expected available memory observation")
1392 }
1393 if expectedBytes := int64(maxPID); pidQuantity.capacity.Value() != expectedBytes {
1394 t.Errorf("Expected %v, actual: %v", expectedBytes, pidQuantity.capacity.Value())
1395 }
1396 if expectedBytes := int64(maxPID - numberOfRunningProcesses); pidQuantity.available.Value() != expectedBytes {
1397 t.Errorf("Expected %v, actual: %v", expectedBytes, pidQuantity.available.Value())
1398 }
1399 for _, pod := range pods {
1400 podStats, found := statsFunc(pod)
1401 if !found {
1402 t.Errorf("Pod stats were not found for pod %v", pod.UID)
1403 }
1404 if *podStats.Memory.WorkingSetBytes != podWorkingSetBytes {
1405 t.Errorf("Pod working set expected %v, actual: %v", podWorkingSetBytes, *podStats.Memory.WorkingSetBytes)
1406 }
1407 }
1408 }
1409
1410 func TestThresholdsMet(t *testing.T) {
1411 hardThreshold := evictionapi.Threshold{
1412 Signal: evictionapi.SignalMemoryAvailable,
1413 Operator: evictionapi.OpLessThan,
1414 Value: evictionapi.ThresholdValue{
1415 Quantity: quantityMustParse("1Gi"),
1416 },
1417 MinReclaim: &evictionapi.ThresholdValue{
1418 Quantity: quantityMustParse("500Mi"),
1419 },
1420 }
1421 testCases := map[string]struct {
1422 enforceMinReclaim bool
1423 thresholds []evictionapi.Threshold
1424 observations signalObservations
1425 result []evictionapi.Threshold
1426 }{
1427 "empty": {
1428 enforceMinReclaim: false,
1429 thresholds: []evictionapi.Threshold{},
1430 observations: signalObservations{},
1431 result: []evictionapi.Threshold{},
1432 },
1433 "threshold-met-memory": {
1434 enforceMinReclaim: false,
1435 thresholds: []evictionapi.Threshold{hardThreshold},
1436 observations: signalObservations{
1437 evictionapi.SignalMemoryAvailable: signalObservation{
1438 available: quantityMustParse("500Mi"),
1439 },
1440 },
1441 result: []evictionapi.Threshold{hardThreshold},
1442 },
1443 "threshold-not-met": {
1444 enforceMinReclaim: false,
1445 thresholds: []evictionapi.Threshold{hardThreshold},
1446 observations: signalObservations{
1447 evictionapi.SignalMemoryAvailable: signalObservation{
1448 available: quantityMustParse("2Gi"),
1449 },
1450 },
1451 result: []evictionapi.Threshold{},
1452 },
1453 "threshold-met-with-min-reclaim": {
1454 enforceMinReclaim: true,
1455 thresholds: []evictionapi.Threshold{hardThreshold},
1456 observations: signalObservations{
1457 evictionapi.SignalMemoryAvailable: signalObservation{
1458 available: quantityMustParse("1.05Gi"),
1459 },
1460 },
1461 result: []evictionapi.Threshold{hardThreshold},
1462 },
1463 "threshold-not-met-with-min-reclaim": {
1464 enforceMinReclaim: true,
1465 thresholds: []evictionapi.Threshold{hardThreshold},
1466 observations: signalObservations{
1467 evictionapi.SignalMemoryAvailable: signalObservation{
1468 available: quantityMustParse("2Gi"),
1469 },
1470 },
1471 result: []evictionapi.Threshold{},
1472 },
1473 }
1474 for testName, testCase := range testCases {
1475 actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinReclaim)
1476 if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
1477 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1478 }
1479 }
1480 }
1481
1482 func TestThresholdsUpdatedStats(t *testing.T) {
1483 updatedThreshold := evictionapi.Threshold{
1484 Signal: evictionapi.SignalMemoryAvailable,
1485 }
1486 locationUTC, err := time.LoadLocation("UTC")
1487 if err != nil {
1488 t.Error(err)
1489 return
1490 }
1491 testCases := map[string]struct {
1492 thresholds []evictionapi.Threshold
1493 observations signalObservations
1494 last signalObservations
1495 result []evictionapi.Threshold
1496 }{
1497 "empty": {
1498 thresholds: []evictionapi.Threshold{},
1499 observations: signalObservations{},
1500 last: signalObservations{},
1501 result: []evictionapi.Threshold{},
1502 },
1503 "no-time": {
1504 thresholds: []evictionapi.Threshold{updatedThreshold},
1505 observations: signalObservations{
1506 evictionapi.SignalMemoryAvailable: signalObservation{},
1507 },
1508 last: signalObservations{},
1509 result: []evictionapi.Threshold{updatedThreshold},
1510 },
1511 "no-last-observation": {
1512 thresholds: []evictionapi.Threshold{updatedThreshold},
1513 observations: signalObservations{
1514 evictionapi.SignalMemoryAvailable: signalObservation{
1515 time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
1516 },
1517 },
1518 last: signalObservations{},
1519 result: []evictionapi.Threshold{updatedThreshold},
1520 },
1521 "time-machine": {
1522 thresholds: []evictionapi.Threshold{updatedThreshold},
1523 observations: signalObservations{
1524 evictionapi.SignalMemoryAvailable: signalObservation{
1525 time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
1526 },
1527 },
1528 last: signalObservations{
1529 evictionapi.SignalMemoryAvailable: signalObservation{
1530 time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
1531 },
1532 },
1533 result: []evictionapi.Threshold{},
1534 },
1535 "same-observation": {
1536 thresholds: []evictionapi.Threshold{updatedThreshold},
1537 observations: signalObservations{
1538 evictionapi.SignalMemoryAvailable: signalObservation{
1539 time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
1540 },
1541 },
1542 last: signalObservations{
1543 evictionapi.SignalMemoryAvailable: signalObservation{
1544 time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
1545 },
1546 },
1547 result: []evictionapi.Threshold{},
1548 },
1549 "new-observation": {
1550 thresholds: []evictionapi.Threshold{updatedThreshold},
1551 observations: signalObservations{
1552 evictionapi.SignalMemoryAvailable: signalObservation{
1553 time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
1554 },
1555 },
1556 last: signalObservations{
1557 evictionapi.SignalMemoryAvailable: signalObservation{
1558 time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
1559 },
1560 },
1561 result: []evictionapi.Threshold{updatedThreshold},
1562 },
1563 }
1564 for testName, testCase := range testCases {
1565 actual := thresholdsUpdatedStats(testCase.thresholds, testCase.observations, testCase.last)
1566 if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
1567 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1568 }
1569 }
1570 }
1571
1572 func TestPercentageThresholdsMet(t *testing.T) {
1573 specificThresholds := []evictionapi.Threshold{
1574 {
1575 Signal: evictionapi.SignalMemoryAvailable,
1576 Operator: evictionapi.OpLessThan,
1577 Value: evictionapi.ThresholdValue{
1578 Percentage: 0.2,
1579 },
1580 MinReclaim: &evictionapi.ThresholdValue{
1581 Percentage: 0.05,
1582 },
1583 },
1584 {
1585 Signal: evictionapi.SignalNodeFsAvailable,
1586 Operator: evictionapi.OpLessThan,
1587 Value: evictionapi.ThresholdValue{
1588 Percentage: 0.3,
1589 },
1590 },
1591 }
1592
1593 testCases := map[string]struct {
1594 enforceMinRelaim bool
1595 thresholds []evictionapi.Threshold
1596 observations signalObservations
1597 result []evictionapi.Threshold
1598 }{
1599 "BothMet": {
1600 enforceMinRelaim: false,
1601 thresholds: specificThresholds,
1602 observations: signalObservations{
1603 evictionapi.SignalMemoryAvailable: signalObservation{
1604 available: quantityMustParse("100Mi"),
1605 capacity: quantityMustParse("1000Mi"),
1606 },
1607 evictionapi.SignalNodeFsAvailable: signalObservation{
1608 available: quantityMustParse("100Gi"),
1609 capacity: quantityMustParse("1000Gi"),
1610 },
1611 },
1612 result: specificThresholds,
1613 },
1614 "NoneMet": {
1615 enforceMinRelaim: false,
1616 thresholds: specificThresholds,
1617 observations: signalObservations{
1618 evictionapi.SignalMemoryAvailable: signalObservation{
1619 available: quantityMustParse("300Mi"),
1620 capacity: quantityMustParse("1000Mi"),
1621 },
1622 evictionapi.SignalNodeFsAvailable: signalObservation{
1623 available: quantityMustParse("400Gi"),
1624 capacity: quantityMustParse("1000Gi"),
1625 },
1626 },
1627 result: []evictionapi.Threshold{},
1628 },
1629 "DiskMet": {
1630 enforceMinRelaim: false,
1631 thresholds: specificThresholds,
1632 observations: signalObservations{
1633 evictionapi.SignalMemoryAvailable: signalObservation{
1634 available: quantityMustParse("300Mi"),
1635 capacity: quantityMustParse("1000Mi"),
1636 },
1637 evictionapi.SignalNodeFsAvailable: signalObservation{
1638 available: quantityMustParse("100Gi"),
1639 capacity: quantityMustParse("1000Gi"),
1640 },
1641 },
1642 result: []evictionapi.Threshold{specificThresholds[1]},
1643 },
1644 "MemoryMet": {
1645 enforceMinRelaim: false,
1646 thresholds: specificThresholds,
1647 observations: signalObservations{
1648 evictionapi.SignalMemoryAvailable: signalObservation{
1649 available: quantityMustParse("100Mi"),
1650 capacity: quantityMustParse("1000Mi"),
1651 },
1652 evictionapi.SignalNodeFsAvailable: signalObservation{
1653 available: quantityMustParse("400Gi"),
1654 capacity: quantityMustParse("1000Gi"),
1655 },
1656 },
1657 result: []evictionapi.Threshold{specificThresholds[0]},
1658 },
1659 "MemoryMetWithMinReclaim": {
1660 enforceMinRelaim: true,
1661 thresholds: specificThresholds,
1662 observations: signalObservations{
1663 evictionapi.SignalMemoryAvailable: signalObservation{
1664 available: quantityMustParse("225Mi"),
1665 capacity: quantityMustParse("1000Mi"),
1666 },
1667 },
1668 result: []evictionapi.Threshold{specificThresholds[0]},
1669 },
1670 "MemoryNotMetWithMinReclaim": {
1671 enforceMinRelaim: true,
1672 thresholds: specificThresholds,
1673 observations: signalObservations{
1674 evictionapi.SignalMemoryAvailable: signalObservation{
1675 available: quantityMustParse("300Mi"),
1676 capacity: quantityMustParse("1000Mi"),
1677 },
1678 },
1679 result: []evictionapi.Threshold{},
1680 },
1681 }
1682 for testName, testCase := range testCases {
1683 actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinRelaim)
1684 if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
1685 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1686 }
1687 }
1688 }
1689
1690 func TestThresholdsFirstObservedAt(t *testing.T) {
1691 hardThreshold := evictionapi.Threshold{
1692 Signal: evictionapi.SignalMemoryAvailable,
1693 Operator: evictionapi.OpLessThan,
1694 Value: evictionapi.ThresholdValue{
1695 Quantity: quantityMustParse("1Gi"),
1696 },
1697 }
1698 now := metav1.Now()
1699 oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
1700 testCases := map[string]struct {
1701 thresholds []evictionapi.Threshold
1702 lastObservedAt thresholdsObservedAt
1703 now time.Time
1704 result thresholdsObservedAt
1705 }{
1706 "empty": {
1707 thresholds: []evictionapi.Threshold{},
1708 lastObservedAt: thresholdsObservedAt{},
1709 now: now.Time,
1710 result: thresholdsObservedAt{},
1711 },
1712 "no-previous-observation": {
1713 thresholds: []evictionapi.Threshold{hardThreshold},
1714 lastObservedAt: thresholdsObservedAt{},
1715 now: now.Time,
1716 result: thresholdsObservedAt{
1717 hardThreshold: now.Time,
1718 },
1719 },
1720 "previous-observation": {
1721 thresholds: []evictionapi.Threshold{hardThreshold},
1722 lastObservedAt: thresholdsObservedAt{
1723 hardThreshold: oldTime.Time,
1724 },
1725 now: now.Time,
1726 result: thresholdsObservedAt{
1727 hardThreshold: oldTime.Time,
1728 },
1729 },
1730 }
1731 for testName, testCase := range testCases {
1732 actual := thresholdsFirstObservedAt(testCase.thresholds, testCase.lastObservedAt, testCase.now)
1733 if !reflect.DeepEqual(actual, testCase.result) {
1734 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1735 }
1736 }
1737 }
1738
1739 func TestThresholdsMetGracePeriod(t *testing.T) {
1740 now := metav1.Now()
1741 hardThreshold := evictionapi.Threshold{
1742 Signal: evictionapi.SignalMemoryAvailable,
1743 Operator: evictionapi.OpLessThan,
1744 Value: evictionapi.ThresholdValue{
1745 Quantity: quantityMustParse("1Gi"),
1746 },
1747 }
1748 softThreshold := evictionapi.Threshold{
1749 Signal: evictionapi.SignalMemoryAvailable,
1750 Operator: evictionapi.OpLessThan,
1751 Value: evictionapi.ThresholdValue{
1752 Quantity: quantityMustParse("2Gi"),
1753 },
1754 GracePeriod: 1 * time.Minute,
1755 }
1756 oldTime := metav1.NewTime(now.Time.Add(-2 * time.Minute))
1757 testCases := map[string]struct {
1758 observedAt thresholdsObservedAt
1759 now time.Time
1760 result []evictionapi.Threshold
1761 }{
1762 "empty": {
1763 observedAt: thresholdsObservedAt{},
1764 now: now.Time,
1765 result: []evictionapi.Threshold{},
1766 },
1767 "hard-threshold-met": {
1768 observedAt: thresholdsObservedAt{
1769 hardThreshold: now.Time,
1770 },
1771 now: now.Time,
1772 result: []evictionapi.Threshold{hardThreshold},
1773 },
1774 "soft-threshold-not-met": {
1775 observedAt: thresholdsObservedAt{
1776 softThreshold: now.Time,
1777 },
1778 now: now.Time,
1779 result: []evictionapi.Threshold{},
1780 },
1781 "soft-threshold-met": {
1782 observedAt: thresholdsObservedAt{
1783 softThreshold: oldTime.Time,
1784 },
1785 now: now.Time,
1786 result: []evictionapi.Threshold{softThreshold},
1787 },
1788 }
1789 for testName, testCase := range testCases {
1790 actual := thresholdsMetGracePeriod(testCase.observedAt, now.Time)
1791 if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
1792 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1793 }
1794 }
1795 }
1796
1797 func TestNodeConditions(t *testing.T) {
1798 testCases := map[string]struct {
1799 inputs []evictionapi.Threshold
1800 result []v1.NodeConditionType
1801 }{
1802 "empty-list": {
1803 inputs: []evictionapi.Threshold{},
1804 result: []v1.NodeConditionType{},
1805 },
1806 "memory.available": {
1807 inputs: []evictionapi.Threshold{
1808 {Signal: evictionapi.SignalMemoryAvailable},
1809 },
1810 result: []v1.NodeConditionType{v1.NodeMemoryPressure},
1811 },
1812 }
1813 for testName, testCase := range testCases {
1814 actual := nodeConditions(testCase.inputs)
1815 if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
1816 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1817 }
1818 }
1819 }
1820
1821 func TestNodeConditionsLastObservedAt(t *testing.T) {
1822 now := metav1.Now()
1823 oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
1824 testCases := map[string]struct {
1825 nodeConditions []v1.NodeConditionType
1826 lastObservedAt nodeConditionsObservedAt
1827 now time.Time
1828 result nodeConditionsObservedAt
1829 }{
1830 "no-previous-observation": {
1831 nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
1832 lastObservedAt: nodeConditionsObservedAt{},
1833 now: now.Time,
1834 result: nodeConditionsObservedAt{
1835 v1.NodeMemoryPressure: now.Time,
1836 },
1837 },
1838 "previous-observation": {
1839 nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
1840 lastObservedAt: nodeConditionsObservedAt{
1841 v1.NodeMemoryPressure: oldTime.Time,
1842 },
1843 now: now.Time,
1844 result: nodeConditionsObservedAt{
1845 v1.NodeMemoryPressure: now.Time,
1846 },
1847 },
1848 "old-observation": {
1849 nodeConditions: []v1.NodeConditionType{},
1850 lastObservedAt: nodeConditionsObservedAt{
1851 v1.NodeMemoryPressure: oldTime.Time,
1852 },
1853 now: now.Time,
1854 result: nodeConditionsObservedAt{
1855 v1.NodeMemoryPressure: oldTime.Time,
1856 },
1857 },
1858 }
1859 for testName, testCase := range testCases {
1860 actual := nodeConditionsLastObservedAt(testCase.nodeConditions, testCase.lastObservedAt, testCase.now)
1861 if !reflect.DeepEqual(actual, testCase.result) {
1862 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1863 }
1864 }
1865 }
1866
1867 func TestNodeConditionsObservedSince(t *testing.T) {
1868 now := metav1.Now()
1869 observedTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
1870 testCases := map[string]struct {
1871 observedAt nodeConditionsObservedAt
1872 period time.Duration
1873 now time.Time
1874 result []v1.NodeConditionType
1875 }{
1876 "in-period": {
1877 observedAt: nodeConditionsObservedAt{
1878 v1.NodeMemoryPressure: observedTime.Time,
1879 },
1880 period: 2 * time.Minute,
1881 now: now.Time,
1882 result: []v1.NodeConditionType{v1.NodeMemoryPressure},
1883 },
1884 "out-of-period": {
1885 observedAt: nodeConditionsObservedAt{
1886 v1.NodeMemoryPressure: observedTime.Time,
1887 },
1888 period: 30 * time.Second,
1889 now: now.Time,
1890 result: []v1.NodeConditionType{},
1891 },
1892 }
1893 for testName, testCase := range testCases {
1894 actual := nodeConditionsObservedSince(testCase.observedAt, testCase.period, testCase.now)
1895 if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
1896 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1897 }
1898 }
1899 }
1900
1901 func TestHasNodeConditions(t *testing.T) {
1902 testCases := map[string]struct {
1903 inputs []v1.NodeConditionType
1904 item v1.NodeConditionType
1905 result bool
1906 }{
1907 "has-condition": {
1908 inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure, v1.NodeMemoryPressure},
1909 item: v1.NodeMemoryPressure,
1910 result: true,
1911 },
1912 "does-not-have-condition": {
1913 inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure},
1914 item: v1.NodeMemoryPressure,
1915 result: false,
1916 },
1917 }
1918 for testName, testCase := range testCases {
1919 if actual := hasNodeCondition(testCase.inputs, testCase.item); actual != testCase.result {
1920 t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
1921 }
1922 }
1923 }
1924
1925 func TestParsePercentage(t *testing.T) {
1926 testCases := map[string]struct {
1927 hasError bool
1928 value float32
1929 }{
1930 "blah": {
1931 hasError: true,
1932 },
1933 "25.5%": {
1934 value: 0.255,
1935 },
1936 "foo%": {
1937 hasError: true,
1938 },
1939 "12%345": {
1940 hasError: true,
1941 },
1942 }
1943 for input, expected := range testCases {
1944 value, err := parsePercentage(input)
1945 if (err != nil) != expected.hasError {
1946 t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.hasError, err != nil)
1947 }
1948 if value != expected.value {
1949 t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.value, value)
1950 }
1951 }
1952 }
1953
1954 func TestCompareThresholdValue(t *testing.T) {
1955 testCases := []struct {
1956 a, b evictionapi.ThresholdValue
1957 equal bool
1958 }{
1959 {
1960 a: evictionapi.ThresholdValue{
1961 Quantity: resource.NewQuantity(123, resource.BinarySI),
1962 },
1963 b: evictionapi.ThresholdValue{
1964 Quantity: resource.NewQuantity(123, resource.BinarySI),
1965 },
1966 equal: true,
1967 },
1968 {
1969 a: evictionapi.ThresholdValue{
1970 Quantity: resource.NewQuantity(123, resource.BinarySI),
1971 },
1972 b: evictionapi.ThresholdValue{
1973 Quantity: resource.NewQuantity(456, resource.BinarySI),
1974 },
1975 equal: false,
1976 },
1977 {
1978 a: evictionapi.ThresholdValue{
1979 Quantity: resource.NewQuantity(123, resource.BinarySI),
1980 },
1981 b: evictionapi.ThresholdValue{
1982 Percentage: 0.1,
1983 },
1984 equal: false,
1985 },
1986 {
1987 a: evictionapi.ThresholdValue{
1988 Percentage: 0.1,
1989 },
1990 b: evictionapi.ThresholdValue{
1991 Percentage: 0.1,
1992 },
1993 equal: true,
1994 },
1995 {
1996 a: evictionapi.ThresholdValue{
1997 Percentage: 0.2,
1998 },
1999 b: evictionapi.ThresholdValue{
2000 Percentage: 0.1,
2001 },
2002 equal: false,
2003 },
2004 }
2005
2006 for i, testCase := range testCases {
2007 if compareThresholdValue(testCase.a, testCase.b) != testCase.equal ||
2008 compareThresholdValue(testCase.b, testCase.a) != testCase.equal {
2009 t.Errorf("Test case: %v failed", i)
2010 }
2011 }
2012 }
2013 func TestAddContainerFsThresholds(t *testing.T) {
2014 gracePeriod := time.Duration(1)
2015 testCases := []struct {
2016 description string
2017 imageFs bool
2018 containerFs bool
2019 expectedContainerFsHard evictionapi.Threshold
2020 expectedContainerFsSoft evictionapi.Threshold
2021 expectedContainerFsINodesHard evictionapi.Threshold
2022 expectedContainerFsINodesSoft evictionapi.Threshold
2023 expectErr bool
2024 thresholdList []evictionapi.Threshold
2025 }{
2026 {
2027 description: "single filesystem",
2028 imageFs: false,
2029 containerFs: false,
2030 expectedContainerFsHard: evictionapi.Threshold{
2031 Signal: evictionapi.SignalContainerFsAvailable,
2032 Operator: evictionapi.OpLessThan,
2033 Value: evictionapi.ThresholdValue{
2034 Quantity: quantityMustParse("100Mi"),
2035 },
2036 MinReclaim: &evictionapi.ThresholdValue{
2037 Quantity: quantityMustParse("1Gi"),
2038 },
2039 },
2040 expectedContainerFsSoft: evictionapi.Threshold{
2041 Signal: evictionapi.SignalContainerFsAvailable,
2042 Operator: evictionapi.OpLessThan,
2043 Value: evictionapi.ThresholdValue{
2044 Quantity: quantityMustParse("200Mi"),
2045 },
2046 GracePeriod: gracePeriod,
2047 MinReclaim: &evictionapi.ThresholdValue{
2048 Quantity: quantityMustParse("1Gi"),
2049 },
2050 },
2051 expectedContainerFsINodesHard: evictionapi.Threshold{
2052 Signal: evictionapi.SignalContainerFsInodesFree,
2053 Operator: evictionapi.OpLessThan,
2054 Value: evictionapi.ThresholdValue{
2055 Quantity: quantityMustParse("100Mi"),
2056 },
2057 MinReclaim: &evictionapi.ThresholdValue{
2058 Quantity: quantityMustParse("1Gi"),
2059 },
2060 },
2061 expectedContainerFsINodesSoft: evictionapi.Threshold{
2062 Signal: evictionapi.SignalContainerFsInodesFree,
2063 Operator: evictionapi.OpLessThan,
2064 Value: evictionapi.ThresholdValue{
2065 Quantity: quantityMustParse("200Mi"),
2066 },
2067 MinReclaim: &evictionapi.ThresholdValue{
2068 Quantity: quantityMustParse("2Gi"),
2069 },
2070 GracePeriod: gracePeriod,
2071 },
2072
2073 thresholdList: []evictionapi.Threshold{
2074 {
2075 Signal: evictionapi.SignalNodeFsAvailable,
2076 Operator: evictionapi.OpLessThan,
2077 Value: evictionapi.ThresholdValue{
2078 Quantity: quantityMustParse("100Mi"),
2079 },
2080 MinReclaim: &evictionapi.ThresholdValue{
2081 Quantity: quantityMustParse("1Gi"),
2082 },
2083 },
2084 {
2085 Signal: evictionapi.SignalNodeFsAvailable,
2086 Operator: evictionapi.OpLessThan,
2087 Value: evictionapi.ThresholdValue{
2088 Quantity: quantityMustParse("200Mi"),
2089 },
2090 GracePeriod: gracePeriod,
2091 MinReclaim: &evictionapi.ThresholdValue{
2092 Quantity: quantityMustParse("1Gi"),
2093 },
2094 },
2095 {
2096 Signal: evictionapi.SignalNodeFsInodesFree,
2097 Operator: evictionapi.OpLessThan,
2098 Value: evictionapi.ThresholdValue{
2099 Quantity: quantityMustParse("200Mi"),
2100 },
2101 MinReclaim: &evictionapi.ThresholdValue{
2102 Quantity: quantityMustParse("2Gi"),
2103 },
2104 GracePeriod: gracePeriod,
2105 },
2106 {
2107 Signal: evictionapi.SignalNodeFsInodesFree,
2108 Operator: evictionapi.OpLessThan,
2109 Value: evictionapi.ThresholdValue{
2110 Quantity: quantityMustParse("100Mi"),
2111 },
2112 MinReclaim: &evictionapi.ThresholdValue{
2113 Quantity: quantityMustParse("1Gi"),
2114 },
2115 },
2116 {
2117 Signal: evictionapi.SignalImageFsAvailable,
2118 Operator: evictionapi.OpLessThan,
2119 Value: evictionapi.ThresholdValue{
2120 Quantity: quantityMustParse("100Mi"),
2121 },
2122 MinReclaim: &evictionapi.ThresholdValue{
2123 Quantity: quantityMustParse("1Gi"),
2124 },
2125 },
2126
2127 {
2128 Signal: evictionapi.SignalImageFsAvailable,
2129 Operator: evictionapi.OpLessThan,
2130 Value: evictionapi.ThresholdValue{
2131 Quantity: quantityMustParse("300Mi"),
2132 },
2133 GracePeriod: gracePeriod,
2134 MinReclaim: &evictionapi.ThresholdValue{
2135 Quantity: quantityMustParse("2Gi"),
2136 },
2137 },
2138 {
2139 Signal: evictionapi.SignalImageFsInodesFree,
2140 Operator: evictionapi.OpLessThan,
2141 Value: evictionapi.ThresholdValue{
2142 Quantity: quantityMustParse("150Mi"),
2143 },
2144 MinReclaim: &evictionapi.ThresholdValue{
2145 Quantity: quantityMustParse("2Gi"),
2146 },
2147 GracePeriod: gracePeriod,
2148 },
2149 {
2150 Signal: evictionapi.SignalImageFsInodesFree,
2151 Operator: evictionapi.OpLessThan,
2152 Value: evictionapi.ThresholdValue{
2153 Quantity: quantityMustParse("150Mi"),
2154 },
2155 MinReclaim: &evictionapi.ThresholdValue{
2156 Quantity: quantityMustParse("2Gi"),
2157 },
2158 },
2159 },
2160 },
2161 {
2162 description: "image filesystem",
2163 imageFs: true,
2164 containerFs: false,
2165 expectedContainerFsHard: evictionapi.Threshold{
2166 Signal: evictionapi.SignalContainerFsAvailable,
2167 Operator: evictionapi.OpLessThan,
2168 Value: evictionapi.ThresholdValue{
2169 Quantity: quantityMustParse("150Mi"),
2170 },
2171 MinReclaim: &evictionapi.ThresholdValue{
2172 Quantity: quantityMustParse("1.5Gi"),
2173 },
2174 },
2175 expectedContainerFsSoft: evictionapi.Threshold{
2176 Signal: evictionapi.SignalContainerFsAvailable,
2177 Operator: evictionapi.OpLessThan,
2178 Value: evictionapi.ThresholdValue{
2179 Quantity: quantityMustParse("300Mi"),
2180 },
2181 GracePeriod: gracePeriod,
2182 MinReclaim: &evictionapi.ThresholdValue{
2183 Quantity: quantityMustParse("3Gi"),
2184 },
2185 },
2186 expectedContainerFsINodesHard: evictionapi.Threshold{
2187 Signal: evictionapi.SignalContainerFsInodesFree,
2188 Operator: evictionapi.OpLessThan,
2189 Value: evictionapi.ThresholdValue{
2190 Quantity: quantityMustParse("300Mi"),
2191 },
2192 MinReclaim: &evictionapi.ThresholdValue{
2193 Quantity: quantityMustParse("2Gi"),
2194 },
2195 },
2196 expectedContainerFsINodesSoft: evictionapi.Threshold{
2197 Signal: evictionapi.SignalContainerFsInodesFree,
2198 Operator: evictionapi.OpLessThan,
2199 Value: evictionapi.ThresholdValue{
2200 Quantity: quantityMustParse("150Mi"),
2201 },
2202 MinReclaim: &evictionapi.ThresholdValue{
2203 Quantity: quantityMustParse("2Gi"),
2204 },
2205 GracePeriod: gracePeriod,
2206 },
2207 thresholdList: []evictionapi.Threshold{
2208 {
2209 Signal: evictionapi.SignalNodeFsAvailable,
2210 Operator: evictionapi.OpLessThan,
2211 Value: evictionapi.ThresholdValue{
2212 Quantity: quantityMustParse("100Mi"),
2213 },
2214 MinReclaim: &evictionapi.ThresholdValue{
2215 Quantity: quantityMustParse("1Gi"),
2216 },
2217 },
2218 {
2219 Signal: evictionapi.SignalNodeFsAvailable,
2220 Operator: evictionapi.OpLessThan,
2221 Value: evictionapi.ThresholdValue{
2222 Quantity: quantityMustParse("200Mi"),
2223 },
2224 GracePeriod: gracePeriod,
2225 MinReclaim: &evictionapi.ThresholdValue{
2226 Quantity: quantityMustParse("1Gi"),
2227 },
2228 },
2229 {
2230 Signal: evictionapi.SignalNodeFsInodesFree,
2231 Operator: evictionapi.OpLessThan,
2232 Value: evictionapi.ThresholdValue{
2233 Quantity: quantityMustParse("200Mi"),
2234 },
2235 MinReclaim: &evictionapi.ThresholdValue{
2236 Quantity: quantityMustParse("2Gi"),
2237 },
2238 GracePeriod: gracePeriod,
2239 },
2240 {
2241 Signal: evictionapi.SignalNodeFsInodesFree,
2242 Operator: evictionapi.OpLessThan,
2243 Value: evictionapi.ThresholdValue{
2244 Quantity: quantityMustParse("150Mi"),
2245 },
2246 MinReclaim: &evictionapi.ThresholdValue{
2247 Quantity: quantityMustParse("1.5Gi"),
2248 },
2249 },
2250 {
2251 Signal: evictionapi.SignalImageFsAvailable,
2252 Operator: evictionapi.OpLessThan,
2253 Value: evictionapi.ThresholdValue{
2254 Quantity: quantityMustParse("150Mi"),
2255 },
2256 MinReclaim: &evictionapi.ThresholdValue{
2257 Quantity: quantityMustParse("1.5Gi"),
2258 },
2259 },
2260 {
2261 Signal: evictionapi.SignalImageFsAvailable,
2262 Operator: evictionapi.OpLessThan,
2263 Value: evictionapi.ThresholdValue{
2264 Quantity: quantityMustParse("300Mi"),
2265 },
2266 GracePeriod: gracePeriod,
2267 MinReclaim: &evictionapi.ThresholdValue{
2268 Quantity: quantityMustParse("3Gi"),
2269 },
2270 },
2271 {
2272 Signal: evictionapi.SignalImageFsInodesFree,
2273 Operator: evictionapi.OpLessThan,
2274 Value: evictionapi.ThresholdValue{
2275 Quantity: quantityMustParse("150Mi"),
2276 },
2277 MinReclaim: &evictionapi.ThresholdValue{
2278 Quantity: quantityMustParse("2Gi"),
2279 },
2280 GracePeriod: gracePeriod,
2281 },
2282 {
2283 Signal: evictionapi.SignalImageFsInodesFree,
2284 Operator: evictionapi.OpLessThan,
2285 Value: evictionapi.ThresholdValue{
2286 Quantity: quantityMustParse("300Mi"),
2287 },
2288 MinReclaim: &evictionapi.ThresholdValue{
2289 Quantity: quantityMustParse("2Gi"),
2290 },
2291 },
2292 },
2293 },
2294 {
2295 description: "container and image are separate",
2296 imageFs: true,
2297 containerFs: true,
2298 expectedContainerFsHard: evictionapi.Threshold{
2299 Signal: evictionapi.SignalContainerFsAvailable,
2300 Operator: evictionapi.OpLessThan,
2301 Value: evictionapi.ThresholdValue{
2302 Quantity: quantityMustParse("100Mi"),
2303 },
2304 MinReclaim: &evictionapi.ThresholdValue{
2305 Quantity: quantityMustParse("1Gi"),
2306 },
2307 },
2308 expectedContainerFsSoft: evictionapi.Threshold{
2309 Signal: evictionapi.SignalContainerFsAvailable,
2310 Operator: evictionapi.OpLessThan,
2311 Value: evictionapi.ThresholdValue{
2312 Quantity: quantityMustParse("200Mi"),
2313 },
2314 GracePeriod: gracePeriod,
2315 MinReclaim: &evictionapi.ThresholdValue{
2316 Quantity: quantityMustParse("1Gi"),
2317 },
2318 },
2319 expectedContainerFsINodesHard: evictionapi.Threshold{
2320 Signal: evictionapi.SignalContainerFsInodesFree,
2321 Operator: evictionapi.OpLessThan,
2322 Value: evictionapi.ThresholdValue{
2323 Quantity: quantityMustParse("100Mi"),
2324 },
2325 MinReclaim: &evictionapi.ThresholdValue{
2326 Quantity: quantityMustParse("1Gi"),
2327 },
2328 },
2329 expectedContainerFsINodesSoft: evictionapi.Threshold{
2330 Signal: evictionapi.SignalContainerFsInodesFree,
2331 Operator: evictionapi.OpLessThan,
2332 Value: evictionapi.ThresholdValue{
2333 Quantity: quantityMustParse("200Mi"),
2334 },
2335 MinReclaim: &evictionapi.ThresholdValue{
2336 Quantity: quantityMustParse("2Gi"),
2337 },
2338 GracePeriod: gracePeriod,
2339 },
2340 thresholdList: []evictionapi.Threshold{
2341 {
2342 Signal: evictionapi.SignalNodeFsAvailable,
2343 Operator: evictionapi.OpLessThan,
2344 Value: evictionapi.ThresholdValue{
2345 Quantity: quantityMustParse("100Mi"),
2346 },
2347 MinReclaim: &evictionapi.ThresholdValue{
2348 Quantity: quantityMustParse("1Gi"),
2349 },
2350 },
2351 {
2352 Signal: evictionapi.SignalNodeFsAvailable,
2353 Operator: evictionapi.OpLessThan,
2354 Value: evictionapi.ThresholdValue{
2355 Quantity: quantityMustParse("200Mi"),
2356 },
2357 GracePeriod: gracePeriod,
2358 MinReclaim: &evictionapi.ThresholdValue{
2359 Quantity: quantityMustParse("1Gi"),
2360 },
2361 },
2362 {
2363 Signal: evictionapi.SignalNodeFsInodesFree,
2364 Operator: evictionapi.OpLessThan,
2365 Value: evictionapi.ThresholdValue{
2366 Quantity: quantityMustParse("200Mi"),
2367 },
2368 MinReclaim: &evictionapi.ThresholdValue{
2369 Quantity: quantityMustParse("2Gi"),
2370 },
2371 GracePeriod: gracePeriod,
2372 },
2373 {
2374 Signal: evictionapi.SignalNodeFsInodesFree,
2375 Operator: evictionapi.OpLessThan,
2376 Value: evictionapi.ThresholdValue{
2377 Quantity: quantityMustParse("100Mi"),
2378 },
2379 MinReclaim: &evictionapi.ThresholdValue{
2380 Quantity: quantityMustParse("1Gi"),
2381 },
2382 },
2383 {
2384 Signal: evictionapi.SignalImageFsAvailable,
2385 Operator: evictionapi.OpLessThan,
2386 Value: evictionapi.ThresholdValue{
2387 Quantity: quantityMustParse("150Mi"),
2388 },
2389 MinReclaim: &evictionapi.ThresholdValue{
2390 Quantity: quantityMustParse("1.5Gi"),
2391 },
2392 },
2393 {
2394 Signal: evictionapi.SignalImageFsAvailable,
2395 Operator: evictionapi.OpLessThan,
2396 Value: evictionapi.ThresholdValue{
2397 Quantity: quantityMustParse("300Mi"),
2398 },
2399 GracePeriod: gracePeriod,
2400 MinReclaim: &evictionapi.ThresholdValue{
2401 Quantity: quantityMustParse("3Gi"),
2402 },
2403 },
2404 {
2405 Signal: evictionapi.SignalImageFsInodesFree,
2406 Operator: evictionapi.OpLessThan,
2407 Value: evictionapi.ThresholdValue{
2408 Quantity: quantityMustParse("150Mi"),
2409 },
2410 MinReclaim: &evictionapi.ThresholdValue{
2411 Quantity: quantityMustParse("2Gi"),
2412 },
2413 GracePeriod: gracePeriod,
2414 },
2415 {
2416 Signal: evictionapi.SignalImageFsInodesFree,
2417 Operator: evictionapi.OpLessThan,
2418 Value: evictionapi.ThresholdValue{
2419 Quantity: quantityMustParse("300Mi"),
2420 },
2421 MinReclaim: &evictionapi.ThresholdValue{
2422 Quantity: quantityMustParse("2Gi"),
2423 },
2424 },
2425 },
2426 },
2427 {
2428 description: "single filesystem; existing containerfsstats",
2429 imageFs: false,
2430 containerFs: false,
2431 expectErr: true,
2432 expectedContainerFsHard: evictionapi.Threshold{
2433 Signal: evictionapi.SignalContainerFsAvailable,
2434 Operator: evictionapi.OpLessThan,
2435 Value: evictionapi.ThresholdValue{
2436 Quantity: quantityMustParse("100Mi"),
2437 },
2438 MinReclaim: &evictionapi.ThresholdValue{
2439 Quantity: quantityMustParse("1Gi"),
2440 },
2441 },
2442 expectedContainerFsSoft: evictionapi.Threshold{
2443 Signal: evictionapi.SignalContainerFsAvailable,
2444 Operator: evictionapi.OpLessThan,
2445 Value: evictionapi.ThresholdValue{
2446 Quantity: quantityMustParse("200Mi"),
2447 },
2448 GracePeriod: gracePeriod,
2449 MinReclaim: &evictionapi.ThresholdValue{
2450 Quantity: quantityMustParse("1Gi"),
2451 },
2452 },
2453 expectedContainerFsINodesHard: evictionapi.Threshold{
2454 Signal: evictionapi.SignalContainerFsInodesFree,
2455 Operator: evictionapi.OpLessThan,
2456 Value: evictionapi.ThresholdValue{
2457 Quantity: quantityMustParse("100Mi"),
2458 },
2459 MinReclaim: &evictionapi.ThresholdValue{
2460 Quantity: quantityMustParse("1Gi"),
2461 },
2462 },
2463 expectedContainerFsINodesSoft: evictionapi.Threshold{
2464 Signal: evictionapi.SignalContainerFsInodesFree,
2465 Operator: evictionapi.OpLessThan,
2466 Value: evictionapi.ThresholdValue{
2467 Quantity: quantityMustParse("200Mi"),
2468 },
2469 MinReclaim: &evictionapi.ThresholdValue{
2470 Quantity: quantityMustParse("2Gi"),
2471 },
2472 GracePeriod: gracePeriod,
2473 },
2474
2475 thresholdList: []evictionapi.Threshold{
2476 {
2477 Signal: evictionapi.SignalNodeFsAvailable,
2478 Operator: evictionapi.OpLessThan,
2479 Value: evictionapi.ThresholdValue{
2480 Quantity: quantityMustParse("100Mi"),
2481 },
2482 MinReclaim: &evictionapi.ThresholdValue{
2483 Quantity: quantityMustParse("1Gi"),
2484 },
2485 },
2486 {
2487 Signal: evictionapi.SignalNodeFsAvailable,
2488 Operator: evictionapi.OpLessThan,
2489 Value: evictionapi.ThresholdValue{
2490 Quantity: quantityMustParse("200Mi"),
2491 },
2492 GracePeriod: gracePeriod,
2493 MinReclaim: &evictionapi.ThresholdValue{
2494 Quantity: quantityMustParse("1Gi"),
2495 },
2496 },
2497 {
2498 Signal: evictionapi.SignalNodeFsInodesFree,
2499 Operator: evictionapi.OpLessThan,
2500 Value: evictionapi.ThresholdValue{
2501 Quantity: quantityMustParse("200Mi"),
2502 },
2503 MinReclaim: &evictionapi.ThresholdValue{
2504 Quantity: quantityMustParse("2Gi"),
2505 },
2506 GracePeriod: gracePeriod,
2507 },
2508 {
2509 Signal: evictionapi.SignalNodeFsInodesFree,
2510 Operator: evictionapi.OpLessThan,
2511 Value: evictionapi.ThresholdValue{
2512 Quantity: quantityMustParse("100Mi"),
2513 },
2514 MinReclaim: &evictionapi.ThresholdValue{
2515 Quantity: quantityMustParse("1Gi"),
2516 },
2517 },
2518 {
2519 Signal: evictionapi.SignalImageFsAvailable,
2520 Operator: evictionapi.OpLessThan,
2521 Value: evictionapi.ThresholdValue{
2522 Quantity: quantityMustParse("100Mi"),
2523 },
2524 MinReclaim: &evictionapi.ThresholdValue{
2525 Quantity: quantityMustParse("1Gi"),
2526 },
2527 },
2528
2529 {
2530 Signal: evictionapi.SignalImageFsAvailable,
2531 Operator: evictionapi.OpLessThan,
2532 Value: evictionapi.ThresholdValue{
2533 Quantity: quantityMustParse("300Mi"),
2534 },
2535 GracePeriod: gracePeriod,
2536 MinReclaim: &evictionapi.ThresholdValue{
2537 Quantity: quantityMustParse("2Gi"),
2538 },
2539 },
2540 {
2541 Signal: evictionapi.SignalImageFsInodesFree,
2542 Operator: evictionapi.OpLessThan,
2543 Value: evictionapi.ThresholdValue{
2544 Quantity: quantityMustParse("150Mi"),
2545 },
2546 MinReclaim: &evictionapi.ThresholdValue{
2547 Quantity: quantityMustParse("2Gi"),
2548 },
2549 GracePeriod: gracePeriod,
2550 },
2551 {
2552 Signal: evictionapi.SignalImageFsInodesFree,
2553 Operator: evictionapi.OpLessThan,
2554 Value: evictionapi.ThresholdValue{
2555 Quantity: quantityMustParse("150Mi"),
2556 },
2557 MinReclaim: &evictionapi.ThresholdValue{
2558 Quantity: quantityMustParse("2Gi"),
2559 },
2560 },
2561 {
2562 Signal: evictionapi.SignalContainerFsAvailable,
2563 Operator: evictionapi.OpLessThan,
2564 Value: evictionapi.ThresholdValue{
2565 Quantity: quantityMustParse("500Mi"),
2566 },
2567 MinReclaim: &evictionapi.ThresholdValue{
2568 Quantity: quantityMustParse("5Gi"),
2569 },
2570 },
2571
2572 {
2573 Signal: evictionapi.SignalContainerFsAvailable,
2574 Operator: evictionapi.OpLessThan,
2575 Value: evictionapi.ThresholdValue{
2576 Quantity: quantityMustParse("500Mi"),
2577 },
2578 GracePeriod: gracePeriod,
2579 MinReclaim: &evictionapi.ThresholdValue{
2580 Quantity: quantityMustParse("5Gi"),
2581 },
2582 },
2583 {
2584 Signal: evictionapi.SignalContainerFsInodesFree,
2585 Operator: evictionapi.OpLessThan,
2586 Value: evictionapi.ThresholdValue{
2587 Quantity: quantityMustParse("500Mi"),
2588 },
2589 MinReclaim: &evictionapi.ThresholdValue{
2590 Quantity: quantityMustParse("5Gi"),
2591 },
2592 GracePeriod: gracePeriod,
2593 },
2594 {
2595 Signal: evictionapi.SignalContainerFsInodesFree,
2596 Operator: evictionapi.OpLessThan,
2597 Value: evictionapi.ThresholdValue{
2598 Quantity: quantityMustParse("500Mi"),
2599 },
2600 MinReclaim: &evictionapi.ThresholdValue{
2601 Quantity: quantityMustParse("5Gi"),
2602 },
2603 },
2604 },
2605 },
2606 {
2607 description: "image filesystem; expect error",
2608 imageFs: true,
2609 containerFs: false,
2610 expectErr: true,
2611 expectedContainerFsHard: evictionapi.Threshold{
2612 Signal: evictionapi.SignalContainerFsAvailable,
2613 Operator: evictionapi.OpLessThan,
2614 Value: evictionapi.ThresholdValue{
2615 Quantity: quantityMustParse("150Mi"),
2616 },
2617 MinReclaim: &evictionapi.ThresholdValue{
2618 Quantity: quantityMustParse("1.5Gi"),
2619 },
2620 },
2621 expectedContainerFsSoft: evictionapi.Threshold{
2622 Signal: evictionapi.SignalContainerFsAvailable,
2623 Operator: evictionapi.OpLessThan,
2624 Value: evictionapi.ThresholdValue{
2625 Quantity: quantityMustParse("300Mi"),
2626 },
2627 GracePeriod: gracePeriod,
2628 MinReclaim: &evictionapi.ThresholdValue{
2629 Quantity: quantityMustParse("3Gi"),
2630 },
2631 },
2632 expectedContainerFsINodesHard: evictionapi.Threshold{
2633 Signal: evictionapi.SignalContainerFsInodesFree,
2634 Operator: evictionapi.OpLessThan,
2635 Value: evictionapi.ThresholdValue{
2636 Quantity: quantityMustParse("300Mi"),
2637 },
2638 MinReclaim: &evictionapi.ThresholdValue{
2639 Quantity: quantityMustParse("2Gi"),
2640 },
2641 },
2642 expectedContainerFsINodesSoft: evictionapi.Threshold{
2643 Signal: evictionapi.SignalContainerFsInodesFree,
2644 Operator: evictionapi.OpLessThan,
2645 Value: evictionapi.ThresholdValue{
2646 Quantity: quantityMustParse("150Mi"),
2647 },
2648 MinReclaim: &evictionapi.ThresholdValue{
2649 Quantity: quantityMustParse("2Gi"),
2650 },
2651 GracePeriod: gracePeriod,
2652 },
2653 thresholdList: []evictionapi.Threshold{
2654 {
2655 Signal: evictionapi.SignalNodeFsAvailable,
2656 Operator: evictionapi.OpLessThan,
2657 Value: evictionapi.ThresholdValue{
2658 Quantity: quantityMustParse("100Mi"),
2659 },
2660 MinReclaim: &evictionapi.ThresholdValue{
2661 Quantity: quantityMustParse("1Gi"),
2662 },
2663 },
2664 {
2665 Signal: evictionapi.SignalNodeFsAvailable,
2666 Operator: evictionapi.OpLessThan,
2667 Value: evictionapi.ThresholdValue{
2668 Quantity: quantityMustParse("200Mi"),
2669 },
2670 GracePeriod: gracePeriod,
2671 MinReclaim: &evictionapi.ThresholdValue{
2672 Quantity: quantityMustParse("1Gi"),
2673 },
2674 },
2675 {
2676 Signal: evictionapi.SignalNodeFsInodesFree,
2677 Operator: evictionapi.OpLessThan,
2678 Value: evictionapi.ThresholdValue{
2679 Quantity: quantityMustParse("200Mi"),
2680 },
2681 MinReclaim: &evictionapi.ThresholdValue{
2682 Quantity: quantityMustParse("2Gi"),
2683 },
2684 GracePeriod: gracePeriod,
2685 },
2686 {
2687 Signal: evictionapi.SignalNodeFsInodesFree,
2688 Operator: evictionapi.OpLessThan,
2689 Value: evictionapi.ThresholdValue{
2690 Quantity: quantityMustParse("150Mi"),
2691 },
2692 MinReclaim: &evictionapi.ThresholdValue{
2693 Quantity: quantityMustParse("1.5Gi"),
2694 },
2695 },
2696 {
2697 Signal: evictionapi.SignalImageFsAvailable,
2698 Operator: evictionapi.OpLessThan,
2699 Value: evictionapi.ThresholdValue{
2700 Quantity: quantityMustParse("150Mi"),
2701 },
2702 MinReclaim: &evictionapi.ThresholdValue{
2703 Quantity: quantityMustParse("1.5Gi"),
2704 },
2705 },
2706 {
2707 Signal: evictionapi.SignalImageFsAvailable,
2708 Operator: evictionapi.OpLessThan,
2709 Value: evictionapi.ThresholdValue{
2710 Quantity: quantityMustParse("300Mi"),
2711 },
2712 GracePeriod: gracePeriod,
2713 MinReclaim: &evictionapi.ThresholdValue{
2714 Quantity: quantityMustParse("3Gi"),
2715 },
2716 },
2717 {
2718 Signal: evictionapi.SignalImageFsInodesFree,
2719 Operator: evictionapi.OpLessThan,
2720 Value: evictionapi.ThresholdValue{
2721 Quantity: quantityMustParse("150Mi"),
2722 },
2723 MinReclaim: &evictionapi.ThresholdValue{
2724 Quantity: quantityMustParse("2Gi"),
2725 },
2726 GracePeriod: gracePeriod,
2727 },
2728 {
2729 Signal: evictionapi.SignalImageFsInodesFree,
2730 Operator: evictionapi.OpLessThan,
2731 Value: evictionapi.ThresholdValue{
2732 Quantity: quantityMustParse("300Mi"),
2733 },
2734 MinReclaim: &evictionapi.ThresholdValue{
2735 Quantity: quantityMustParse("2Gi"),
2736 },
2737 },
2738 {
2739 Signal: evictionapi.SignalContainerFsAvailable,
2740 Operator: evictionapi.OpLessThan,
2741 Value: evictionapi.ThresholdValue{
2742 Quantity: quantityMustParse("500Mi"),
2743 },
2744 MinReclaim: &evictionapi.ThresholdValue{
2745 Quantity: quantityMustParse("5Gi"),
2746 },
2747 },
2748
2749 {
2750 Signal: evictionapi.SignalContainerFsAvailable,
2751 Operator: evictionapi.OpLessThan,
2752 Value: evictionapi.ThresholdValue{
2753 Quantity: quantityMustParse("500Mi"),
2754 },
2755 GracePeriod: gracePeriod,
2756 MinReclaim: &evictionapi.ThresholdValue{
2757 Quantity: quantityMustParse("5Gi"),
2758 },
2759 },
2760 {
2761 Signal: evictionapi.SignalContainerFsInodesFree,
2762 Operator: evictionapi.OpLessThan,
2763 Value: evictionapi.ThresholdValue{
2764 Quantity: quantityMustParse("500Mi"),
2765 },
2766 MinReclaim: &evictionapi.ThresholdValue{
2767 Quantity: quantityMustParse("5Gi"),
2768 },
2769 GracePeriod: gracePeriod,
2770 },
2771 {
2772 Signal: evictionapi.SignalContainerFsInodesFree,
2773 Operator: evictionapi.OpLessThan,
2774 Value: evictionapi.ThresholdValue{
2775 Quantity: quantityMustParse("500Mi"),
2776 },
2777 MinReclaim: &evictionapi.ThresholdValue{
2778 Quantity: quantityMustParse("5Gi"),
2779 },
2780 },
2781 },
2782 },
2783 {
2784 description: "container and image are separate; expect error",
2785 imageFs: true,
2786 containerFs: true,
2787 expectErr: true,
2788 expectedContainerFsHard: evictionapi.Threshold{
2789 Signal: evictionapi.SignalContainerFsAvailable,
2790 Operator: evictionapi.OpLessThan,
2791 Value: evictionapi.ThresholdValue{
2792 Quantity: quantityMustParse("100Mi"),
2793 },
2794 MinReclaim: &evictionapi.ThresholdValue{
2795 Quantity: quantityMustParse("1Gi"),
2796 },
2797 },
2798 expectedContainerFsSoft: evictionapi.Threshold{
2799 Signal: evictionapi.SignalContainerFsAvailable,
2800 Operator: evictionapi.OpLessThan,
2801 Value: evictionapi.ThresholdValue{
2802 Quantity: quantityMustParse("200Mi"),
2803 },
2804 GracePeriod: gracePeriod,
2805 MinReclaim: &evictionapi.ThresholdValue{
2806 Quantity: quantityMustParse("1Gi"),
2807 },
2808 },
2809 expectedContainerFsINodesHard: evictionapi.Threshold{
2810 Signal: evictionapi.SignalContainerFsInodesFree,
2811 Operator: evictionapi.OpLessThan,
2812 Value: evictionapi.ThresholdValue{
2813 Quantity: quantityMustParse("100Mi"),
2814 },
2815 MinReclaim: &evictionapi.ThresholdValue{
2816 Quantity: quantityMustParse("1Gi"),
2817 },
2818 },
2819 expectedContainerFsINodesSoft: evictionapi.Threshold{
2820 Signal: evictionapi.SignalContainerFsInodesFree,
2821 Operator: evictionapi.OpLessThan,
2822 Value: evictionapi.ThresholdValue{
2823 Quantity: quantityMustParse("200Mi"),
2824 },
2825 MinReclaim: &evictionapi.ThresholdValue{
2826 Quantity: quantityMustParse("2Gi"),
2827 },
2828 GracePeriod: gracePeriod,
2829 },
2830 thresholdList: []evictionapi.Threshold{
2831 {
2832 Signal: evictionapi.SignalNodeFsAvailable,
2833 Operator: evictionapi.OpLessThan,
2834 Value: evictionapi.ThresholdValue{
2835 Quantity: quantityMustParse("100Mi"),
2836 },
2837 MinReclaim: &evictionapi.ThresholdValue{
2838 Quantity: quantityMustParse("1Gi"),
2839 },
2840 },
2841 {
2842 Signal: evictionapi.SignalNodeFsAvailable,
2843 Operator: evictionapi.OpLessThan,
2844 Value: evictionapi.ThresholdValue{
2845 Quantity: quantityMustParse("200Mi"),
2846 },
2847 GracePeriod: gracePeriod,
2848 MinReclaim: &evictionapi.ThresholdValue{
2849 Quantity: quantityMustParse("1Gi"),
2850 },
2851 },
2852 {
2853 Signal: evictionapi.SignalNodeFsInodesFree,
2854 Operator: evictionapi.OpLessThan,
2855 Value: evictionapi.ThresholdValue{
2856 Quantity: quantityMustParse("200Mi"),
2857 },
2858 MinReclaim: &evictionapi.ThresholdValue{
2859 Quantity: quantityMustParse("2Gi"),
2860 },
2861 GracePeriod: gracePeriod,
2862 },
2863 {
2864 Signal: evictionapi.SignalNodeFsInodesFree,
2865 Operator: evictionapi.OpLessThan,
2866 Value: evictionapi.ThresholdValue{
2867 Quantity: quantityMustParse("100Mi"),
2868 },
2869 MinReclaim: &evictionapi.ThresholdValue{
2870 Quantity: quantityMustParse("1Gi"),
2871 },
2872 },
2873 {
2874 Signal: evictionapi.SignalImageFsAvailable,
2875 Operator: evictionapi.OpLessThan,
2876 Value: evictionapi.ThresholdValue{
2877 Quantity: quantityMustParse("150Mi"),
2878 },
2879 MinReclaim: &evictionapi.ThresholdValue{
2880 Quantity: quantityMustParse("1.5Gi"),
2881 },
2882 },
2883 {
2884 Signal: evictionapi.SignalImageFsAvailable,
2885 Operator: evictionapi.OpLessThan,
2886 Value: evictionapi.ThresholdValue{
2887 Quantity: quantityMustParse("300Mi"),
2888 },
2889 GracePeriod: gracePeriod,
2890 MinReclaim: &evictionapi.ThresholdValue{
2891 Quantity: quantityMustParse("3Gi"),
2892 },
2893 },
2894 {
2895 Signal: evictionapi.SignalImageFsInodesFree,
2896 Operator: evictionapi.OpLessThan,
2897 Value: evictionapi.ThresholdValue{
2898 Quantity: quantityMustParse("150Mi"),
2899 },
2900 MinReclaim: &evictionapi.ThresholdValue{
2901 Quantity: quantityMustParse("2Gi"),
2902 },
2903 GracePeriod: gracePeriod,
2904 },
2905 {
2906 Signal: evictionapi.SignalImageFsInodesFree,
2907 Operator: evictionapi.OpLessThan,
2908 Value: evictionapi.ThresholdValue{
2909 Quantity: quantityMustParse("300Mi"),
2910 },
2911 MinReclaim: &evictionapi.ThresholdValue{
2912 Quantity: quantityMustParse("2Gi"),
2913 },
2914 },
2915 {
2916 Signal: evictionapi.SignalContainerFsAvailable,
2917 Operator: evictionapi.OpLessThan,
2918 Value: evictionapi.ThresholdValue{
2919 Quantity: quantityMustParse("500Mi"),
2920 },
2921 MinReclaim: &evictionapi.ThresholdValue{
2922 Quantity: quantityMustParse("5Gi"),
2923 },
2924 },
2925
2926 {
2927 Signal: evictionapi.SignalContainerFsAvailable,
2928 Operator: evictionapi.OpLessThan,
2929 Value: evictionapi.ThresholdValue{
2930 Quantity: quantityMustParse("500Mi"),
2931 },
2932 GracePeriod: gracePeriod,
2933 MinReclaim: &evictionapi.ThresholdValue{
2934 Quantity: quantityMustParse("5Gi"),
2935 },
2936 },
2937 {
2938 Signal: evictionapi.SignalContainerFsInodesFree,
2939 Operator: evictionapi.OpLessThan,
2940 Value: evictionapi.ThresholdValue{
2941 Quantity: quantityMustParse("500Mi"),
2942 },
2943 MinReclaim: &evictionapi.ThresholdValue{
2944 Quantity: quantityMustParse("5Gi"),
2945 },
2946 GracePeriod: gracePeriod,
2947 },
2948 {
2949 Signal: evictionapi.SignalContainerFsInodesFree,
2950 Operator: evictionapi.OpLessThan,
2951 Value: evictionapi.ThresholdValue{
2952 Quantity: quantityMustParse("500Mi"),
2953 },
2954 MinReclaim: &evictionapi.ThresholdValue{
2955 Quantity: quantityMustParse("5Gi"),
2956 },
2957 },
2958 },
2959 },
2960 }
2961
2962 for _, testCase := range testCases {
2963 t.Run(testCase.description, func(t *testing.T) {
2964 expected, err := UpdateContainerFsThresholds(testCase.thresholdList, testCase.imageFs, testCase.containerFs)
2965 if err != nil && !testCase.expectErr {
2966 t.Fatalf("got error but did not expect any")
2967 }
2968 hardContainerFsMatch := -1
2969 softContainerFsMatch := -1
2970 hardContainerFsINodesMatch := -1
2971 softContainerFsINodesMatch := -1
2972 for idx, val := range expected {
2973 if val.Signal == evictionapi.SignalContainerFsAvailable && isHardEvictionThreshold(val) {
2974 if !reflect.DeepEqual(val, testCase.expectedContainerFsHard) {
2975 t.Fatalf("want %v got %v", testCase.expectedContainerFsHard, val)
2976 }
2977 hardContainerFsMatch = idx
2978 }
2979 if val.Signal == evictionapi.SignalContainerFsAvailable && !isHardEvictionThreshold(val) {
2980 if !reflect.DeepEqual(val, testCase.expectedContainerFsSoft) {
2981 t.Fatalf("want %v got %v", testCase.expectedContainerFsSoft, val)
2982 }
2983 softContainerFsMatch = idx
2984 }
2985 if val.Signal == evictionapi.SignalContainerFsInodesFree && isHardEvictionThreshold(val) {
2986 if !reflect.DeepEqual(val, testCase.expectedContainerFsINodesHard) {
2987 t.Fatalf("want %v got %v", testCase.expectedContainerFsINodesHard, val)
2988 }
2989 hardContainerFsINodesMatch = idx
2990 }
2991 if val.Signal == evictionapi.SignalContainerFsInodesFree && !isHardEvictionThreshold(val) {
2992 if !reflect.DeepEqual(val, testCase.expectedContainerFsINodesSoft) {
2993 t.Fatalf("want %v got %v", testCase.expectedContainerFsINodesSoft, val)
2994 }
2995 softContainerFsINodesMatch = idx
2996 }
2997 }
2998 if hardContainerFsMatch == -1 {
2999 t.Fatalf("did not find hard containerfs.available")
3000 }
3001 if softContainerFsMatch == -1 {
3002 t.Fatalf("did not find soft containerfs.available")
3003 }
3004 if hardContainerFsINodesMatch == -1 {
3005 t.Fatalf("did not find hard containerfs.inodesfree")
3006 }
3007 if softContainerFsINodesMatch == -1 {
3008 t.Fatalf("did not find soft containerfs.inodesfree")
3009 }
3010 })
3011 }
3012 }
3013
3014
3015 func newPodInodeStats(pod *v1.Pod, rootFsInodesUsed, logsInodesUsed, perLocalVolumeInodesUsed resource.Quantity) statsapi.PodStats {
3016 result := statsapi.PodStats{
3017 PodRef: statsapi.PodReference{
3018 Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
3019 },
3020 }
3021 rootFsUsed := uint64(rootFsInodesUsed.Value())
3022 logsUsed := uint64(logsInodesUsed.Value())
3023 for range pod.Spec.Containers {
3024 result.Containers = append(result.Containers, statsapi.ContainerStats{
3025 Rootfs: &statsapi.FsStats{
3026 InodesUsed: &rootFsUsed,
3027 },
3028 Logs: &statsapi.FsStats{
3029 InodesUsed: &logsUsed,
3030 },
3031 })
3032 }
3033
3034 perLocalVolumeUsed := uint64(perLocalVolumeInodesUsed.Value())
3035 for _, volumeName := range localVolumeNames(pod) {
3036 result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
3037 Name: volumeName,
3038 FsStats: statsapi.FsStats{
3039 InodesUsed: &perLocalVolumeUsed,
3040 },
3041 })
3042 }
3043 return result
3044 }
3045
3046
3047 func newPodDiskStats(pod *v1.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats {
3048 result := statsapi.PodStats{
3049 PodRef: statsapi.PodReference{
3050 Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
3051 },
3052 }
3053
3054 rootFsUsedBytes := uint64(rootFsUsed.Value())
3055 logsUsedBytes := uint64(logsUsed.Value())
3056 for range pod.Spec.Containers {
3057 result.Containers = append(result.Containers, statsapi.ContainerStats{
3058 Rootfs: &statsapi.FsStats{
3059 UsedBytes: &rootFsUsedBytes,
3060 },
3061 Logs: &statsapi.FsStats{
3062 UsedBytes: &logsUsedBytes,
3063 },
3064 })
3065 }
3066
3067 perLocalVolumeUsedBytes := uint64(perLocalVolumeUsed.Value())
3068 for _, volumeName := range localVolumeNames(pod) {
3069 result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
3070 Name: volumeName,
3071 FsStats: statsapi.FsStats{
3072 UsedBytes: &perLocalVolumeUsedBytes,
3073 },
3074 })
3075 }
3076
3077 return result
3078 }
3079
3080 func newPodMemoryStats(pod *v1.Pod, workingSet resource.Quantity) statsapi.PodStats {
3081 workingSetBytes := uint64(workingSet.Value())
3082 return statsapi.PodStats{
3083 PodRef: statsapi.PodReference{
3084 Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
3085 },
3086 Memory: &statsapi.MemoryStats{
3087 WorkingSetBytes: &workingSetBytes,
3088 },
3089 VolumeStats: []statsapi.VolumeStats{
3090 {
3091 FsStats: statsapi.FsStats{
3092 UsedBytes: &workingSetBytes,
3093 },
3094 Name: "local-volume",
3095 },
3096 },
3097 Containers: []statsapi.ContainerStats{
3098 {
3099 Name: pod.Name,
3100 Logs: &statsapi.FsStats{
3101 UsedBytes: &workingSetBytes,
3102 },
3103 Rootfs: &statsapi.FsStats{UsedBytes: &workingSetBytes},
3104 },
3105 },
3106 EphemeralStorage: &statsapi.FsStats{UsedBytes: &workingSetBytes},
3107 }
3108 }
3109
3110 func newPodProcessStats(pod *v1.Pod, num uint64) statsapi.PodStats {
3111 return statsapi.PodStats{
3112 PodRef: statsapi.PodReference{
3113 Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
3114 },
3115 ProcessStats: &statsapi.ProcessStats{
3116 ProcessCount: &num,
3117 },
3118 }
3119 }
3120
3121 func newResourceList(cpu, memory, disk string) v1.ResourceList {
3122 res := v1.ResourceList{}
3123 if cpu != "" {
3124 res[v1.ResourceCPU] = resource.MustParse(cpu)
3125 }
3126 if memory != "" {
3127 res[v1.ResourceMemory] = resource.MustParse(memory)
3128 }
3129 if disk != "" {
3130 res[v1.ResourceEphemeralStorage] = resource.MustParse(disk)
3131 }
3132 return res
3133 }
3134
3135 func newResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequirements {
3136 res := v1.ResourceRequirements{}
3137 res.Requests = requests
3138 res.Limits = limits
3139 return res
3140 }
3141
3142 func newContainer(name string, requests v1.ResourceList, limits v1.ResourceList) v1.Container {
3143 return v1.Container{
3144 Name: name,
3145 Resources: newResourceRequirements(requests, limits),
3146 }
3147 }
3148
3149 func newVolume(name string, volumeSource v1.VolumeSource) v1.Volume {
3150 return v1.Volume{
3151 Name: name,
3152 VolumeSource: volumeSource,
3153 }
3154 }
3155
3156
3157 func newPod(name string, priority int32, containers []v1.Container, volumes []v1.Volume) *v1.Pod {
3158 return &v1.Pod{
3159 ObjectMeta: metav1.ObjectMeta{
3160 Name: name,
3161 UID: types.UID(name),
3162 },
3163 Spec: v1.PodSpec{
3164 Containers: containers,
3165 Volumes: volumes,
3166 Priority: &priority,
3167 },
3168 }
3169 }
3170
3171
3172 type nodeConditionList []v1.NodeConditionType
3173
3174
3175 func (s1 nodeConditionList) Equal(s2 nodeConditionList) bool {
3176 if len(s1) != len(s2) {
3177 return false
3178 }
3179 for _, item := range s1 {
3180 if !hasNodeCondition(s2, item) {
3181 return false
3182 }
3183 }
3184 return true
3185 }
3186
3187
3188 type thresholdList []evictionapi.Threshold
3189
3190
3191 func (s1 thresholdList) Equal(s2 thresholdList) bool {
3192 if len(s1) != len(s2) {
3193 return false
3194 }
3195 for _, item := range s1 {
3196 if !hasThreshold(s2, item) {
3197 return false
3198 }
3199 }
3200 return true
3201 }
3202
3203 func TestEvictonMessageWithResourceResize(t *testing.T) {
3204 testpod := newPod("testpod", 1, []v1.Container{
3205 newContainer("testcontainer", newResourceList("", "200Mi", ""), newResourceList("", "", "")),
3206 }, nil)
3207 testpod.Status = v1.PodStatus{
3208 ContainerStatuses: []v1.ContainerStatus{
3209 {
3210 Name: "testcontainer",
3211 AllocatedResources: newResourceList("", "100Mi", ""),
3212 },
3213 },
3214 }
3215 testpodMemory := resource.MustParse("150Mi")
3216 testpodStats := newPodMemoryStats(testpod, testpodMemory)
3217 testpodMemoryBytes := uint64(testpodMemory.Value())
3218 testpodStats.Containers = []statsapi.ContainerStats{
3219 {
3220 Name: "testcontainer",
3221 Memory: &statsapi.MemoryStats{
3222 WorkingSetBytes: &testpodMemoryBytes,
3223 },
3224 },
3225 }
3226 stats := map[*v1.Pod]statsapi.PodStats{
3227 testpod: testpodStats,
3228 }
3229 statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
3230 result, found := stats[pod]
3231 return result, found
3232 }
3233 threshold := []evictionapi.Threshold{}
3234 observations := signalObservations{}
3235
3236 for _, enabled := range []bool{true, false} {
3237 t.Run(fmt.Sprintf("InPlacePodVerticalScaling enabled=%v", enabled), func(t *testing.T) {
3238 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, enabled)()
3239 msg, _ := evictionMessage(v1.ResourceMemory, testpod, statsFn, threshold, observations)
3240 if enabled {
3241 if !strings.Contains(msg, "testcontainer was using 150Mi, request is 100Mi") {
3242 t.Errorf("Expected 'exceeds memory' eviction message was not found.")
3243 }
3244 } else {
3245 if strings.Contains(msg, "which exceeds its request") {
3246 t.Errorf("Found 'exceeds memory' eviction message which was not expected.")
3247 }
3248 }
3249 })
3250 }
3251 }
3252
View as plain text