k8s.io/kubernetes@v1.29.3/pkg/quota/v1/evaluator/core/pods_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 core 18 19 import ( 20 "fmt" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 quota "k8s.io/apiserver/pkg/quota/v1" 31 "k8s.io/apiserver/pkg/quota/v1/generic" 32 utilfeature "k8s.io/apiserver/pkg/util/feature" 33 "k8s.io/client-go/tools/cache" 34 featuregatetesting "k8s.io/component-base/featuregate/testing" 35 api "k8s.io/kubernetes/pkg/apis/core" 36 "k8s.io/kubernetes/pkg/features" 37 "k8s.io/kubernetes/pkg/util/node" 38 "k8s.io/utils/clock" 39 testingclock "k8s.io/utils/clock/testing" 40 ) 41 42 func TestPodConstraintsFunc(t *testing.T) { 43 testCases := map[string]struct { 44 pod *api.Pod 45 required []corev1.ResourceName 46 err string 47 }{ 48 "init container resource missing": { 49 pod: &api.Pod{ 50 Spec: api.PodSpec{ 51 InitContainers: []api.Container{{ 52 Name: "dummy", 53 Resources: api.ResourceRequirements{ 54 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 55 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 56 }, 57 }}, 58 }, 59 }, 60 required: []corev1.ResourceName{corev1.ResourceMemory}, 61 err: `must specify memory for: dummy`, 62 }, 63 "multiple init container resource missing": { 64 pod: &api.Pod{ 65 Spec: api.PodSpec{ 66 InitContainers: []api.Container{{ 67 Name: "foo", 68 Resources: api.ResourceRequirements{ 69 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 70 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 71 }, 72 }, { 73 Name: "bar", 74 Resources: api.ResourceRequirements{ 75 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 76 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 77 }, 78 }}, 79 }, 80 }, 81 required: []corev1.ResourceName{corev1.ResourceMemory}, 82 err: `must specify memory for: bar,foo`, 83 }, 84 "container resource missing": { 85 pod: &api.Pod{ 86 Spec: api.PodSpec{ 87 Containers: []api.Container{{ 88 Name: "dummy", 89 Resources: api.ResourceRequirements{ 90 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 91 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 92 }, 93 }}, 94 }, 95 }, 96 required: []corev1.ResourceName{corev1.ResourceMemory}, 97 err: `must specify memory for: dummy`, 98 }, 99 "multiple container resource missing": { 100 pod: &api.Pod{ 101 Spec: api.PodSpec{ 102 Containers: []api.Container{{ 103 Name: "foo", 104 Resources: api.ResourceRequirements{ 105 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 106 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 107 }, 108 }, { 109 Name: "bar", 110 Resources: api.ResourceRequirements{ 111 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 112 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 113 }, 114 }}, 115 }, 116 }, 117 required: []corev1.ResourceName{corev1.ResourceMemory}, 118 err: `must specify memory for: bar,foo`, 119 }, 120 "container resource missing multiple": { 121 pod: &api.Pod{ 122 Spec: api.PodSpec{ 123 Containers: []api.Container{{ 124 Name: "foo", 125 Resources: api.ResourceRequirements{}, 126 }, { 127 Name: "bar", 128 Resources: api.ResourceRequirements{}, 129 }}, 130 }, 131 }, 132 required: []corev1.ResourceName{corev1.ResourceMemory, corev1.ResourceCPU}, 133 err: `must specify cpu for: bar,foo; memory for: bar,foo`, 134 }, 135 } 136 evaluator := NewPodEvaluator(nil, clock.RealClock{}) 137 for testName, test := range testCases { 138 err := evaluator.Constraints(test.required, test.pod) 139 switch { 140 case err != nil && len(test.err) == 0, 141 err == nil && len(test.err) != 0, 142 err != nil && test.err != err.Error(): 143 t.Errorf("%s want: %v,got: %v", testName, test.err, err) 144 } 145 } 146 } 147 148 func TestPodEvaluatorUsage(t *testing.T) { 149 fakeClock := testingclock.NewFakeClock(time.Now()) 150 evaluator := NewPodEvaluator(nil, fakeClock) 151 152 // fields use to simulate a pod undergoing termination 153 // note: we set the deletion time in the past 154 now := fakeClock.Now() 155 terminationGracePeriodSeconds := int64(30) 156 deletionTimestampPastGracePeriod := metav1.NewTime(now.Add(time.Duration(terminationGracePeriodSeconds) * time.Second * time.Duration(-2))) 157 deletionTimestampNotPastGracePeriod := metav1.NewTime(fakeClock.Now()) 158 159 testCases := map[string]struct { 160 pod *api.Pod 161 usage corev1.ResourceList 162 }{ 163 "init container CPU": { 164 pod: &api.Pod{ 165 Spec: api.PodSpec{ 166 InitContainers: []api.Container{{ 167 Resources: api.ResourceRequirements{ 168 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 169 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 170 }, 171 }}, 172 }, 173 }, 174 usage: corev1.ResourceList{ 175 corev1.ResourceRequestsCPU: resource.MustParse("1m"), 176 corev1.ResourceLimitsCPU: resource.MustParse("2m"), 177 corev1.ResourcePods: resource.MustParse("1"), 178 corev1.ResourceCPU: resource.MustParse("1m"), 179 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 180 }, 181 }, 182 "init container MEM": { 183 pod: &api.Pod{ 184 Spec: api.PodSpec{ 185 InitContainers: []api.Container{{ 186 Resources: api.ResourceRequirements{ 187 Requests: api.ResourceList{api.ResourceMemory: resource.MustParse("1m")}, 188 Limits: api.ResourceList{api.ResourceMemory: resource.MustParse("2m")}, 189 }, 190 }}, 191 }, 192 }, 193 usage: corev1.ResourceList{ 194 corev1.ResourceRequestsMemory: resource.MustParse("1m"), 195 corev1.ResourceLimitsMemory: resource.MustParse("2m"), 196 corev1.ResourcePods: resource.MustParse("1"), 197 corev1.ResourceMemory: resource.MustParse("1m"), 198 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 199 }, 200 }, 201 "init container local ephemeral storage": { 202 pod: &api.Pod{ 203 Spec: api.PodSpec{ 204 InitContainers: []api.Container{{ 205 Resources: api.ResourceRequirements{ 206 Requests: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("32Mi")}, 207 Limits: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("64Mi")}, 208 }, 209 }}, 210 }, 211 }, 212 usage: corev1.ResourceList{ 213 corev1.ResourceEphemeralStorage: resource.MustParse("32Mi"), 214 corev1.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"), 215 corev1.ResourceLimitsEphemeralStorage: resource.MustParse("64Mi"), 216 corev1.ResourcePods: resource.MustParse("1"), 217 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 218 }, 219 }, 220 "init container hugepages": { 221 pod: &api.Pod{ 222 Spec: api.PodSpec{ 223 InitContainers: []api.Container{{ 224 Resources: api.ResourceRequirements{ 225 Requests: api.ResourceList{api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi")}, 226 }, 227 }}, 228 }, 229 }, 230 usage: corev1.ResourceList{ 231 corev1.ResourceName(corev1.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), 232 corev1.ResourceName(corev1.ResourceRequestsHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), 233 corev1.ResourcePods: resource.MustParse("1"), 234 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 235 }, 236 }, 237 "init container extended resources": { 238 pod: &api.Pod{ 239 Spec: api.PodSpec{ 240 InitContainers: []api.Container{{ 241 Resources: api.ResourceRequirements{ 242 Requests: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 243 Limits: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 244 }, 245 }}, 246 }, 247 }, 248 usage: corev1.ResourceList{ 249 corev1.ResourceName("requests.example.com/dongle"): resource.MustParse("3"), 250 corev1.ResourcePods: resource.MustParse("1"), 251 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 252 }, 253 }, 254 "container CPU": { 255 pod: &api.Pod{ 256 Spec: api.PodSpec{ 257 Containers: []api.Container{{ 258 Resources: api.ResourceRequirements{ 259 Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("1m")}, 260 Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("2m")}, 261 }, 262 }}, 263 }, 264 }, 265 usage: corev1.ResourceList{ 266 corev1.ResourceRequestsCPU: resource.MustParse("1m"), 267 corev1.ResourceLimitsCPU: resource.MustParse("2m"), 268 corev1.ResourcePods: resource.MustParse("1"), 269 corev1.ResourceCPU: resource.MustParse("1m"), 270 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 271 }, 272 }, 273 "container MEM": { 274 pod: &api.Pod{ 275 Spec: api.PodSpec{ 276 Containers: []api.Container{{ 277 Resources: api.ResourceRequirements{ 278 Requests: api.ResourceList{api.ResourceMemory: resource.MustParse("1m")}, 279 Limits: api.ResourceList{api.ResourceMemory: resource.MustParse("2m")}, 280 }, 281 }}, 282 }, 283 }, 284 usage: corev1.ResourceList{ 285 corev1.ResourceRequestsMemory: resource.MustParse("1m"), 286 corev1.ResourceLimitsMemory: resource.MustParse("2m"), 287 corev1.ResourcePods: resource.MustParse("1"), 288 corev1.ResourceMemory: resource.MustParse("1m"), 289 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 290 }, 291 }, 292 "container local ephemeral storage": { 293 pod: &api.Pod{ 294 Spec: api.PodSpec{ 295 Containers: []api.Container{{ 296 Resources: api.ResourceRequirements{ 297 Requests: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("32Mi")}, 298 Limits: api.ResourceList{api.ResourceEphemeralStorage: resource.MustParse("64Mi")}, 299 }, 300 }}, 301 }, 302 }, 303 usage: corev1.ResourceList{ 304 corev1.ResourceEphemeralStorage: resource.MustParse("32Mi"), 305 corev1.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"), 306 corev1.ResourceLimitsEphemeralStorage: resource.MustParse("64Mi"), 307 corev1.ResourcePods: resource.MustParse("1"), 308 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 309 }, 310 }, 311 "container hugepages": { 312 pod: &api.Pod{ 313 Spec: api.PodSpec{ 314 Containers: []api.Container{{ 315 Resources: api.ResourceRequirements{ 316 Requests: api.ResourceList{api.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi")}, 317 }, 318 }}, 319 }, 320 }, 321 usage: corev1.ResourceList{ 322 corev1.ResourceName(api.ResourceHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), 323 corev1.ResourceName(api.ResourceRequestsHugePagesPrefix + "2Mi"): resource.MustParse("100Mi"), 324 corev1.ResourcePods: resource.MustParse("1"), 325 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 326 }, 327 }, 328 "container extended resources": { 329 pod: &api.Pod{ 330 Spec: api.PodSpec{ 331 Containers: []api.Container{{ 332 Resources: api.ResourceRequirements{ 333 Requests: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 334 Limits: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 335 }, 336 }}, 337 }, 338 }, 339 usage: corev1.ResourceList{ 340 corev1.ResourceName("requests.example.com/dongle"): resource.MustParse("3"), 341 corev1.ResourcePods: resource.MustParse("1"), 342 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 343 }, 344 }, 345 "terminated generic count still appears": { 346 pod: &api.Pod{ 347 Spec: api.PodSpec{ 348 Containers: []api.Container{{ 349 Resources: api.ResourceRequirements{ 350 Requests: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 351 Limits: api.ResourceList{api.ResourceName("example.com/dongle"): resource.MustParse("3")}, 352 }, 353 }}, 354 }, 355 Status: api.PodStatus{ 356 Phase: api.PodSucceeded, 357 }, 358 }, 359 usage: corev1.ResourceList{ 360 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 361 }, 362 }, 363 "init container maximums override sum of containers": { 364 pod: &api.Pod{ 365 Spec: api.PodSpec{ 366 InitContainers: []api.Container{ 367 { 368 Resources: api.ResourceRequirements{ 369 Requests: api.ResourceList{ 370 api.ResourceCPU: resource.MustParse("4"), 371 api.ResourceMemory: resource.MustParse("100M"), 372 api.ResourceName("example.com/dongle"): resource.MustParse("4"), 373 }, 374 Limits: api.ResourceList{ 375 api.ResourceCPU: resource.MustParse("8"), 376 api.ResourceMemory: resource.MustParse("200M"), 377 api.ResourceName("example.com/dongle"): resource.MustParse("4"), 378 }, 379 }, 380 }, 381 { 382 Resources: api.ResourceRequirements{ 383 Requests: api.ResourceList{ 384 api.ResourceCPU: resource.MustParse("1"), 385 api.ResourceMemory: resource.MustParse("50M"), 386 api.ResourceName("example.com/dongle"): resource.MustParse("2"), 387 }, 388 Limits: api.ResourceList{ 389 api.ResourceCPU: resource.MustParse("2"), 390 api.ResourceMemory: resource.MustParse("100M"), 391 api.ResourceName("example.com/dongle"): resource.MustParse("2"), 392 }, 393 }, 394 }, 395 }, 396 Containers: []api.Container{ 397 { 398 Resources: api.ResourceRequirements{ 399 Requests: api.ResourceList{ 400 api.ResourceCPU: resource.MustParse("1"), 401 api.ResourceMemory: resource.MustParse("50M"), 402 api.ResourceName("example.com/dongle"): resource.MustParse("1"), 403 }, 404 Limits: api.ResourceList{ 405 api.ResourceCPU: resource.MustParse("2"), 406 api.ResourceMemory: resource.MustParse("100M"), 407 api.ResourceName("example.com/dongle"): resource.MustParse("1"), 408 }, 409 }, 410 }, 411 { 412 Resources: api.ResourceRequirements{ 413 Requests: api.ResourceList{ 414 api.ResourceCPU: resource.MustParse("2"), 415 api.ResourceMemory: resource.MustParse("25M"), 416 api.ResourceName("example.com/dongle"): resource.MustParse("2"), 417 }, 418 Limits: api.ResourceList{ 419 api.ResourceCPU: resource.MustParse("5"), 420 api.ResourceMemory: resource.MustParse("50M"), 421 api.ResourceName("example.com/dongle"): resource.MustParse("2"), 422 }, 423 }, 424 }, 425 }, 426 }, 427 }, 428 usage: corev1.ResourceList{ 429 corev1.ResourceRequestsCPU: resource.MustParse("4"), 430 corev1.ResourceRequestsMemory: resource.MustParse("100M"), 431 corev1.ResourceLimitsCPU: resource.MustParse("8"), 432 corev1.ResourceLimitsMemory: resource.MustParse("200M"), 433 corev1.ResourcePods: resource.MustParse("1"), 434 corev1.ResourceCPU: resource.MustParse("4"), 435 corev1.ResourceMemory: resource.MustParse("100M"), 436 corev1.ResourceName("requests.example.com/dongle"): resource.MustParse("4"), 437 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 438 }, 439 }, 440 "pod deletion timestamp exceeded": { 441 pod: &api.Pod{ 442 ObjectMeta: metav1.ObjectMeta{ 443 DeletionTimestamp: &deletionTimestampPastGracePeriod, 444 DeletionGracePeriodSeconds: &terminationGracePeriodSeconds, 445 }, 446 Status: api.PodStatus{ 447 Reason: node.NodeUnreachablePodReason, 448 }, 449 Spec: api.PodSpec{ 450 TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, 451 Containers: []api.Container{ 452 { 453 Resources: api.ResourceRequirements{ 454 Requests: api.ResourceList{ 455 api.ResourceCPU: resource.MustParse("1"), 456 api.ResourceMemory: resource.MustParse("50M"), 457 }, 458 Limits: api.ResourceList{ 459 api.ResourceCPU: resource.MustParse("2"), 460 api.ResourceMemory: resource.MustParse("100M"), 461 }, 462 }, 463 }, 464 }, 465 }, 466 }, 467 usage: corev1.ResourceList{ 468 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 469 }, 470 }, 471 "pod deletion timestamp not exceeded": { 472 pod: &api.Pod{ 473 ObjectMeta: metav1.ObjectMeta{ 474 DeletionTimestamp: &deletionTimestampNotPastGracePeriod, 475 DeletionGracePeriodSeconds: &terminationGracePeriodSeconds, 476 }, 477 Status: api.PodStatus{ 478 Reason: node.NodeUnreachablePodReason, 479 }, 480 Spec: api.PodSpec{ 481 Containers: []api.Container{ 482 { 483 Resources: api.ResourceRequirements{ 484 Requests: api.ResourceList{ 485 api.ResourceCPU: resource.MustParse("1"), 486 }, 487 Limits: api.ResourceList{ 488 api.ResourceCPU: resource.MustParse("2"), 489 }, 490 }, 491 }, 492 }, 493 }, 494 }, 495 usage: corev1.ResourceList{ 496 corev1.ResourceRequestsCPU: resource.MustParse("1"), 497 corev1.ResourceLimitsCPU: resource.MustParse("2"), 498 corev1.ResourcePods: resource.MustParse("1"), 499 corev1.ResourceCPU: resource.MustParse("1"), 500 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 501 }, 502 }, 503 "count pod overhead as usage": { 504 pod: &api.Pod{ 505 Spec: api.PodSpec{ 506 Overhead: api.ResourceList{ 507 api.ResourceCPU: resource.MustParse("1"), 508 }, 509 Containers: []api.Container{ 510 { 511 Resources: api.ResourceRequirements{ 512 Requests: api.ResourceList{ 513 api.ResourceCPU: resource.MustParse("1"), 514 }, 515 Limits: api.ResourceList{ 516 api.ResourceCPU: resource.MustParse("2"), 517 }, 518 }, 519 }, 520 }, 521 }, 522 }, 523 usage: corev1.ResourceList{ 524 corev1.ResourceRequestsCPU: resource.MustParse("2"), 525 corev1.ResourceLimitsCPU: resource.MustParse("3"), 526 corev1.ResourcePods: resource.MustParse("1"), 527 corev1.ResourceCPU: resource.MustParse("2"), 528 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 529 }, 530 }, 531 } 532 t.Parallel() 533 for testName, testCase := range testCases { 534 t.Run(testName, func(t *testing.T) { 535 actual, err := evaluator.Usage(testCase.pod) 536 if err != nil { 537 t.Error(err) 538 } 539 if !quota.Equals(testCase.usage, actual) { 540 t.Errorf("expected: %v, actual: %v", testCase.usage, actual) 541 } 542 }) 543 } 544 } 545 546 func TestPodEvaluatorUsageStats(t *testing.T) { 547 cpu1 := api.ResourceList{api.ResourceCPU: resource.MustParse("1")} 548 tests := []struct { 549 name string 550 objs []runtime.Object 551 quotaScopes []corev1.ResourceQuotaScope 552 quotaScopeSelector *corev1.ScopeSelector 553 want corev1.ResourceList 554 }{ 555 { 556 name: "nil case", 557 }, 558 { 559 name: "all pods in running state", 560 objs: []runtime.Object{ 561 makePod("p1", "", cpu1, api.PodRunning), 562 makePod("p2", "", cpu1, api.PodRunning), 563 }, 564 want: corev1.ResourceList{ 565 corev1.ResourcePods: resource.MustParse("2"), 566 corev1.ResourceName("count/pods"): resource.MustParse("2"), 567 corev1.ResourceCPU: resource.MustParse("2"), 568 corev1.ResourceRequestsCPU: resource.MustParse("2"), 569 corev1.ResourceLimitsCPU: resource.MustParse("2"), 570 }, 571 }, 572 { 573 name: "one pods in terminal state", 574 objs: []runtime.Object{ 575 makePod("p1", "", cpu1, api.PodRunning), 576 makePod("p2", "", cpu1, api.PodSucceeded), 577 }, 578 want: corev1.ResourceList{ 579 corev1.ResourcePods: resource.MustParse("1"), 580 corev1.ResourceName("count/pods"): resource.MustParse("2"), 581 corev1.ResourceCPU: resource.MustParse("1"), 582 corev1.ResourceRequestsCPU: resource.MustParse("1"), 583 corev1.ResourceLimitsCPU: resource.MustParse("1"), 584 }, 585 }, 586 { 587 name: "partial pods matching quotaScopeSelector", 588 objs: []runtime.Object{ 589 makePod("p1", "high-priority", cpu1, api.PodRunning), 590 makePod("p2", "high-priority", cpu1, api.PodSucceeded), 591 makePod("p3", "low-priority", cpu1, api.PodRunning), 592 }, 593 quotaScopeSelector: &corev1.ScopeSelector{ 594 MatchExpressions: []corev1.ScopedResourceSelectorRequirement{ 595 { 596 ScopeName: corev1.ResourceQuotaScopePriorityClass, 597 Operator: corev1.ScopeSelectorOpIn, 598 Values: []string{"high-priority"}, 599 }, 600 }, 601 }, 602 want: corev1.ResourceList{ 603 corev1.ResourcePods: resource.MustParse("1"), 604 corev1.ResourceName("count/pods"): resource.MustParse("2"), 605 corev1.ResourceCPU: resource.MustParse("1"), 606 corev1.ResourceRequestsCPU: resource.MustParse("1"), 607 corev1.ResourceLimitsCPU: resource.MustParse("1"), 608 }, 609 }, 610 { 611 name: "partial pods matching quotaScopeSelector - w/ scopeName specified", 612 objs: []runtime.Object{ 613 makePod("p1", "high-priority", cpu1, api.PodRunning), 614 makePod("p2", "high-priority", cpu1, api.PodSucceeded), 615 makePod("p3", "low-priority", cpu1, api.PodRunning), 616 }, 617 quotaScopes: []corev1.ResourceQuotaScope{ 618 corev1.ResourceQuotaScopePriorityClass, 619 }, 620 quotaScopeSelector: &corev1.ScopeSelector{ 621 MatchExpressions: []corev1.ScopedResourceSelectorRequirement{ 622 { 623 ScopeName: corev1.ResourceQuotaScopePriorityClass, 624 Operator: corev1.ScopeSelectorOpIn, 625 Values: []string{"high-priority"}, 626 }, 627 }, 628 }, 629 want: corev1.ResourceList{ 630 corev1.ResourcePods: resource.MustParse("1"), 631 corev1.ResourceName("count/pods"): resource.MustParse("2"), 632 corev1.ResourceCPU: resource.MustParse("1"), 633 corev1.ResourceRequestsCPU: resource.MustParse("1"), 634 corev1.ResourceLimitsCPU: resource.MustParse("1"), 635 }, 636 }, 637 { 638 name: "partial pods matching quotaScopeSelector - w/ multiple scopeNames specified", 639 objs: []runtime.Object{ 640 makePod("p1", "high-priority", cpu1, api.PodRunning), 641 makePod("p2", "high-priority", cpu1, api.PodSucceeded), 642 makePod("p3", "low-priority", cpu1, api.PodRunning), 643 makePod("p4", "high-priority", nil, api.PodFailed), 644 }, 645 quotaScopes: []corev1.ResourceQuotaScope{ 646 corev1.ResourceQuotaScopePriorityClass, 647 corev1.ResourceQuotaScopeBestEffort, 648 }, 649 quotaScopeSelector: &corev1.ScopeSelector{ 650 MatchExpressions: []corev1.ScopedResourceSelectorRequirement{ 651 { 652 ScopeName: corev1.ResourceQuotaScopePriorityClass, 653 Operator: corev1.ScopeSelectorOpIn, 654 Values: []string{"high-priority"}, 655 }, 656 }, 657 }, 658 want: corev1.ResourceList{ 659 corev1.ResourceName("count/pods"): resource.MustParse("1"), 660 }, 661 }, 662 } 663 664 for _, tt := range tests { 665 t.Run(tt.name, func(t *testing.T) { 666 gvr := corev1.SchemeGroupVersion.WithResource("pods") 667 listerForPod := map[schema.GroupVersionResource]cache.GenericLister{ 668 gvr: newGenericLister(gvr.GroupResource(), tt.objs), 669 } 670 evaluator := NewPodEvaluator(mockListerForResourceFunc(listerForPod), testingclock.NewFakeClock(time.Now())) 671 usageStatsOption := quota.UsageStatsOptions{ 672 Scopes: tt.quotaScopes, 673 ScopeSelector: tt.quotaScopeSelector, 674 } 675 actual, err := evaluator.UsageStats(usageStatsOption) 676 if err != nil { 677 t.Error(err) 678 } 679 if !quota.Equals(tt.want, actual.Used) { 680 t.Errorf("expected: %v, actual: %v", tt.want, actual.Used) 681 } 682 }) 683 } 684 } 685 686 func TestPodEvaluatorMatchingScopes(t *testing.T) { 687 fakeClock := testingclock.NewFakeClock(time.Now()) 688 evaluator := NewPodEvaluator(nil, fakeClock) 689 activeDeadlineSeconds := int64(30) 690 testCases := map[string]struct { 691 pod *api.Pod 692 selectors []corev1.ScopedResourceSelectorRequirement 693 wantSelectors []corev1.ScopedResourceSelectorRequirement 694 }{ 695 "EmptyPod": { 696 pod: &api.Pod{}, 697 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 698 {ScopeName: corev1.ResourceQuotaScopeNotTerminating}, 699 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 700 }, 701 }, 702 "PriorityClass": { 703 pod: &api.Pod{ 704 Spec: api.PodSpec{ 705 PriorityClassName: "class1", 706 }, 707 }, 708 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 709 {ScopeName: corev1.ResourceQuotaScopeNotTerminating}, 710 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 711 {ScopeName: corev1.ResourceQuotaScopePriorityClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}}, 712 }, 713 }, 714 "NotBestEffort": { 715 pod: &api.Pod{ 716 Spec: api.PodSpec{ 717 Containers: []api.Container{{ 718 Resources: api.ResourceRequirements{ 719 Requests: api.ResourceList{ 720 api.ResourceCPU: resource.MustParse("1"), 721 api.ResourceMemory: resource.MustParse("50M"), 722 api.ResourceName("example.com/dongle"): resource.MustParse("1"), 723 }, 724 Limits: api.ResourceList{ 725 api.ResourceCPU: resource.MustParse("2"), 726 api.ResourceMemory: resource.MustParse("100M"), 727 api.ResourceName("example.com/dongle"): resource.MustParse("1"), 728 }, 729 }, 730 }}, 731 }, 732 }, 733 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 734 {ScopeName: corev1.ResourceQuotaScopeNotTerminating}, 735 {ScopeName: corev1.ResourceQuotaScopeNotBestEffort}, 736 }, 737 }, 738 "Terminating": { 739 pod: &api.Pod{ 740 Spec: api.PodSpec{ 741 ActiveDeadlineSeconds: &activeDeadlineSeconds, 742 }, 743 }, 744 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 745 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 746 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 747 }, 748 }, 749 "OnlyTerminating": { 750 pod: &api.Pod{ 751 Spec: api.PodSpec{ 752 ActiveDeadlineSeconds: &activeDeadlineSeconds, 753 }, 754 }, 755 selectors: []corev1.ScopedResourceSelectorRequirement{ 756 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 757 }, 758 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 759 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 760 }, 761 }, 762 "CrossNamespaceRequiredAffinity": { 763 pod: &api.Pod{ 764 Spec: api.PodSpec{ 765 ActiveDeadlineSeconds: &activeDeadlineSeconds, 766 Affinity: &api.Affinity{ 767 PodAffinity: &api.PodAffinity{ 768 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 769 {LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}, NamespaceSelector: &metav1.LabelSelector{}}, 770 }, 771 }, 772 }, 773 }, 774 }, 775 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 776 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 777 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 778 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 779 }, 780 }, 781 "CrossNamespaceRequiredAffinityWithSlice": { 782 pod: &api.Pod{ 783 Spec: api.PodSpec{ 784 ActiveDeadlineSeconds: &activeDeadlineSeconds, 785 Affinity: &api.Affinity{ 786 PodAffinity: &api.PodAffinity{ 787 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 788 {LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns1"}}, 789 }, 790 }, 791 }, 792 }, 793 }, 794 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 795 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 796 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 797 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 798 }, 799 }, 800 "CrossNamespacePreferredAffinity": { 801 pod: &api.Pod{ 802 Spec: api.PodSpec{ 803 ActiveDeadlineSeconds: &activeDeadlineSeconds, 804 Affinity: &api.Affinity{ 805 PodAffinity: &api.PodAffinity{ 806 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 807 {PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns2"}, NamespaceSelector: &metav1.LabelSelector{}}}, 808 }, 809 }, 810 }, 811 }, 812 }, 813 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 814 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 815 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 816 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 817 }, 818 }, 819 "CrossNamespacePreferredAffinityWithSelector": { 820 pod: &api.Pod{ 821 Spec: api.PodSpec{ 822 ActiveDeadlineSeconds: &activeDeadlineSeconds, 823 Affinity: &api.Affinity{ 824 PodAffinity: &api.PodAffinity{ 825 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 826 {PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{}}}, 827 }, 828 }, 829 }, 830 }, 831 }, 832 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 833 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 834 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 835 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 836 }, 837 }, 838 "CrossNamespacePreferredAntiAffinity": { 839 pod: &api.Pod{ 840 Spec: api.PodSpec{ 841 ActiveDeadlineSeconds: &activeDeadlineSeconds, 842 Affinity: &api.Affinity{ 843 PodAntiAffinity: &api.PodAntiAffinity{ 844 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 845 {PodAffinityTerm: api.PodAffinityTerm{LabelSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{}}}, 846 }, 847 }, 848 }, 849 }, 850 }, 851 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 852 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 853 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 854 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 855 }, 856 }, 857 "CrossNamespaceRequiredAntiAffinity": { 858 pod: &api.Pod{ 859 Spec: api.PodSpec{ 860 ActiveDeadlineSeconds: &activeDeadlineSeconds, 861 Affinity: &api.Affinity{ 862 PodAntiAffinity: &api.PodAntiAffinity{ 863 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 864 {LabelSelector: &metav1.LabelSelector{}, Namespaces: []string{"ns3"}}, 865 }, 866 }, 867 }, 868 }, 869 }, 870 wantSelectors: []corev1.ScopedResourceSelectorRequirement{ 871 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 872 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 873 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 874 }, 875 }, 876 } 877 for testName, testCase := range testCases { 878 t.Run(testName, func(t *testing.T) { 879 if testCase.selectors == nil { 880 testCase.selectors = []corev1.ScopedResourceSelectorRequirement{ 881 {ScopeName: corev1.ResourceQuotaScopeTerminating}, 882 {ScopeName: corev1.ResourceQuotaScopeNotTerminating}, 883 {ScopeName: corev1.ResourceQuotaScopeBestEffort}, 884 {ScopeName: corev1.ResourceQuotaScopeNotBestEffort}, 885 {ScopeName: corev1.ResourceQuotaScopePriorityClass, Operator: corev1.ScopeSelectorOpIn, Values: []string{"class1"}}, 886 {ScopeName: corev1.ResourceQuotaScopeCrossNamespacePodAffinity}, 887 } 888 } 889 gotSelectors, err := evaluator.MatchingScopes(testCase.pod, testCase.selectors) 890 if err != nil { 891 t.Error(err) 892 } 893 if diff := cmp.Diff(testCase.wantSelectors, gotSelectors); diff != "" { 894 t.Errorf("%v: unexpected diff (-want, +got):\n%s", testName, diff) 895 } 896 }) 897 } 898 } 899 900 func TestPodEvaluatorUsageResourceResize(t *testing.T) { 901 fakeClock := testingclock.NewFakeClock(time.Now()) 902 evaluator := NewPodEvaluator(nil, fakeClock) 903 904 testCases := map[string]struct { 905 pod *api.Pod 906 usageFgEnabled corev1.ResourceList 907 usageFgDisabled corev1.ResourceList 908 }{ 909 "verify Max(Container.Spec.Requests, ContainerStatus.AllocatedResources) for memory resource": { 910 pod: &api.Pod{ 911 Spec: api.PodSpec{ 912 Containers: []api.Container{ 913 { 914 Resources: api.ResourceRequirements{ 915 Requests: api.ResourceList{ 916 api.ResourceMemory: resource.MustParse("200Mi"), 917 }, 918 Limits: api.ResourceList{ 919 api.ResourceMemory: resource.MustParse("400Mi"), 920 }, 921 }, 922 }, 923 }, 924 }, 925 Status: api.PodStatus{ 926 ContainerStatuses: []api.ContainerStatus{ 927 { 928 AllocatedResources: api.ResourceList{ 929 api.ResourceMemory: resource.MustParse("150Mi"), 930 }, 931 }, 932 }, 933 }, 934 }, 935 usageFgEnabled: corev1.ResourceList{ 936 corev1.ResourceRequestsMemory: resource.MustParse("200Mi"), 937 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 938 corev1.ResourcePods: resource.MustParse("1"), 939 corev1.ResourceMemory: resource.MustParse("200Mi"), 940 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 941 }, 942 usageFgDisabled: corev1.ResourceList{ 943 corev1.ResourceRequestsMemory: resource.MustParse("200Mi"), 944 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 945 corev1.ResourcePods: resource.MustParse("1"), 946 corev1.ResourceMemory: resource.MustParse("200Mi"), 947 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 948 }, 949 }, 950 "verify Max(Container.Spec.Requests, ContainerStatus.AllocatedResources) for CPU resource": { 951 pod: &api.Pod{ 952 Spec: api.PodSpec{ 953 Containers: []api.Container{ 954 { 955 Resources: api.ResourceRequirements{ 956 Requests: api.ResourceList{ 957 api.ResourceCPU: resource.MustParse("100m"), 958 }, 959 Limits: api.ResourceList{ 960 api.ResourceCPU: resource.MustParse("200m"), 961 }, 962 }, 963 }, 964 }, 965 }, 966 Status: api.PodStatus{ 967 ContainerStatuses: []api.ContainerStatus{ 968 { 969 AllocatedResources: api.ResourceList{ 970 api.ResourceCPU: resource.MustParse("150m"), 971 }, 972 }, 973 }, 974 }, 975 }, 976 usageFgEnabled: corev1.ResourceList{ 977 corev1.ResourceRequestsCPU: resource.MustParse("150m"), 978 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 979 corev1.ResourcePods: resource.MustParse("1"), 980 corev1.ResourceCPU: resource.MustParse("150m"), 981 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 982 }, 983 usageFgDisabled: corev1.ResourceList{ 984 corev1.ResourceRequestsCPU: resource.MustParse("100m"), 985 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 986 corev1.ResourcePods: resource.MustParse("1"), 987 corev1.ResourceCPU: resource.MustParse("100m"), 988 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 989 }, 990 }, 991 "verify Max(Container.Spec.Requests, ContainerStatus.AllocatedResources) for CPU and memory resource": { 992 pod: &api.Pod{ 993 Spec: api.PodSpec{ 994 Containers: []api.Container{ 995 { 996 Resources: api.ResourceRequirements{ 997 Requests: api.ResourceList{ 998 api.ResourceCPU: resource.MustParse("100m"), 999 api.ResourceMemory: resource.MustParse("200Mi"), 1000 }, 1001 Limits: api.ResourceList{ 1002 api.ResourceCPU: resource.MustParse("200m"), 1003 api.ResourceMemory: resource.MustParse("400Mi"), 1004 }, 1005 }, 1006 }, 1007 }, 1008 }, 1009 Status: api.PodStatus{ 1010 ContainerStatuses: []api.ContainerStatus{ 1011 { 1012 AllocatedResources: api.ResourceList{ 1013 api.ResourceCPU: resource.MustParse("150m"), 1014 api.ResourceMemory: resource.MustParse("250Mi"), 1015 }, 1016 }, 1017 }, 1018 }, 1019 }, 1020 usageFgEnabled: corev1.ResourceList{ 1021 corev1.ResourceRequestsCPU: resource.MustParse("150m"), 1022 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 1023 corev1.ResourceRequestsMemory: resource.MustParse("250Mi"), 1024 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 1025 corev1.ResourcePods: resource.MustParse("1"), 1026 corev1.ResourceCPU: resource.MustParse("150m"), 1027 corev1.ResourceMemory: resource.MustParse("250Mi"), 1028 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 1029 }, 1030 usageFgDisabled: corev1.ResourceList{ 1031 corev1.ResourceRequestsCPU: resource.MustParse("100m"), 1032 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 1033 corev1.ResourceRequestsMemory: resource.MustParse("200Mi"), 1034 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 1035 corev1.ResourcePods: resource.MustParse("1"), 1036 corev1.ResourceCPU: resource.MustParse("100m"), 1037 corev1.ResourceMemory: resource.MustParse("200Mi"), 1038 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 1039 }, 1040 }, 1041 "verify Max(Container.Spec.Requests, ContainerStatus.AllocatedResources==nil) for CPU and memory resource": { 1042 pod: &api.Pod{ 1043 Spec: api.PodSpec{ 1044 Containers: []api.Container{ 1045 { 1046 Resources: api.ResourceRequirements{ 1047 Requests: api.ResourceList{ 1048 api.ResourceCPU: resource.MustParse("100m"), 1049 api.ResourceMemory: resource.MustParse("200Mi"), 1050 }, 1051 Limits: api.ResourceList{ 1052 api.ResourceCPU: resource.MustParse("200m"), 1053 api.ResourceMemory: resource.MustParse("400Mi"), 1054 }, 1055 }, 1056 }, 1057 }, 1058 }, 1059 Status: api.PodStatus{ 1060 ContainerStatuses: []api.ContainerStatus{ 1061 {}, 1062 }, 1063 }, 1064 }, 1065 usageFgEnabled: corev1.ResourceList{ 1066 corev1.ResourceRequestsCPU: resource.MustParse("100m"), 1067 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 1068 corev1.ResourceRequestsMemory: resource.MustParse("200Mi"), 1069 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 1070 corev1.ResourcePods: resource.MustParse("1"), 1071 corev1.ResourceCPU: resource.MustParse("100m"), 1072 corev1.ResourceMemory: resource.MustParse("200Mi"), 1073 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 1074 }, 1075 usageFgDisabled: corev1.ResourceList{ 1076 corev1.ResourceRequestsCPU: resource.MustParse("100m"), 1077 corev1.ResourceLimitsCPU: resource.MustParse("200m"), 1078 corev1.ResourceRequestsMemory: resource.MustParse("200Mi"), 1079 corev1.ResourceLimitsMemory: resource.MustParse("400Mi"), 1080 corev1.ResourcePods: resource.MustParse("1"), 1081 corev1.ResourceCPU: resource.MustParse("100m"), 1082 corev1.ResourceMemory: resource.MustParse("200Mi"), 1083 generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"), 1084 }, 1085 }, 1086 } 1087 t.Parallel() 1088 for _, enabled := range []bool{true, false} { 1089 for testName, testCase := range testCases { 1090 t.Run(testName, func(t *testing.T) { 1091 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, enabled)() 1092 actual, err := evaluator.Usage(testCase.pod) 1093 if err != nil { 1094 t.Error(err) 1095 } 1096 usage := testCase.usageFgEnabled 1097 if !enabled { 1098 usage = testCase.usageFgDisabled 1099 } 1100 if !quota.Equals(usage, actual) { 1101 t.Errorf("FG enabled: %v, expected: %v, actual: %v", enabled, usage, actual) 1102 } 1103 }) 1104 } 1105 } 1106 } 1107 1108 func BenchmarkPodMatchesScopeFunc(b *testing.B) { 1109 pod, _ := toExternalPodOrError(makePod("p1", "high-priority", 1110 api.ResourceList{api.ResourceCPU: resource.MustParse("1")}, api.PodRunning)) 1111 1112 tests := []struct { 1113 name string 1114 selector corev1.ScopedResourceSelectorRequirement 1115 }{ 1116 { 1117 name: "PriorityClass selector w/o operator", 1118 selector: corev1.ScopedResourceSelectorRequirement{ 1119 ScopeName: corev1.ResourceQuotaScopePriorityClass, 1120 }, 1121 }, 1122 { 1123 name: "PriorityClass selector w/ 'Exists' operator", 1124 selector: corev1.ScopedResourceSelectorRequirement{ 1125 ScopeName: corev1.ResourceQuotaScopePriorityClass, 1126 Operator: corev1.ScopeSelectorOpExists, 1127 }, 1128 }, 1129 { 1130 name: "BestEfforts selector w/o operator", 1131 selector: corev1.ScopedResourceSelectorRequirement{ 1132 ScopeName: corev1.ResourceQuotaScopeBestEffort, 1133 }, 1134 }, 1135 { 1136 name: "BestEfforts selector w/ 'Exists' operator", 1137 selector: corev1.ScopedResourceSelectorRequirement{ 1138 ScopeName: corev1.ResourceQuotaScopeBestEffort, 1139 Operator: corev1.ScopeSelectorOpExists, 1140 }, 1141 }, 1142 } 1143 1144 for _, tt := range tests { 1145 b.Run(tt.name, func(b *testing.B) { 1146 for n := 0; n < b.N; n++ { 1147 _, _ = podMatchesScopeFunc(tt.selector, pod) 1148 } 1149 }) 1150 } 1151 } 1152 1153 func mockListerForResourceFunc(listerForResource map[schema.GroupVersionResource]cache.GenericLister) quota.ListerForResourceFunc { 1154 return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) { 1155 lister, found := listerForResource[gvr] 1156 if !found { 1157 return nil, fmt.Errorf("no lister found for resource %v", gvr) 1158 } 1159 return lister, nil 1160 } 1161 } 1162 1163 func newGenericLister(groupResource schema.GroupResource, items []runtime.Object) cache.GenericLister { 1164 store := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) 1165 for _, item := range items { 1166 store.Add(item) 1167 } 1168 return cache.NewGenericLister(store, groupResource) 1169 } 1170 1171 func makePod(name, pcName string, resList api.ResourceList, phase api.PodPhase) *api.Pod { 1172 return &api.Pod{ 1173 ObjectMeta: metav1.ObjectMeta{ 1174 Name: name, 1175 }, 1176 Spec: api.PodSpec{ 1177 PriorityClassName: pcName, 1178 Containers: []api.Container{ 1179 { 1180 Resources: api.ResourceRequirements{ 1181 Requests: resList, 1182 Limits: resList, 1183 }, 1184 }, 1185 }, 1186 }, 1187 Status: api.PodStatus{ 1188 Phase: phase, 1189 }, 1190 } 1191 }