k8s.io/kubernetes@v1.29.3/pkg/api/v1/resource/helpers_test.go (about)

     1  /*
     2  Copyright 2015 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 resource
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/equality"
    26  	"k8s.io/apimachinery/pkg/api/resource"
    27  )
    28  
    29  func TestResourceHelpers(t *testing.T) {
    30  	cpuLimit := resource.MustParse("10")
    31  	memoryLimit := resource.MustParse("10G")
    32  	resourceSpec := v1.ResourceRequirements{
    33  		Limits: v1.ResourceList{
    34  			v1.ResourceCPU:    cpuLimit,
    35  			v1.ResourceMemory: memoryLimit,
    36  		},
    37  	}
    38  	if res := resourceSpec.Limits.Cpu(); res.Cmp(cpuLimit) != 0 {
    39  		t.Errorf("expected cpulimit %v, got %v", cpuLimit, res)
    40  	}
    41  	if res := resourceSpec.Limits.Memory(); res.Cmp(memoryLimit) != 0 {
    42  		t.Errorf("expected memorylimit %v, got %v", memoryLimit, res)
    43  	}
    44  	resourceSpec = v1.ResourceRequirements{
    45  		Limits: v1.ResourceList{
    46  			v1.ResourceMemory: memoryLimit,
    47  		},
    48  	}
    49  	if res := resourceSpec.Limits.Cpu(); res.Value() != 0 {
    50  		t.Errorf("expected cpulimit %v, got %v", 0, res)
    51  	}
    52  	if res := resourceSpec.Limits.Memory(); res.Cmp(memoryLimit) != 0 {
    53  		t.Errorf("expected memorylimit %v, got %v", memoryLimit, res)
    54  	}
    55  }
    56  
    57  func TestDefaultResourceHelpers(t *testing.T) {
    58  	resourceList := v1.ResourceList{}
    59  	if resourceList.Cpu().Format != resource.DecimalSI {
    60  		t.Errorf("expected %v, actual %v", resource.DecimalSI, resourceList.Cpu().Format)
    61  	}
    62  	if resourceList.Memory().Format != resource.BinarySI {
    63  		t.Errorf("expected %v, actual %v", resource.BinarySI, resourceList.Memory().Format)
    64  	}
    65  }
    66  
    67  func TestGetResourceRequest(t *testing.T) {
    68  	cases := []struct {
    69  		pod           *v1.Pod
    70  		cName         string
    71  		resourceName  v1.ResourceName
    72  		expectedValue int64
    73  	}{
    74  		{
    75  			pod:           getPod("foo", podResources{cpuRequest: "9"}),
    76  			resourceName:  v1.ResourceCPU,
    77  			expectedValue: 9000,
    78  		},
    79  		{
    80  			pod:           getPod("foo", podResources{memoryRequest: "90Mi"}),
    81  			resourceName:  v1.ResourceMemory,
    82  			expectedValue: 94371840,
    83  		},
    84  		{
    85  			cName:         "just-overhead for cpu",
    86  			pod:           getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}),
    87  			resourceName:  v1.ResourceCPU,
    88  			expectedValue: 0,
    89  		},
    90  		{
    91  			cName:         "just-overhead for memory",
    92  			pod:           getPod("foo", podResources{memoryOverhead: "5"}),
    93  			resourceName:  v1.ResourceMemory,
    94  			expectedValue: 0,
    95  		},
    96  		{
    97  			cName:         "cpu overhead and req",
    98  			pod:           getPod("foo", podResources{cpuRequest: "2", cpuOverhead: "5", memoryOverhead: "5"}),
    99  			resourceName:  v1.ResourceCPU,
   100  			expectedValue: 7000,
   101  		},
   102  		{
   103  			cName:         "mem overhead and req",
   104  			pod:           getPod("foo", podResources{cpuRequest: "2", memoryRequest: "1024", cpuOverhead: "5", memoryOverhead: "5"}),
   105  			resourceName:  v1.ResourceMemory,
   106  			expectedValue: 1029,
   107  		},
   108  	}
   109  	as := assert.New(t)
   110  	for idx, tc := range cases {
   111  		actual := GetResourceRequest(tc.pod, tc.resourceName)
   112  		as.Equal(actual, tc.expectedValue, "expected test case [%d] %v: to return %q; got %q instead", idx, tc.cName, tc.expectedValue, actual)
   113  	}
   114  }
   115  
   116  func TestExtractResourceValue(t *testing.T) {
   117  	cases := []struct {
   118  		fs            *v1.ResourceFieldSelector
   119  		pod           *v1.Pod
   120  		cName         string
   121  		expectedValue string
   122  		expectedError error
   123  	}{
   124  		{
   125  			fs: &v1.ResourceFieldSelector{
   126  				Resource: "limits.cpu",
   127  			},
   128  			cName:         "foo",
   129  			pod:           getPod("foo", podResources{cpuLimit: "9"}),
   130  			expectedValue: "9",
   131  		},
   132  		{
   133  			fs: &v1.ResourceFieldSelector{
   134  				Resource: "requests.cpu",
   135  			},
   136  			cName:         "foo",
   137  			pod:           getPod("foo", podResources{}),
   138  			expectedValue: "0",
   139  		},
   140  		{
   141  			fs: &v1.ResourceFieldSelector{
   142  				Resource: "requests.cpu",
   143  			},
   144  			cName:         "foo",
   145  			pod:           getPod("foo", podResources{cpuRequest: "8"}),
   146  			expectedValue: "8",
   147  		},
   148  		{
   149  			fs: &v1.ResourceFieldSelector{
   150  				Resource: "requests.cpu",
   151  			},
   152  			cName:         "foo",
   153  			pod:           getPod("foo", podResources{cpuRequest: "100m"}),
   154  			expectedValue: "1",
   155  		},
   156  		{
   157  			fs: &v1.ResourceFieldSelector{
   158  				Resource: "requests.cpu",
   159  				Divisor:  resource.MustParse("100m"),
   160  			},
   161  			cName:         "foo",
   162  			pod:           getPod("foo", podResources{cpuRequest: "1200m"}),
   163  			expectedValue: "12",
   164  		},
   165  		{
   166  			fs: &v1.ResourceFieldSelector{
   167  				Resource: "requests.memory",
   168  			},
   169  			cName:         "foo",
   170  			pod:           getPod("foo", podResources{memoryRequest: "100Mi"}),
   171  			expectedValue: "104857600",
   172  		},
   173  		{
   174  			fs: &v1.ResourceFieldSelector{
   175  				Resource: "requests.memory",
   176  				Divisor:  resource.MustParse("1Mi"),
   177  			},
   178  			cName:         "foo",
   179  			pod:           getPod("foo", podResources{memoryRequest: "100Mi", memoryLimit: "1Gi"}),
   180  			expectedValue: "100",
   181  		},
   182  		{
   183  			fs: &v1.ResourceFieldSelector{
   184  				Resource: "limits.memory",
   185  			},
   186  			cName:         "foo",
   187  			pod:           getPod("foo", podResources{memoryRequest: "10Mi", memoryLimit: "100Mi"}),
   188  			expectedValue: "104857600",
   189  		},
   190  		{
   191  			fs: &v1.ResourceFieldSelector{
   192  				Resource: "limits.cpu",
   193  			},
   194  			cName:         "init-foo",
   195  			pod:           getPod("foo", podResources{cpuLimit: "9"}),
   196  			expectedValue: "9",
   197  		},
   198  		{
   199  			fs: &v1.ResourceFieldSelector{
   200  				Resource: "requests.cpu",
   201  			},
   202  			cName:         "init-foo",
   203  			pod:           getPod("foo", podResources{}),
   204  			expectedValue: "0",
   205  		},
   206  		{
   207  			fs: &v1.ResourceFieldSelector{
   208  				Resource: "requests.cpu",
   209  			},
   210  			cName:         "init-foo",
   211  			pod:           getPod("foo", podResources{cpuRequest: "8"}),
   212  			expectedValue: "8",
   213  		},
   214  		{
   215  			fs: &v1.ResourceFieldSelector{
   216  				Resource: "requests.cpu",
   217  			},
   218  			cName:         "init-foo",
   219  			pod:           getPod("foo", podResources{cpuRequest: "100m"}),
   220  			expectedValue: "1",
   221  		},
   222  		{
   223  			fs: &v1.ResourceFieldSelector{
   224  				Resource: "requests.cpu",
   225  				Divisor:  resource.MustParse("100m"),
   226  			},
   227  			cName:         "init-foo",
   228  			pod:           getPod("foo", podResources{cpuRequest: "1200m"}),
   229  			expectedValue: "12",
   230  		},
   231  		{
   232  			fs: &v1.ResourceFieldSelector{
   233  				Resource: "requests.memory",
   234  			},
   235  			cName:         "init-foo",
   236  			pod:           getPod("foo", podResources{memoryRequest: "100Mi"}),
   237  			expectedValue: "104857600",
   238  		},
   239  		{
   240  			fs: &v1.ResourceFieldSelector{
   241  				Resource: "requests.memory",
   242  				Divisor:  resource.MustParse("1Mi"),
   243  			},
   244  			cName:         "init-foo",
   245  			pod:           getPod("foo", podResources{memoryRequest: "100Mi", memoryLimit: "1Gi"}),
   246  			expectedValue: "100",
   247  		},
   248  		{
   249  			fs: &v1.ResourceFieldSelector{
   250  				Resource: "limits.memory",
   251  			},
   252  			cName: "init-foo",
   253  			pod:   getPod("foo", podResources{memoryRequest: "10Mi", memoryLimit: "100Mi"}),
   254  
   255  			expectedValue: "104857600",
   256  		},
   257  	}
   258  	as := assert.New(t)
   259  	for idx, tc := range cases {
   260  		actual, err := ExtractResourceValueByContainerName(tc.fs, tc.pod, tc.cName)
   261  		if tc.expectedError != nil {
   262  			as.Equal(tc.expectedError, err, "expected test case [%d] to fail with error %v; got %v", idx, tc.expectedError, err)
   263  		} else {
   264  			as.Nil(err, "expected test case [%d] to not return an error; got %v", idx, err)
   265  			as.Equal(tc.expectedValue, actual, "expected test case [%d] to return %q; got %q instead", idx, tc.expectedValue, actual)
   266  		}
   267  	}
   268  }
   269  
   270  func TestPodRequestsAndLimits(t *testing.T) {
   271  	cases := []struct {
   272  		pod              *v1.Pod
   273  		cName            string
   274  		expectedRequests v1.ResourceList
   275  		expectedLimits   v1.ResourceList
   276  	}{
   277  		{
   278  			cName:            "just-limit-no-overhead",
   279  			pod:              getPod("foo", podResources{cpuLimit: "9"}),
   280  			expectedRequests: v1.ResourceList{},
   281  			expectedLimits: v1.ResourceList{
   282  				v1.ResourceName(v1.ResourceCPU): resource.MustParse("9"),
   283  			},
   284  		},
   285  		{
   286  			cName: "just-overhead",
   287  			pod:   getPod("foo", podResources{cpuOverhead: "5", memoryOverhead: "5"}),
   288  			expectedRequests: v1.ResourceList{
   289  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("5"),
   290  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   291  			},
   292  			expectedLimits: v1.ResourceList{},
   293  		},
   294  		{
   295  			cName: "req-and-overhead",
   296  			pod:   getPod("foo", podResources{cpuRequest: "1", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}),
   297  			expectedRequests: v1.ResourceList{
   298  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("6"),
   299  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
   300  			},
   301  			expectedLimits: v1.ResourceList{},
   302  		},
   303  		{
   304  			cName: "all-req-lim-and-overhead",
   305  			pod:   getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", memoryLimit: "12", cpuOverhead: "5", memoryOverhead: "5"}),
   306  			expectedRequests: v1.ResourceList{
   307  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("6"),
   308  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
   309  			},
   310  			expectedLimits: v1.ResourceList{
   311  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("7"),
   312  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"),
   313  			},
   314  		},
   315  		{
   316  			cName: "req-some-lim-and-overhead",
   317  			pod:   getPod("foo", podResources{cpuRequest: "1", cpuLimit: "2", memoryRequest: "10", cpuOverhead: "5", memoryOverhead: "5"}),
   318  			expectedRequests: v1.ResourceList{
   319  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("6"),
   320  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("15"),
   321  			},
   322  			expectedLimits: v1.ResourceList{
   323  				v1.ResourceName(v1.ResourceCPU): resource.MustParse("7"),
   324  			},
   325  		},
   326  	}
   327  	for idx, tc := range cases {
   328  		resRequests := PodRequests(tc.pod, PodResourcesOptions{})
   329  		resLimits := PodLimits(tc.pod, PodResourcesOptions{})
   330  
   331  		if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) {
   332  			t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedRequests, resRequests)
   333  		}
   334  
   335  		if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) {
   336  			t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.cName, tc.expectedLimits, resLimits)
   337  		}
   338  	}
   339  }
   340  
   341  func TestPodRequestsAndLimitsWithoutOverhead(t *testing.T) {
   342  	cases := []struct {
   343  		pod              *v1.Pod
   344  		name             string
   345  		expectedRequests v1.ResourceList
   346  		expectedLimits   v1.ResourceList
   347  	}{
   348  		{
   349  			name: "two container no overhead - should just be sum of containers",
   350  			pod: &v1.Pod{
   351  				Spec: v1.PodSpec{
   352  					Containers: []v1.Container{
   353  						{
   354  							Name: "foobar",
   355  							Resources: v1.ResourceRequirements{
   356  								Requests: v1.ResourceList{
   357  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   358  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   359  								},
   360  								Limits: v1.ResourceList{
   361  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   362  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"),
   363  								},
   364  							},
   365  						},
   366  						{
   367  							Name: "foobar2",
   368  							Resources: v1.ResourceRequirements{
   369  								Requests: v1.ResourceList{
   370  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("4"),
   371  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"),
   372  								},
   373  								Limits: v1.ResourceList{
   374  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("8"),
   375  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"),
   376  								},
   377  							},
   378  						},
   379  					},
   380  				},
   381  			},
   382  			expectedRequests: v1.ResourceList{
   383  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("5"),
   384  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"),
   385  			},
   386  			expectedLimits: v1.ResourceList{
   387  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
   388  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"),
   389  			},
   390  		},
   391  		{
   392  			name: "two container with overhead - shouldn't consider overhead",
   393  			pod: &v1.Pod{
   394  				Spec: v1.PodSpec{
   395  					Overhead: v1.ResourceList{
   396  						v1.ResourceName(v1.ResourceCPU):    resource.MustParse("3"),
   397  						v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"),
   398  					},
   399  					Containers: []v1.Container{
   400  						{
   401  							Name: "foobar",
   402  							Resources: v1.ResourceRequirements{
   403  								Requests: v1.ResourceList{
   404  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   405  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   406  								},
   407  								Limits: v1.ResourceList{
   408  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   409  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"),
   410  								},
   411  							},
   412  						},
   413  						{
   414  							Name: "foobar2",
   415  							Resources: v1.ResourceRequirements{
   416  								Requests: v1.ResourceList{
   417  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("4"),
   418  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"),
   419  								},
   420  								Limits: v1.ResourceList{
   421  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("8"),
   422  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"),
   423  								},
   424  							},
   425  						},
   426  					},
   427  				},
   428  			},
   429  			expectedRequests: v1.ResourceList{
   430  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("5"),
   431  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("17"),
   432  			},
   433  			expectedLimits: v1.ResourceList{
   434  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
   435  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("34"),
   436  			},
   437  		},
   438  		{
   439  			name: "two container with overhead, massive init - should just be the largest init",
   440  			pod: &v1.Pod{
   441  				Spec: v1.PodSpec{
   442  					Overhead: v1.ResourceList{
   443  						v1.ResourceName(v1.ResourceCPU):    resource.MustParse("3"),
   444  						v1.ResourceName(v1.ResourceMemory): resource.MustParse("8"),
   445  					},
   446  					Containers: []v1.Container{
   447  						{
   448  							Name: "foobar",
   449  							Resources: v1.ResourceRequirements{
   450  								Requests: v1.ResourceList{
   451  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   452  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   453  								},
   454  								Limits: v1.ResourceList{
   455  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   456  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("10"),
   457  								},
   458  							},
   459  						},
   460  						{
   461  							Name: "foobar2",
   462  							Resources: v1.ResourceRequirements{
   463  								Requests: v1.ResourceList{
   464  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("4"),
   465  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("12"),
   466  								},
   467  								Limits: v1.ResourceList{
   468  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("8"),
   469  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("24"),
   470  								},
   471  							},
   472  						},
   473  					},
   474  					InitContainers: []v1.Container{
   475  						{
   476  							Name: "small-init",
   477  							Resources: v1.ResourceRequirements{
   478  								Requests: v1.ResourceList{
   479  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   480  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   481  								},
   482  								Limits: v1.ResourceList{
   483  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   484  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("5"),
   485  								},
   486  							},
   487  						},
   488  						{
   489  							Name: "big-init",
   490  							Resources: v1.ResourceRequirements{
   491  								Requests: v1.ResourceList{
   492  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("40"),
   493  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"),
   494  								},
   495  								Limits: v1.ResourceList{
   496  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("80"),
   497  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"),
   498  								},
   499  							},
   500  						},
   501  					},
   502  				},
   503  			},
   504  			expectedRequests: v1.ResourceList{
   505  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("40"),
   506  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("120"),
   507  			},
   508  			expectedLimits: v1.ResourceList{
   509  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("80"),
   510  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("240"),
   511  			},
   512  		},
   513  	}
   514  	for idx, tc := range cases {
   515  		resRequests := PodRequests(tc.pod, PodResourcesOptions{ExcludeOverhead: true})
   516  		resLimits := PodLimits(tc.pod, PodResourcesOptions{ExcludeOverhead: true})
   517  
   518  		if !equality.Semantic.DeepEqual(tc.expectedRequests, resRequests) {
   519  			t.Errorf("test case failure[%d]: %v, requests:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedRequests, resRequests)
   520  		}
   521  
   522  		if !equality.Semantic.DeepEqual(tc.expectedLimits, resLimits) {
   523  			t.Errorf("test case failure[%d]: %v, limits:\n expected:\t%v\ngot\t\t%v", idx, tc.name, tc.expectedLimits, resLimits)
   524  		}
   525  	}
   526  }
   527  
   528  type podResources struct {
   529  	cpuRequest, cpuLimit, memoryRequest, memoryLimit, cpuOverhead, memoryOverhead string
   530  }
   531  
   532  func getPod(cname string, resources podResources) *v1.Pod {
   533  	r := v1.ResourceRequirements{
   534  		Limits:   make(v1.ResourceList),
   535  		Requests: make(v1.ResourceList),
   536  	}
   537  
   538  	overhead := make(v1.ResourceList)
   539  
   540  	if resources.cpuLimit != "" {
   541  		r.Limits[v1.ResourceCPU] = resource.MustParse(resources.cpuLimit)
   542  	}
   543  	if resources.memoryLimit != "" {
   544  		r.Limits[v1.ResourceMemory] = resource.MustParse(resources.memoryLimit)
   545  	}
   546  	if resources.cpuRequest != "" {
   547  		r.Requests[v1.ResourceCPU] = resource.MustParse(resources.cpuRequest)
   548  	}
   549  	if resources.memoryRequest != "" {
   550  		r.Requests[v1.ResourceMemory] = resource.MustParse(resources.memoryRequest)
   551  	}
   552  	if resources.cpuOverhead != "" {
   553  		overhead[v1.ResourceCPU] = resource.MustParse(resources.cpuOverhead)
   554  	}
   555  	if resources.memoryOverhead != "" {
   556  		overhead[v1.ResourceMemory] = resource.MustParse(resources.memoryOverhead)
   557  	}
   558  
   559  	return &v1.Pod{
   560  		Spec: v1.PodSpec{
   561  			Containers: []v1.Container{
   562  				{
   563  					Name:      cname,
   564  					Resources: r,
   565  				},
   566  			},
   567  			InitContainers: []v1.Container{
   568  				{
   569  					Name:      "init-" + cname,
   570  					Resources: r,
   571  				},
   572  			},
   573  			Overhead: overhead,
   574  		},
   575  	}
   576  }
   577  
   578  func TestPodResourceRequests(t *testing.T) {
   579  	restartAlways := v1.ContainerRestartPolicyAlways
   580  	testCases := []struct {
   581  		description      string
   582  		options          PodResourcesOptions
   583  		overhead         v1.ResourceList
   584  		podResizeStatus  v1.PodResizeStatus
   585  		initContainers   []v1.Container
   586  		containers       []v1.Container
   587  		containerStatus  []v1.ContainerStatus
   588  		expectedRequests v1.ResourceList
   589  	}{
   590  		{
   591  			description: "nil options, larger init container",
   592  			expectedRequests: v1.ResourceList{
   593  				v1.ResourceCPU: resource.MustParse("4"),
   594  			},
   595  			initContainers: []v1.Container{
   596  				{
   597  					Resources: v1.ResourceRequirements{
   598  						Requests: v1.ResourceList{
   599  							v1.ResourceCPU: resource.MustParse("4"),
   600  						},
   601  					},
   602  				},
   603  			},
   604  			containers: []v1.Container{
   605  				{
   606  					Resources: v1.ResourceRequirements{
   607  						Requests: v1.ResourceList{
   608  							v1.ResourceCPU: resource.MustParse("1"),
   609  						},
   610  					},
   611  				},
   612  			},
   613  		},
   614  		{
   615  			description: "nil options, larger containers",
   616  			expectedRequests: v1.ResourceList{
   617  				v1.ResourceCPU: resource.MustParse("5"),
   618  			},
   619  			initContainers: []v1.Container{
   620  				{
   621  					Resources: v1.ResourceRequirements{
   622  						Requests: v1.ResourceList{
   623  							v1.ResourceCPU: resource.MustParse("2"),
   624  						},
   625  					},
   626  				},
   627  			},
   628  			containers: []v1.Container{
   629  				{
   630  					Resources: v1.ResourceRequirements{
   631  						Requests: v1.ResourceList{
   632  							v1.ResourceCPU: resource.MustParse("2"),
   633  						},
   634  					},
   635  				},
   636  				{
   637  					Resources: v1.ResourceRequirements{
   638  						Requests: v1.ResourceList{
   639  							v1.ResourceCPU: resource.MustParse("3"),
   640  						},
   641  					},
   642  				},
   643  			},
   644  		},
   645  		{
   646  			description: "pod overhead excluded",
   647  			expectedRequests: v1.ResourceList{
   648  				v1.ResourceCPU: resource.MustParse("5"),
   649  			},
   650  			options: PodResourcesOptions{
   651  				ExcludeOverhead: true,
   652  			},
   653  			overhead: v1.ResourceList{
   654  				v1.ResourceCPU: resource.MustParse("1"),
   655  			},
   656  			initContainers: []v1.Container{
   657  				{
   658  					Resources: v1.ResourceRequirements{
   659  						Requests: v1.ResourceList{
   660  							v1.ResourceCPU: resource.MustParse("2"),
   661  						},
   662  					},
   663  				},
   664  			},
   665  			containers: []v1.Container{
   666  				{
   667  					Resources: v1.ResourceRequirements{
   668  						Requests: v1.ResourceList{
   669  							v1.ResourceCPU: resource.MustParse("2"),
   670  						},
   671  					},
   672  				},
   673  				{
   674  					Resources: v1.ResourceRequirements{
   675  						Requests: v1.ResourceList{
   676  							v1.ResourceCPU: resource.MustParse("3"),
   677  						},
   678  					},
   679  				},
   680  			},
   681  		},
   682  		{
   683  			description: "pod overhead included",
   684  			expectedRequests: v1.ResourceList{
   685  				v1.ResourceCPU:    resource.MustParse("6"),
   686  				v1.ResourceMemory: resource.MustParse("1Gi"),
   687  			},
   688  			overhead: v1.ResourceList{
   689  				v1.ResourceCPU:    resource.MustParse("1"),
   690  				v1.ResourceMemory: resource.MustParse("1Gi"),
   691  			},
   692  			initContainers: []v1.Container{
   693  				{
   694  					Resources: v1.ResourceRequirements{
   695  						Requests: v1.ResourceList{
   696  							v1.ResourceCPU: resource.MustParse("2"),
   697  						},
   698  						Limits: v1.ResourceList{
   699  							v1.ResourceCPU: resource.MustParse("2"),
   700  						},
   701  					},
   702  				},
   703  			},
   704  			containers: []v1.Container{
   705  				{
   706  					Resources: v1.ResourceRequirements{
   707  						Requests: v1.ResourceList{
   708  							v1.ResourceCPU: resource.MustParse("2"),
   709  						},
   710  					},
   711  				},
   712  				{
   713  					Resources: v1.ResourceRequirements{
   714  						Requests: v1.ResourceList{
   715  							v1.ResourceCPU: resource.MustParse("3"),
   716  						},
   717  					},
   718  				},
   719  			},
   720  		},
   721  		{
   722  			description: "resized, infeasible",
   723  			expectedRequests: v1.ResourceList{
   724  				v1.ResourceCPU: resource.MustParse("2"),
   725  			},
   726  			podResizeStatus: v1.PodResizeStatusInfeasible,
   727  			options:         PodResourcesOptions{InPlacePodVerticalScalingEnabled: true},
   728  			containers: []v1.Container{
   729  				{
   730  					Name: "container-1",
   731  					Resources: v1.ResourceRequirements{
   732  						Requests: v1.ResourceList{
   733  							v1.ResourceCPU: resource.MustParse("4"),
   734  						},
   735  					},
   736  				},
   737  			},
   738  			containerStatus: []v1.ContainerStatus{
   739  				{
   740  					Name: "container-1",
   741  					AllocatedResources: v1.ResourceList{
   742  						v1.ResourceCPU: resource.MustParse("2"),
   743  					},
   744  				},
   745  			},
   746  		},
   747  		{
   748  			description: "resized, no resize status",
   749  			expectedRequests: v1.ResourceList{
   750  				v1.ResourceCPU: resource.MustParse("4"),
   751  			},
   752  			options: PodResourcesOptions{InPlacePodVerticalScalingEnabled: true},
   753  			containers: []v1.Container{
   754  				{
   755  					Name: "container-1",
   756  					Resources: v1.ResourceRequirements{
   757  						Requests: v1.ResourceList{
   758  							v1.ResourceCPU: resource.MustParse("4"),
   759  						},
   760  					},
   761  				},
   762  			},
   763  			containerStatus: []v1.ContainerStatus{
   764  				{
   765  					Name: "container-1",
   766  					AllocatedResources: v1.ResourceList{
   767  						v1.ResourceCPU: resource.MustParse("2"),
   768  					},
   769  				},
   770  			},
   771  		},
   772  		{
   773  			description: "resized, infeasible, feature gate disabled",
   774  			expectedRequests: v1.ResourceList{
   775  				v1.ResourceCPU: resource.MustParse("4"),
   776  			},
   777  			podResizeStatus: v1.PodResizeStatusInfeasible,
   778  			options:         PodResourcesOptions{InPlacePodVerticalScalingEnabled: false},
   779  			containers: []v1.Container{
   780  				{
   781  					Name: "container-1",
   782  					Resources: v1.ResourceRequirements{
   783  						Requests: v1.ResourceList{
   784  							v1.ResourceCPU: resource.MustParse("4"),
   785  						},
   786  					},
   787  				},
   788  			},
   789  			containerStatus: []v1.ContainerStatus{
   790  				{
   791  					Name: "container-1",
   792  					AllocatedResources: v1.ResourceList{
   793  						v1.ResourceCPU: resource.MustParse("2"),
   794  					},
   795  				},
   796  			},
   797  		},
   798  		{
   799  			description: "restartable init container",
   800  			expectedRequests: v1.ResourceList{
   801  				// restartable init + regular container
   802  				v1.ResourceCPU: resource.MustParse("2"),
   803  			},
   804  			initContainers: []v1.Container{
   805  				{
   806  					Name:          "restartable-init-1",
   807  					RestartPolicy: &restartAlways,
   808  					Resources: v1.ResourceRequirements{
   809  						Requests: v1.ResourceList{
   810  							v1.ResourceCPU: resource.MustParse("1"),
   811  						},
   812  					},
   813  				},
   814  			},
   815  			containers: []v1.Container{
   816  				{
   817  					Name: "container-1",
   818  					Resources: v1.ResourceRequirements{
   819  						Requests: v1.ResourceList{
   820  							v1.ResourceCPU: resource.MustParse("1"),
   821  						},
   822  					},
   823  				},
   824  			},
   825  		},
   826  		{
   827  			description: "multiple restartable init containers",
   828  			expectedRequests: v1.ResourceList{
   829  				// max(5, restartable init containers(3+2+1) + regular(1)) = 7
   830  				v1.ResourceCPU: resource.MustParse("7"),
   831  			},
   832  			initContainers: []v1.Container{
   833  				{
   834  					Name: "init-1",
   835  					Resources: v1.ResourceRequirements{
   836  						Requests: v1.ResourceList{
   837  							v1.ResourceCPU: resource.MustParse("5"),
   838  						},
   839  					},
   840  				},
   841  				{
   842  					Name:          "restartable-init-1",
   843  					RestartPolicy: &restartAlways,
   844  					Resources: v1.ResourceRequirements{
   845  						Requests: v1.ResourceList{
   846  							v1.ResourceCPU: resource.MustParse("1"),
   847  						},
   848  					},
   849  				},
   850  				{
   851  					Name:          "restartable-init-2",
   852  					RestartPolicy: &restartAlways,
   853  					Resources: v1.ResourceRequirements{
   854  						Requests: v1.ResourceList{
   855  							v1.ResourceCPU: resource.MustParse("2"),
   856  						},
   857  					},
   858  				},
   859  				{
   860  					Name:          "restartable-init-3",
   861  					RestartPolicy: &restartAlways,
   862  					Resources: v1.ResourceRequirements{
   863  						Requests: v1.ResourceList{
   864  							v1.ResourceCPU: resource.MustParse("3"),
   865  						},
   866  					},
   867  				},
   868  			},
   869  			containers: []v1.Container{
   870  				{
   871  					Name: "container-1",
   872  					Resources: v1.ResourceRequirements{
   873  						Requests: v1.ResourceList{
   874  							v1.ResourceCPU: resource.MustParse("1"),
   875  						},
   876  					},
   877  				},
   878  			},
   879  		},
   880  		{
   881  			description: "multiple restartable and regular init containers",
   882  			expectedRequests: v1.ResourceList{
   883  				// init-2 requires 5 + the previously running restartable init
   884  				// containers(1+2) = 8, the restartable init container that starts
   885  				// after it doesn't count
   886  				v1.ResourceCPU: resource.MustParse("8"),
   887  			},
   888  			initContainers: []v1.Container{
   889  				{
   890  					Name: "init-1",
   891  					Resources: v1.ResourceRequirements{
   892  						Requests: v1.ResourceList{
   893  							v1.ResourceCPU: resource.MustParse("5"),
   894  						},
   895  					},
   896  				},
   897  				{
   898  					Name:          "restartable-init-1",
   899  					RestartPolicy: &restartAlways,
   900  					Resources: v1.ResourceRequirements{
   901  						Requests: v1.ResourceList{
   902  							v1.ResourceCPU: resource.MustParse("1"),
   903  						},
   904  					},
   905  				},
   906  				{
   907  					Name:          "restartable-init-2",
   908  					RestartPolicy: &restartAlways,
   909  					Resources: v1.ResourceRequirements{
   910  						Requests: v1.ResourceList{
   911  							v1.ResourceCPU: resource.MustParse("2"),
   912  						},
   913  					},
   914  				},
   915  				{
   916  					Name: "init-2",
   917  					Resources: v1.ResourceRequirements{
   918  						Requests: v1.ResourceList{
   919  							v1.ResourceCPU: resource.MustParse("5"),
   920  						},
   921  					},
   922  				},
   923  				{
   924  					Name:          "restartable-init-3",
   925  					RestartPolicy: &restartAlways,
   926  					Resources: v1.ResourceRequirements{
   927  						Requests: v1.ResourceList{
   928  							v1.ResourceCPU: resource.MustParse("3"),
   929  						},
   930  					},
   931  				},
   932  			},
   933  			containers: []v1.Container{
   934  				{
   935  					Name: "container-1",
   936  					Resources: v1.ResourceRequirements{
   937  						Requests: v1.ResourceList{
   938  							v1.ResourceCPU: resource.MustParse("1"),
   939  						},
   940  					},
   941  				},
   942  			},
   943  		},
   944  		{
   945  			description: "restartable-init, init and regular",
   946  			expectedRequests: v1.ResourceList{
   947  				v1.ResourceCPU: resource.MustParse("210"),
   948  			},
   949  			initContainers: []v1.Container{
   950  				{
   951  					Name:          "restartable-init-1",
   952  					RestartPolicy: &restartAlways,
   953  					Resources: v1.ResourceRequirements{
   954  						Requests: v1.ResourceList{
   955  							v1.ResourceCPU: resource.MustParse("10"),
   956  						},
   957  					},
   958  				},
   959  				{
   960  					Name: "init-1",
   961  					Resources: v1.ResourceRequirements{
   962  						Requests: v1.ResourceList{
   963  							v1.ResourceCPU: resource.MustParse("200"),
   964  						},
   965  					},
   966  				},
   967  			},
   968  			containers: []v1.Container{
   969  				{
   970  					Name: "container-1",
   971  					Resources: v1.ResourceRequirements{
   972  						Requests: v1.ResourceList{
   973  							v1.ResourceCPU: resource.MustParse("100"),
   974  						},
   975  					},
   976  				},
   977  			},
   978  		},
   979  	}
   980  	for _, tc := range testCases {
   981  		t.Run(tc.description, func(t *testing.T) {
   982  			p := &v1.Pod{
   983  				Spec: v1.PodSpec{
   984  					Containers:     tc.containers,
   985  					InitContainers: tc.initContainers,
   986  					Overhead:       tc.overhead,
   987  				},
   988  				Status: v1.PodStatus{
   989  					ContainerStatuses: tc.containerStatus,
   990  					Resize:            tc.podResizeStatus,
   991  				},
   992  			}
   993  			request := PodRequests(p, tc.options)
   994  			if !resourcesEqual(tc.expectedRequests, request) {
   995  				t.Errorf("[%s] expected requests = %v, got %v", tc.description, tc.expectedRequests, request)
   996  			}
   997  		})
   998  	}
   999  }
  1000  
  1001  func TestPodResourceRequestsReuse(t *testing.T) {
  1002  	expectedRequests := v1.ResourceList{
  1003  		v1.ResourceCPU: resource.MustParse("1"),
  1004  	}
  1005  	p := &v1.Pod{
  1006  		Spec: v1.PodSpec{
  1007  			Containers: []v1.Container{
  1008  				{
  1009  					Resources: v1.ResourceRequirements{
  1010  						Requests: expectedRequests,
  1011  					},
  1012  				},
  1013  			},
  1014  		},
  1015  	}
  1016  
  1017  	opts := PodResourcesOptions{
  1018  		Reuse: v1.ResourceList{
  1019  			v1.ResourceCPU: resource.MustParse("25"),
  1020  		},
  1021  	}
  1022  	requests := PodRequests(p, opts)
  1023  
  1024  	if !resourcesEqual(expectedRequests, requests) {
  1025  		t.Errorf("expected requests = %v, got %v", expectedRequests, requests)
  1026  	}
  1027  
  1028  	// should re-use the maps we passed in
  1029  	if !resourcesEqual(expectedRequests, opts.Reuse) {
  1030  		t.Errorf("expected to re-use the requests")
  1031  	}
  1032  }
  1033  
  1034  func TestPodResourceLimits(t *testing.T) {
  1035  	restartAlways := v1.ContainerRestartPolicyAlways
  1036  	testCases := []struct {
  1037  		description    string
  1038  		options        PodResourcesOptions
  1039  		overhead       v1.ResourceList
  1040  		initContainers []v1.Container
  1041  		containers     []v1.Container
  1042  		expectedLimits v1.ResourceList
  1043  	}{
  1044  		{
  1045  			description: "nil options, larger init container",
  1046  			expectedLimits: v1.ResourceList{
  1047  				v1.ResourceCPU: resource.MustParse("4"),
  1048  			},
  1049  			initContainers: []v1.Container{
  1050  				{
  1051  					Resources: v1.ResourceRequirements{
  1052  						Limits: v1.ResourceList{
  1053  							v1.ResourceCPU: resource.MustParse("4"),
  1054  						},
  1055  					},
  1056  				},
  1057  			},
  1058  			containers: []v1.Container{
  1059  				{
  1060  					Resources: v1.ResourceRequirements{
  1061  						Limits: v1.ResourceList{
  1062  							v1.ResourceCPU: resource.MustParse("1"),
  1063  						},
  1064  					},
  1065  				},
  1066  			},
  1067  		},
  1068  		{
  1069  			description: "nil options, larger containers",
  1070  			expectedLimits: v1.ResourceList{
  1071  				v1.ResourceCPU: resource.MustParse("5"),
  1072  			},
  1073  			initContainers: []v1.Container{
  1074  				{
  1075  					Resources: v1.ResourceRequirements{
  1076  						Limits: v1.ResourceList{
  1077  							v1.ResourceCPU: resource.MustParse("2"),
  1078  						},
  1079  					},
  1080  				},
  1081  			},
  1082  			containers: []v1.Container{
  1083  				{
  1084  					Resources: v1.ResourceRequirements{
  1085  						Limits: v1.ResourceList{
  1086  							v1.ResourceCPU: resource.MustParse("2"),
  1087  						},
  1088  					},
  1089  				},
  1090  				{
  1091  					Resources: v1.ResourceRequirements{
  1092  						Limits: v1.ResourceList{
  1093  							v1.ResourceCPU: resource.MustParse("3"),
  1094  						},
  1095  					},
  1096  				},
  1097  			},
  1098  		},
  1099  		{
  1100  			description: "pod overhead excluded",
  1101  			expectedLimits: v1.ResourceList{
  1102  				v1.ResourceCPU: resource.MustParse("5"),
  1103  			},
  1104  			options: PodResourcesOptions{
  1105  				ExcludeOverhead: true,
  1106  			},
  1107  			overhead: v1.ResourceList{
  1108  				v1.ResourceCPU: resource.MustParse("1"),
  1109  			},
  1110  			initContainers: []v1.Container{
  1111  				{
  1112  					Resources: v1.ResourceRequirements{
  1113  						Limits: v1.ResourceList{
  1114  							v1.ResourceCPU: resource.MustParse("2"),
  1115  						},
  1116  					},
  1117  				},
  1118  			},
  1119  			containers: []v1.Container{
  1120  				{
  1121  					Resources: v1.ResourceRequirements{
  1122  						Limits: v1.ResourceList{
  1123  							v1.ResourceCPU: resource.MustParse("2"),
  1124  						},
  1125  					},
  1126  				},
  1127  				{
  1128  					Resources: v1.ResourceRequirements{
  1129  						Limits: v1.ResourceList{
  1130  							v1.ResourceCPU: resource.MustParse("3"),
  1131  						},
  1132  					},
  1133  				},
  1134  			},
  1135  		},
  1136  		{
  1137  			description: "pod overhead included",
  1138  			overhead: v1.ResourceList{
  1139  				v1.ResourceCPU:    resource.MustParse("1"),
  1140  				v1.ResourceMemory: resource.MustParse("1Gi"),
  1141  			},
  1142  			expectedLimits: v1.ResourceList{
  1143  				v1.ResourceCPU: resource.MustParse("6"),
  1144  				// overhead is only added to non-zero limits, so there will be no expected memory limit
  1145  			},
  1146  			initContainers: []v1.Container{
  1147  				{
  1148  					Resources: v1.ResourceRequirements{
  1149  						Limits: v1.ResourceList{
  1150  							v1.ResourceCPU: resource.MustParse("2"),
  1151  						},
  1152  					},
  1153  				},
  1154  			},
  1155  			containers: []v1.Container{
  1156  				{
  1157  					Resources: v1.ResourceRequirements{
  1158  						Limits: v1.ResourceList{
  1159  							v1.ResourceCPU: resource.MustParse("2"),
  1160  						},
  1161  					},
  1162  				},
  1163  				{
  1164  					Resources: v1.ResourceRequirements{
  1165  						Limits: v1.ResourceList{
  1166  							v1.ResourceCPU: resource.MustParse("3"),
  1167  						},
  1168  					},
  1169  				},
  1170  			},
  1171  		},
  1172  		{
  1173  			description:    "no limited containers should result in no limits for the pod",
  1174  			expectedLimits: v1.ResourceList{},
  1175  			initContainers: []v1.Container{},
  1176  			containers: []v1.Container{
  1177  				{
  1178  					// Unlimited container
  1179  				},
  1180  			},
  1181  		},
  1182  		{
  1183  			description: "one limited and one unlimited container should result in the limited container's limits for the pod",
  1184  			expectedLimits: v1.ResourceList{
  1185  				v1.ResourceCPU:    resource.MustParse("2"),
  1186  				v1.ResourceMemory: resource.MustParse("2Gi"),
  1187  			},
  1188  			initContainers: []v1.Container{},
  1189  			containers: []v1.Container{
  1190  				{
  1191  					Resources: v1.ResourceRequirements{
  1192  						Limits: v1.ResourceList{
  1193  							v1.ResourceCPU:    resource.MustParse("2"),
  1194  							v1.ResourceMemory: resource.MustParse("2Gi"),
  1195  						},
  1196  					},
  1197  				},
  1198  				{
  1199  					// Unlimited container
  1200  				},
  1201  			},
  1202  		},
  1203  		{
  1204  			description: "one limited and one unlimited init container should result in the limited init container's limits for the pod",
  1205  			expectedLimits: v1.ResourceList{
  1206  				v1.ResourceCPU:    resource.MustParse("2"),
  1207  				v1.ResourceMemory: resource.MustParse("2Gi"),
  1208  			},
  1209  			initContainers: []v1.Container{
  1210  				{
  1211  					Resources: v1.ResourceRequirements{
  1212  						Limits: v1.ResourceList{
  1213  							v1.ResourceCPU:    resource.MustParse("2"),
  1214  							v1.ResourceMemory: resource.MustParse("2Gi"),
  1215  						},
  1216  					},
  1217  				},
  1218  				{
  1219  					// Unlimited init container
  1220  				},
  1221  			},
  1222  			containers: []v1.Container{
  1223  				{
  1224  					Resources: v1.ResourceRequirements{
  1225  						Limits: v1.ResourceList{
  1226  							v1.ResourceCPU:    resource.MustParse("1"),
  1227  							v1.ResourceMemory: resource.MustParse("1Gi"),
  1228  						},
  1229  					},
  1230  				},
  1231  			},
  1232  		},
  1233  		{
  1234  			description: "restartable init container",
  1235  			expectedLimits: v1.ResourceList{
  1236  				// restartable init + regular container
  1237  				v1.ResourceCPU: resource.MustParse("2"),
  1238  			},
  1239  			initContainers: []v1.Container{
  1240  				{
  1241  					Name:          "restartable-init-1",
  1242  					RestartPolicy: &restartAlways,
  1243  					Resources: v1.ResourceRequirements{
  1244  						Limits: v1.ResourceList{
  1245  							v1.ResourceCPU: resource.MustParse("1"),
  1246  						},
  1247  					},
  1248  				},
  1249  			},
  1250  			containers: []v1.Container{
  1251  				{
  1252  					Name: "container-1",
  1253  					Resources: v1.ResourceRequirements{
  1254  						Limits: v1.ResourceList{
  1255  							v1.ResourceCPU: resource.MustParse("1"),
  1256  						},
  1257  					},
  1258  				},
  1259  			},
  1260  		},
  1261  		{
  1262  			description: "multiple restartable init containers",
  1263  			expectedLimits: v1.ResourceList{
  1264  				// max(5, restartable init containers(3+2+1) + regular(1)) = 7
  1265  				v1.ResourceCPU: resource.MustParse("7"),
  1266  			},
  1267  			initContainers: []v1.Container{
  1268  				{
  1269  					Name: "init-1",
  1270  					Resources: v1.ResourceRequirements{
  1271  						Limits: v1.ResourceList{
  1272  							v1.ResourceCPU: resource.MustParse("5"),
  1273  						},
  1274  					},
  1275  				},
  1276  				{
  1277  					Name:          "restartable-init-1",
  1278  					RestartPolicy: &restartAlways,
  1279  					Resources: v1.ResourceRequirements{
  1280  						Limits: v1.ResourceList{
  1281  							v1.ResourceCPU: resource.MustParse("1"),
  1282  						},
  1283  					},
  1284  				},
  1285  				{
  1286  					Name:          "restartable-init-2",
  1287  					RestartPolicy: &restartAlways,
  1288  					Resources: v1.ResourceRequirements{
  1289  						Limits: v1.ResourceList{
  1290  							v1.ResourceCPU: resource.MustParse("2"),
  1291  						},
  1292  					},
  1293  				},
  1294  				{
  1295  					Name:          "restartable-init-3",
  1296  					RestartPolicy: &restartAlways,
  1297  					Resources: v1.ResourceRequirements{
  1298  						Limits: v1.ResourceList{
  1299  							v1.ResourceCPU: resource.MustParse("3"),
  1300  						},
  1301  					},
  1302  				},
  1303  			},
  1304  			containers: []v1.Container{
  1305  				{
  1306  					Name: "container-1",
  1307  					Resources: v1.ResourceRequirements{
  1308  						Limits: v1.ResourceList{
  1309  							v1.ResourceCPU: resource.MustParse("1"),
  1310  						},
  1311  					},
  1312  				},
  1313  			},
  1314  		},
  1315  		{
  1316  			description: "multiple restartable and regular init containers",
  1317  			expectedLimits: v1.ResourceList{
  1318  				// init-2 requires 5 + the previously running restartable init
  1319  				// containers(1+2) = 8, the restartable init container that starts
  1320  				// after it doesn't count
  1321  				v1.ResourceCPU: resource.MustParse("8"),
  1322  			},
  1323  			initContainers: []v1.Container{
  1324  				{
  1325  					Name: "init-1",
  1326  					Resources: v1.ResourceRequirements{
  1327  						Limits: v1.ResourceList{
  1328  							v1.ResourceCPU: resource.MustParse("5"),
  1329  						},
  1330  					},
  1331  				},
  1332  				{
  1333  					Name:          "restartable-init-1",
  1334  					RestartPolicy: &restartAlways,
  1335  					Resources: v1.ResourceRequirements{
  1336  						Limits: v1.ResourceList{
  1337  							v1.ResourceCPU: resource.MustParse("1"),
  1338  						},
  1339  					},
  1340  				},
  1341  				{
  1342  					Name:          "restartable-init-2",
  1343  					RestartPolicy: &restartAlways,
  1344  					Resources: v1.ResourceRequirements{
  1345  						Limits: v1.ResourceList{
  1346  							v1.ResourceCPU: resource.MustParse("2"),
  1347  						},
  1348  					},
  1349  				},
  1350  				{
  1351  					Name: "init-2",
  1352  					Resources: v1.ResourceRequirements{
  1353  						Limits: v1.ResourceList{
  1354  							v1.ResourceCPU: resource.MustParse("5"),
  1355  						},
  1356  					},
  1357  				},
  1358  				{
  1359  					Name:          "restartable-init-3",
  1360  					RestartPolicy: &restartAlways,
  1361  					Resources: v1.ResourceRequirements{
  1362  						Limits: v1.ResourceList{
  1363  							v1.ResourceCPU: resource.MustParse("3"),
  1364  						},
  1365  					},
  1366  				},
  1367  			},
  1368  			containers: []v1.Container{
  1369  				{
  1370  					Name: "container-1",
  1371  					Resources: v1.ResourceRequirements{
  1372  						Limits: v1.ResourceList{
  1373  							v1.ResourceCPU: resource.MustParse("1"),
  1374  						},
  1375  					},
  1376  				},
  1377  			},
  1378  		},
  1379  		{
  1380  			description: "restartable-init, init and regular",
  1381  			expectedLimits: v1.ResourceList{
  1382  				v1.ResourceCPU: resource.MustParse("210"),
  1383  			},
  1384  			initContainers: []v1.Container{
  1385  				{
  1386  					Name:          "restartable-init-1",
  1387  					RestartPolicy: &restartAlways,
  1388  					Resources: v1.ResourceRequirements{
  1389  						Limits: v1.ResourceList{
  1390  							v1.ResourceCPU: resource.MustParse("10"),
  1391  						},
  1392  					},
  1393  				},
  1394  				{
  1395  					Name: "init-1",
  1396  					Resources: v1.ResourceRequirements{
  1397  						Limits: v1.ResourceList{
  1398  							v1.ResourceCPU: resource.MustParse("200"),
  1399  						},
  1400  					},
  1401  				},
  1402  			},
  1403  			containers: []v1.Container{
  1404  				{
  1405  					Name: "container-1",
  1406  					Resources: v1.ResourceRequirements{
  1407  						Limits: v1.ResourceList{
  1408  							v1.ResourceCPU: resource.MustParse("100"),
  1409  						},
  1410  					},
  1411  				},
  1412  			},
  1413  		},
  1414  	}
  1415  	for _, tc := range testCases {
  1416  		t.Run(tc.description, func(t *testing.T) {
  1417  			p := &v1.Pod{
  1418  				Spec: v1.PodSpec{
  1419  					Containers:     tc.containers,
  1420  					InitContainers: tc.initContainers,
  1421  					Overhead:       tc.overhead,
  1422  				},
  1423  			}
  1424  			limits := PodLimits(p, tc.options)
  1425  			if !resourcesEqual(tc.expectedLimits, limits) {
  1426  				t.Errorf("[%s] expected limits = %v, got %v", tc.description, tc.expectedLimits, limits)
  1427  			}
  1428  		})
  1429  	}
  1430  }
  1431  
  1432  func resourcesEqual(lhs, rhs v1.ResourceList) bool {
  1433  	if len(lhs) != len(rhs) {
  1434  		return false
  1435  	}
  1436  	for name, lhsv := range lhs {
  1437  		rhsv, ok := rhs[name]
  1438  		if !ok {
  1439  			return false
  1440  		}
  1441  		if !lhsv.Equal(rhsv) {
  1442  			return false
  1443  		}
  1444  	}
  1445  	return true
  1446  }