k8s.io/kubernetes@v1.29.3/pkg/kubelet/eviction/helpers_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // About func addAllocatableThresholds, only someone threshold that "Signal" is "memory.available" and "GracePeriod" is 0, 472 // append this threshold(changed "Signal" to "allocatableMemory.available") to thresholds 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")), // -1 relative to request 697 exceeds: newPodMemoryStats(exceeds, resource.MustParse("101Mi")), // 1 relative to request 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")), // -1 relative to request 723 exceeds: newPodDiskStats(exceeds, resource.MustParse("90Mi"), resource.MustParse("11Mi"), resource.MustParse("0Mi")), // 1 relative to request 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")), // 300Mi - 0 = 300Mi 807 pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")), // 100Mi - 0 = 100Mi 808 pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi - 100Mi = 250Mi 809 pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")), // 110Mi - 100Mi = 10Mi 810 pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi - 400Mi = -25Mi 811 pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")), // 120Mi - 400Mi = -280Mi 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")), // 200000 851 medium: newPodInodeStats(medium, resource.MustParse("100000"), resource.MustParse("150000"), resource.MustParse("50000")), // 300000 852 high: newPodInodeStats(high, resource.MustParse("200000"), resource.MustParse("150000"), resource.MustParse("50000")), // 400000 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 // TestOrderedByPriorityDisk ensures we order pods by priority and then greediest resource consumer 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")), // 500 relative to request 928 pod2: newPodDiskStats(pod2, resource.MustParse("10Mi"), resource.MustParse("10Mi"), resource.MustParse("30Mi")), // 50 relative to request 929 pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("250Mi")), // 500 relative to request 930 pod4: newPodDiskStats(pod4, resource.MustParse("90Mi"), resource.MustParse("50Mi"), resource.MustParse("10Mi")), // 50 relative to request 931 pod5: newPodDiskStats(pod5, resource.MustParse("500Mi"), resource.MustParse("200Mi"), resource.MustParse("100Mi")), // -200 relative to request 932 pod6: newPodDiskStats(pod6, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // -800 relative to request 933 pod7: newPodDiskStats(pod7, resource.MustParse("250Mi"), resource.MustParse("500Mi"), resource.MustParse("50Mi")), // -200 relative to request 934 pod8: newPodDiskStats(pod8, resource.MustParse("100Mi"), resource.MustParse("60Mi"), resource.MustParse("40Mi")), // -800 relative to request 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 // TestOrderedByPriorityInodes ensures we order pods by priority and then greediest resource consumer 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")), // 400000 983 pod2: newPodInodeStats(pod2, resource.MustParse("60000"), resource.MustParse("30000"), resource.MustParse("10000")), // 100000 984 pod3: newPodInodeStats(pod3, resource.MustParse("150000"), resource.MustParse("150000"), resource.MustParse("50000")), // 350000 985 pod4: newPodInodeStats(pod4, resource.MustParse("10000"), resource.MustParse("40000"), resource.MustParse("100000")), // 150000 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 // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request. 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")), // 500 relative to request 1023 pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")), // 300 relative to request 1024 pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")), // 700 relative to request 1025 pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request 1026 pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request 1027 pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request 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 // TestOrderedByPriorityMemory ensures we order by priority and then memory consumption relative to request. 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")), // 500 relative to request 1071 pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")), // 50 relative to request 1072 pod3: newPodMemoryStats(pod3, resource.MustParse("600Mi")), // 500 relative to request 1073 pod4: newPodMemoryStats(pod4, resource.MustParse("150Mi")), // 50 relative to request 1074 pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request 1075 pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request 1076 pod7: newPodMemoryStats(pod7, resource.MustParse("800Mi")), // -200 relative to request 1077 pod8: newPodMemoryStats(pod8, resource.MustParse("200Mi")), // -800 relative to request 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 // TestOrderedByPriorityProcess ensures we order by priority and then process consumption relative to request. 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 // newPodStats returns a pod stat where each container is using the specified working set 1206 // each pod must have a Name, UID, Namespace 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 // Allocatable thresholds are always 100%. Verify that Threshold == Capacity. 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 // newPodInodeStats returns stats with specified usage amounts. 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 // newPodDiskStats returns stats with specified usage amounts. 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 // newPod uses the name as the uid. Make names unique for testing. 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 // nodeConditionList is a simple alias to support equality checking independent of order 3172 type nodeConditionList []v1.NodeConditionType 3173 3174 // Equal adds the ability to check equality between two lists of node conditions. 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 // thresholdList is a simple alias to support equality checking independent of order 3188 type thresholdList []evictionapi.Threshold 3189 3190 // Equal adds the ability to check equality between two lists of node conditions. 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 }