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  }