...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/limitranger

     1  /*
     2  Copyright 2014 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 limitranger
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strconv"
    23  	"sync"
    24  	"sync/atomic"
    25  	"testing"
    26  	"time"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/util/wait"
    34  	"k8s.io/apiserver/pkg/admission"
    35  	genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
    36  	admissiontesting "k8s.io/apiserver/pkg/admission/testing"
    37  	"k8s.io/client-go/informers"
    38  	clientset "k8s.io/client-go/kubernetes"
    39  	"k8s.io/client-go/kubernetes/fake"
    40  	core "k8s.io/client-go/testing"
    41  
    42  	api "k8s.io/kubernetes/pkg/apis/core"
    43  	v1 "k8s.io/kubernetes/pkg/apis/core/v1"
    44  )
    45  
    46  func getComputeResourceList(cpu, memory string) api.ResourceList {
    47  	res := api.ResourceList{}
    48  	if cpu != "" {
    49  		res[api.ResourceCPU] = resource.MustParse(cpu)
    50  	}
    51  	if memory != "" {
    52  		res[api.ResourceMemory] = resource.MustParse(memory)
    53  	}
    54  	return res
    55  }
    56  
    57  func getStorageResourceList(storage string) api.ResourceList {
    58  	res := api.ResourceList{}
    59  	if storage != "" {
    60  		res[api.ResourceStorage] = resource.MustParse(storage)
    61  	}
    62  	return res
    63  }
    64  
    65  func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
    66  	res := api.ResourceRequirements{}
    67  	res.Requests = requests
    68  	res.Limits = limits
    69  	return res
    70  }
    71  
    72  func getVolumeResourceRequirements(requests, limits api.ResourceList) api.VolumeResourceRequirements {
    73  	res := api.VolumeResourceRequirements{}
    74  	res.Requests = requests
    75  	res.Limits = limits
    76  	return res
    77  }
    78  
    79  // createLimitRange creates a limit range with the specified data
    80  func createLimitRange(limitType api.LimitType, min, max, defaultLimit, defaultRequest, maxLimitRequestRatio api.ResourceList) corev1.LimitRange {
    81  	internalLimitRage := api.LimitRange{
    82  		ObjectMeta: metav1.ObjectMeta{
    83  			Name:      "abc",
    84  			Namespace: "test",
    85  		},
    86  		Spec: api.LimitRangeSpec{
    87  			Limits: []api.LimitRangeItem{
    88  				{
    89  					Type:                 limitType,
    90  					Min:                  min,
    91  					Max:                  max,
    92  					Default:              defaultLimit,
    93  					DefaultRequest:       defaultRequest,
    94  					MaxLimitRequestRatio: maxLimitRequestRatio,
    95  				},
    96  			},
    97  		},
    98  	}
    99  	externalLimitRange := corev1.LimitRange{}
   100  	v1.Convert_core_LimitRange_To_v1_LimitRange(&internalLimitRage, &externalLimitRange, nil)
   101  	return externalLimitRange
   102  }
   103  
   104  func validLimitRange() corev1.LimitRange {
   105  	internalLimitRange := api.LimitRange{
   106  		ObjectMeta: metav1.ObjectMeta{
   107  			Name:      "abc",
   108  			Namespace: "test",
   109  		},
   110  		Spec: api.LimitRangeSpec{
   111  			Limits: []api.LimitRangeItem{
   112  				{
   113  					Type: api.LimitTypePod,
   114  					Max:  getComputeResourceList("200m", "4Gi"),
   115  					Min:  getComputeResourceList("50m", "2Mi"),
   116  				},
   117  				{
   118  					Type:           api.LimitTypeContainer,
   119  					Max:            getComputeResourceList("100m", "2Gi"),
   120  					Min:            getComputeResourceList("25m", "1Mi"),
   121  					Default:        getComputeResourceList("75m", "10Mi"),
   122  					DefaultRequest: getComputeResourceList("50m", "5Mi"),
   123  				},
   124  			},
   125  		},
   126  	}
   127  	externalLimitRange := corev1.LimitRange{}
   128  	v1.Convert_core_LimitRange_To_v1_LimitRange(&internalLimitRange, &externalLimitRange, nil)
   129  	return externalLimitRange
   130  }
   131  
   132  func validLimitRangeNoDefaults() corev1.LimitRange {
   133  	internalLimitRange := api.LimitRange{
   134  		ObjectMeta: metav1.ObjectMeta{
   135  			Name:      "abc",
   136  			Namespace: "test",
   137  		},
   138  		Spec: api.LimitRangeSpec{
   139  			Limits: []api.LimitRangeItem{
   140  				{
   141  					Type: api.LimitTypePod,
   142  					Max:  getComputeResourceList("200m", "4Gi"),
   143  					Min:  getComputeResourceList("50m", "2Mi"),
   144  				},
   145  				{
   146  					Type: api.LimitTypeContainer,
   147  					Max:  getComputeResourceList("100m", "2Gi"),
   148  					Min:  getComputeResourceList("25m", "1Mi"),
   149  				},
   150  			},
   151  		},
   152  	}
   153  	externalLimitRange := corev1.LimitRange{}
   154  	v1.Convert_core_LimitRange_To_v1_LimitRange(&internalLimitRange, &externalLimitRange, nil)
   155  	return externalLimitRange
   156  }
   157  
   158  func validPod(name string, numContainers int, resources api.ResourceRequirements) api.Pod {
   159  	pod := api.Pod{
   160  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
   161  		Spec:       api.PodSpec{},
   162  	}
   163  	pod.Spec.Containers = make([]api.Container, 0, numContainers)
   164  	for i := 0; i < numContainers; i++ {
   165  		pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
   166  			Image:     "foo:V" + strconv.Itoa(i),
   167  			Resources: resources,
   168  			Name:      "foo-" + strconv.Itoa(i),
   169  		})
   170  	}
   171  	return pod
   172  }
   173  
   174  func validPodInit(pod api.Pod, resources ...api.ResourceRequirements) api.Pod {
   175  	for i := 0; i < len(resources); i++ {
   176  		pod.Spec.InitContainers = append(pod.Spec.InitContainers, api.Container{
   177  			Image:     "foo:V" + strconv.Itoa(i),
   178  			Resources: resources[i],
   179  			Name:      "foo-" + strconv.Itoa(i),
   180  		})
   181  	}
   182  	return pod
   183  }
   184  
   185  func TestDefaultContainerResourceRequirements(t *testing.T) {
   186  	limitRange := validLimitRange()
   187  	expected := api.ResourceRequirements{
   188  		Requests: getComputeResourceList("50m", "5Mi"),
   189  		Limits:   getComputeResourceList("75m", "10Mi"),
   190  	}
   191  
   192  	actual := defaultContainerResourceRequirements(&limitRange)
   193  	if !apiequality.Semantic.DeepEqual(expected, actual) {
   194  		t.Errorf("actual.Limits != expected.Limits; %v != %v", actual.Limits, expected.Limits)
   195  		t.Errorf("actual.Requests != expected.Requests; %v != %v", actual.Requests, expected.Requests)
   196  		t.Errorf("expected != actual; %v != %v", expected, actual)
   197  	}
   198  }
   199  
   200  func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
   201  	a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]
   202  	if !ok {
   203  		t.Errorf("No annotation but expected %v", expected)
   204  	}
   205  	if a != expected {
   206  		t.Errorf("Wrong annotation set by Limit Ranger: got %v, expected %v", a, expected)
   207  	}
   208  }
   209  
   210  func expectNoAnnotation(t *testing.T, pod *api.Pod) {
   211  	if a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]; ok {
   212  		t.Errorf("Expected no annotation but got %v", a)
   213  	}
   214  }
   215  
   216  func TestMergePodResourceRequirements(t *testing.T) {
   217  	limitRange := validLimitRange()
   218  
   219  	// pod with no resources enumerated should get each resource from default request
   220  	expected := getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))
   221  	pod := validPod("empty-resources", 1, expected)
   222  	defaultRequirements := defaultContainerResourceRequirements(&limitRange)
   223  	mergePodResourceRequirements(&pod, &defaultRequirements)
   224  	for i := range pod.Spec.Containers {
   225  		actual := pod.Spec.Containers[i].Resources
   226  		if !apiequality.Semantic.DeepEqual(expected, actual) {
   227  			t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
   228  		}
   229  	}
   230  	verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu, memory request for container foo-0; cpu, memory limit for container foo-0")
   231  
   232  	// pod with some resources enumerated should only merge empty
   233  	input := getResourceRequirements(getComputeResourceList("", "512Mi"), getComputeResourceList("", ""))
   234  	pod = validPodInit(validPod("limit-memory", 1, input), input)
   235  	expected = api.ResourceRequirements{
   236  		Requests: api.ResourceList{
   237  			api.ResourceCPU:    defaultRequirements.Requests[api.ResourceCPU],
   238  			api.ResourceMemory: resource.MustParse("512Mi"),
   239  		},
   240  		Limits: defaultRequirements.Limits,
   241  	}
   242  	mergePodResourceRequirements(&pod, &defaultRequirements)
   243  	for i := range pod.Spec.Containers {
   244  		actual := pod.Spec.Containers[i].Resources
   245  		if !apiequality.Semantic.DeepEqual(expected, actual) {
   246  			t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
   247  		}
   248  	}
   249  	for i := range pod.Spec.InitContainers {
   250  		actual := pod.Spec.InitContainers[i].Resources
   251  		if !apiequality.Semantic.DeepEqual(expected, actual) {
   252  			t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
   253  		}
   254  	}
   255  	verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu request for container foo-0; cpu, memory limit for container foo-0")
   256  
   257  	// pod with all resources enumerated should not merge anything
   258  	input = getResourceRequirements(getComputeResourceList("100m", "512Mi"), getComputeResourceList("200m", "1G"))
   259  	initInputs := []api.ResourceRequirements{getResourceRequirements(getComputeResourceList("200m", "1G"), getComputeResourceList("400m", "2G"))}
   260  	pod = validPodInit(validPod("limit-memory", 1, input), initInputs...)
   261  	expected = input
   262  	mergePodResourceRequirements(&pod, &defaultRequirements)
   263  	for i := range pod.Spec.Containers {
   264  		actual := pod.Spec.Containers[i].Resources
   265  		if !apiequality.Semantic.DeepEqual(expected, actual) {
   266  			t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
   267  		}
   268  	}
   269  	for i := range pod.Spec.InitContainers {
   270  		actual := pod.Spec.InitContainers[i].Resources
   271  		if !apiequality.Semantic.DeepEqual(initInputs[i], actual) {
   272  			t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, initInputs[i], actual)
   273  		}
   274  	}
   275  	expectNoAnnotation(t, &pod)
   276  }
   277  
   278  func TestPodLimitFunc(t *testing.T) {
   279  	type testCase struct {
   280  		pod        api.Pod
   281  		limitRange corev1.LimitRange
   282  	}
   283  
   284  	successCases := []testCase{
   285  		{
   286  			pod:        validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("", ""))),
   287  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   288  		},
   289  		{
   290  			pod:        validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("200m", ""))),
   291  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   292  		},
   293  		{
   294  			pod:        validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
   295  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   296  		},
   297  		{
   298  			pod:        validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
   299  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   300  		},
   301  		{
   302  			pod:        validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
   303  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   304  		},
   305  		{
   306  			pod:        validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
   307  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   308  		},
   309  		{
   310  			pod:        validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
   311  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   312  		},
   313  		{
   314  			pod:        validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("750m", ""))),
   315  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1.5", "")),
   316  		},
   317  		{
   318  			pod:        validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
   319  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   320  		},
   321  		{
   322  			pod:        validPod("pod-min-cpu-request", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
   323  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   324  		},
   325  		{
   326  			pod:        validPod("pod-min-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
   327  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   328  		},
   329  		{
   330  			pod:        validPod("pod-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
   331  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   332  		},
   333  		{
   334  			pod:        validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
   335  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   336  		},
   337  		{
   338  			pod: validPodInit(
   339  				validPod("pod-init-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
   340  				getResourceRequirements(getComputeResourceList("", "100Mi"), getComputeResourceList("", "")),
   341  			),
   342  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   343  		},
   344  		{
   345  			pod: validPodInit(
   346  				validPod("pod-init-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
   347  				getResourceRequirements(getComputeResourceList("", "80Mi"), getComputeResourceList("", "100Mi")),
   348  			),
   349  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   350  		},
   351  		{
   352  			pod:        validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
   353  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   354  		},
   355  		{
   356  			pod:        validPod("pod-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
   357  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   358  		},
   359  		{
   360  			pod: validPodInit(
   361  				validPod("pod-init-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
   362  				getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("2", "")),
   363  				getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("1", "")),
   364  			),
   365  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   366  		},
   367  		{
   368  			pod: validPodInit(
   369  				validPod("pod-init-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
   370  				getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
   371  				getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
   372  			),
   373  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   374  		},
   375  		{
   376  			pod:        validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
   377  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   378  		},
   379  		{
   380  			pod:        validPod("pod-max-mem-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
   381  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   382  		},
   383  		{
   384  			pod:        validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "300Mi"), getComputeResourceList("", "450Mi"))),
   385  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
   386  		},
   387  		{
   388  			pod:        validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
   389  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   390  		},
   391  		{
   392  			pod:        validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
   393  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   394  		},
   395  		{
   396  			pod:        validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
   397  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   398  		},
   399  		{
   400  			pod:        validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
   401  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   402  		},
   403  		{
   404  			pod:        validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
   405  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   406  		},
   407  		{
   408  			pod:        validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
   409  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   410  		},
   411  		{
   412  			pod:        validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
   413  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   414  		},
   415  		{
   416  			pod:        validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
   417  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   418  		},
   419  		{
   420  			pod:        validPod("pod-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
   421  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   422  		},
   423  		{
   424  			pod:        validPod("pod-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
   425  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   426  		},
   427  		{
   428  			pod: validPodInit(
   429  				validPod("pod-init-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
   430  				getResourceRequirements(getLocalStorageResourceList("100Mi"), getLocalStorageResourceList("")),
   431  			),
   432  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   433  		},
   434  		{
   435  			pod: validPodInit(
   436  				validPod("pod-init-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
   437  				getResourceRequirements(getLocalStorageResourceList("80Mi"), getLocalStorageResourceList("100Mi")),
   438  			),
   439  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   440  		},
   441  		{
   442  			pod:        validPod("pod-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
   443  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   444  		},
   445  		{
   446  			pod:        validPod("pod-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
   447  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   448  		},
   449  		{
   450  			pod:        validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("300Mi"), getLocalStorageResourceList("450Mi"))),
   451  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
   452  		},
   453  	}
   454  	for i := range successCases {
   455  		test := successCases[i]
   456  		err := PodMutateLimitFunc(&test.limitRange, &test.pod)
   457  		if err != nil {
   458  			t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
   459  		}
   460  		err = PodValidateLimitFunc(&test.limitRange, &test.pod)
   461  		if err != nil {
   462  			t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
   463  		}
   464  	}
   465  
   466  	errorCases := []testCase{
   467  		{
   468  			pod:        validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("", ""))),
   469  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   470  		},
   471  		{
   472  			pod:        validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("200m", ""))),
   473  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   474  		},
   475  		{
   476  			pod:        validPod("ctr-min-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
   477  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   478  		},
   479  		{
   480  			pod:        validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", ""))),
   481  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   482  		},
   483  		{
   484  			pod:        validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", "100Mi"))),
   485  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   486  		},
   487  		{
   488  			pod:        validPod("ctr-min-memory-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
   489  			limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   490  		},
   491  		{
   492  			pod:        validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("2500m", ""))),
   493  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   494  		},
   495  		{
   496  			pod:        validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2500m", ""))),
   497  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   498  		},
   499  		{
   500  			pod:        validPod("ctr-max-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
   501  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   502  		},
   503  		{
   504  			pod:        validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("1250m", ""), getComputeResourceList("2500m", ""))),
   505  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1", "")),
   506  		},
   507  		{
   508  			pod:        validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "2Gi"))),
   509  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   510  		},
   511  		{
   512  			pod:        validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "2Gi"))),
   513  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   514  		},
   515  		{
   516  			pod:        validPod("ctr-max-mem-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
   517  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   518  		},
   519  		{
   520  			pod:        validPod("pod-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
   521  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   522  		},
   523  		{
   524  			pod:        validPod("pod-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
   525  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   526  		},
   527  		{
   528  			pod:        validPod("pod-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
   529  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   530  		},
   531  		{
   532  			pod:        validPod("pod-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
   533  			limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   534  		},
   535  		{
   536  			pod:        validPod("pod-max-cpu-request-limit", 3, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
   537  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   538  		},
   539  		{
   540  			pod:        validPod("pod-max-cpu-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
   541  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   542  		},
   543  		{
   544  			pod:        validPod("pod-max-mem-request-limit", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
   545  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   546  		},
   547  		{
   548  			pod:        validPod("pod-max-mem-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
   549  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   550  		},
   551  		{
   552  			pod: validPodInit(
   553  				validPod("pod-init-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
   554  				getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "1.5Gi")),
   555  			),
   556  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   557  		},
   558  		{
   559  			pod:        validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
   560  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
   561  		},
   562  		{
   563  			pod:        validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
   564  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   565  		},
   566  		{
   567  			pod:        validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
   568  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   569  		},
   570  		{
   571  			pod:        validPod("ctr-1-min-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
   572  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   573  		},
   574  		{
   575  			pod:        validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
   576  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   577  		},
   578  		{
   579  			pod:        validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
   580  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   581  		},
   582  		{
   583  			pod:        validPod("ctr-1-max-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
   584  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   585  		},
   586  		{
   587  			pod:        validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
   588  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   589  		},
   590  		{
   591  			pod:        validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
   592  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   593  		},
   594  		{
   595  			pod:        validPod("ctr-2-min-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
   596  			limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   597  		},
   598  		{
   599  			pod:        validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
   600  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   601  		},
   602  		{
   603  			pod:        validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
   604  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   605  		},
   606  		{
   607  			pod:        validPod("ctr-2-max-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
   608  			limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   609  		},
   610  		{
   611  			pod:        validPod("pod-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
   612  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   613  		},
   614  		{
   615  			pod:        validPod("pod-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
   616  			limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   617  		},
   618  		{
   619  			pod:        validPod("pod-max-local-ephemeral-storage-request-limit", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
   620  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   621  		},
   622  		{
   623  			pod:        validPod("pod-max-local-ephemeral-storage-limit", 3, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
   624  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   625  		},
   626  		{
   627  			pod: validPodInit(
   628  				validPod("pod-init-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
   629  				getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("1.5Gi")),
   630  			),
   631  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   632  		},
   633  		{
   634  			pod:        validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
   635  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
   636  		},
   637  		{
   638  			pod: withRestartableInitContainer(getComputeResourceList("1500m", ""), api.ResourceList{},
   639  				validPod("ctr-max-cpu-limit-restartable-init-container", 1, getResourceRequirements(getComputeResourceList("1000m", ""), getComputeResourceList("1500m", "")))),
   640  			limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   641  		},
   642  	}
   643  	for i := range errorCases {
   644  		test := errorCases[i]
   645  		err := PodMutateLimitFunc(&test.limitRange, &test.pod)
   646  		if err != nil {
   647  			t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
   648  		}
   649  		err = PodValidateLimitFunc(&test.limitRange, &test.pod)
   650  		if err == nil {
   651  			t.Errorf("Expected error for pod: %s", test.pod.Name)
   652  		}
   653  	}
   654  }
   655  
   656  func withRestartableInitContainer(requests, limits api.ResourceList, pod api.Pod) api.Pod {
   657  	policyAlways := api.ContainerRestartPolicyAlways
   658  	pod.Spec.InitContainers = append(pod.Spec.InitContainers,
   659  		api.Container{
   660  			RestartPolicy: &policyAlways,
   661  			Image:         "foo:V" + strconv.Itoa(len(pod.Spec.InitContainers)),
   662  			Resources:     getResourceRequirements(requests, limits),
   663  			Name:          "foo-" + strconv.Itoa(len(pod.Spec.InitContainers)),
   664  		})
   665  	return pod
   666  }
   667  
   668  func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList {
   669  	res := api.ResourceList{}
   670  	if ephemeralStorage != "" {
   671  		res[api.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
   672  	}
   673  	return res
   674  }
   675  
   676  func TestPodLimitFuncApplyDefault(t *testing.T) {
   677  	limitRange := validLimitRange()
   678  	testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{}))
   679  	err := PodMutateLimitFunc(&limitRange, &testPod)
   680  	if err != nil {
   681  		t.Errorf("Unexpected error for valid pod: %s, %v", testPod.Name, err)
   682  	}
   683  
   684  	for i := range testPod.Spec.Containers {
   685  		container := testPod.Spec.Containers[i]
   686  		limitMemory := container.Resources.Limits.Memory().String()
   687  		limitCPU := container.Resources.Limits.CPU().String()
   688  		requestMemory := container.Resources.Requests.Memory().String()
   689  		requestCPU := container.Resources.Requests.CPU().String()
   690  
   691  		if limitMemory != "10Mi" {
   692  			t.Errorf("Unexpected limit memory value %s", limitMemory)
   693  		}
   694  		if limitCPU != "75m" {
   695  			t.Errorf("Unexpected limit cpu value %s", limitCPU)
   696  		}
   697  		if requestMemory != "5Mi" {
   698  			t.Errorf("Unexpected request memory value %s", requestMemory)
   699  		}
   700  		if requestCPU != "50m" {
   701  			t.Errorf("Unexpected request cpu value %s", requestCPU)
   702  		}
   703  	}
   704  
   705  	for i := range testPod.Spec.InitContainers {
   706  		container := testPod.Spec.InitContainers[i]
   707  		limitMemory := container.Resources.Limits.Memory().String()
   708  		limitCPU := container.Resources.Limits.CPU().String()
   709  		requestMemory := container.Resources.Requests.Memory().String()
   710  		requestCPU := container.Resources.Requests.CPU().String()
   711  
   712  		if limitMemory != "10Mi" {
   713  			t.Errorf("Unexpected limit memory value %s", limitMemory)
   714  		}
   715  		if limitCPU != "75m" {
   716  			t.Errorf("Unexpected limit cpu value %s", limitCPU)
   717  		}
   718  		if requestMemory != "5Mi" {
   719  			t.Errorf("Unexpected request memory value %s", requestMemory)
   720  		}
   721  		if requestCPU != "50m" {
   722  			t.Errorf("Unexpected request cpu value %s", requestCPU)
   723  		}
   724  	}
   725  }
   726  
   727  func TestLimitRangerIgnoresSubresource(t *testing.T) {
   728  	limitRange := validLimitRangeNoDefaults()
   729  	mockClient := newMockClientForTest([]corev1.LimitRange{limitRange})
   730  	handler, informerFactory, err := newHandlerForTest(mockClient)
   731  	if err != nil {
   732  		t.Errorf("unexpected error initializing handler: %v", err)
   733  	}
   734  	informerFactory.Start(wait.NeverStop)
   735  
   736  	testPod := validPod("testPod", 1, api.ResourceRequirements{})
   737  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   738  	if err != nil {
   739  		t.Fatal(err)
   740  	}
   741  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   742  	if err == nil {
   743  		t.Errorf("Expected an error since the pod did not specify resource limits in its create call")
   744  	}
   745  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   746  	if err != nil {
   747  		t.Errorf("Expected not to call limitranger actions on pod updates")
   748  	}
   749  
   750  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   751  	if err != nil {
   752  		t.Errorf("Should have ignored calls to any subresource of pod %v", err)
   753  	}
   754  
   755  }
   756  
   757  func TestLimitRangerAdmitPod(t *testing.T) {
   758  	limitRange := validLimitRangeNoDefaults()
   759  	mockClient := newMockClientForTest([]corev1.LimitRange{limitRange})
   760  	handler, informerFactory, err := newHandlerForTest(mockClient)
   761  	if err != nil {
   762  		t.Errorf("unexpected error initializing handler: %v", err)
   763  	}
   764  	informerFactory.Start(wait.NeverStop)
   765  
   766  	testPod := validPod("testPod", 1, api.ResourceRequirements{})
   767  	err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   768  	if err != nil {
   769  		t.Fatal(err)
   770  	}
   771  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
   772  	if err == nil {
   773  		t.Errorf("Expected an error since the pod did not specify resource limits in its create call")
   774  	}
   775  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   776  	if err != nil {
   777  		t.Errorf("Expected not to call limitranger actions on pod updates")
   778  	}
   779  
   780  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   781  	if err != nil {
   782  		t.Errorf("Should have ignored calls to any subresource of pod %v", err)
   783  	}
   784  
   785  	// a pod that is undergoing termination should never be blocked
   786  	terminatingPod := validPod("terminatingPod", 1, api.ResourceRequirements{})
   787  	now := metav1.Now()
   788  	terminatingPod.DeletionTimestamp = &now
   789  	err = handler.Validate(context.TODO(), admission.NewAttributesRecord(&terminatingPod, &terminatingPod, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "terminatingPod", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil)
   790  	if err != nil {
   791  		t.Errorf("LimitRange should ignore a pod marked for termination")
   792  	}
   793  }
   794  
   795  // newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges
   796  func newMockClientForTest(limitRanges []corev1.LimitRange) *fake.Clientset {
   797  	mockClient := &fake.Clientset{}
   798  	mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
   799  		limitRangeList := &corev1.LimitRangeList{
   800  			ListMeta: metav1.ListMeta{
   801  				ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
   802  			},
   803  		}
   804  		for index, value := range limitRanges {
   805  			value.ResourceVersion = fmt.Sprintf("%d", index)
   806  			limitRangeList.Items = append(limitRangeList.Items, value)
   807  		}
   808  		return true, limitRangeList, nil
   809  	})
   810  	return mockClient
   811  }
   812  
   813  // newHandlerForTest returns a handler configured for testing.
   814  func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInformerFactory, error) {
   815  	f := informers.NewSharedInformerFactory(c, 5*time.Minute)
   816  	handler, err := NewLimitRanger(&DefaultLimitRangerActions{})
   817  	if err != nil {
   818  		return nil, f, err
   819  	}
   820  	pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil)
   821  	pluginInitializer.Initialize(handler)
   822  	err = admission.ValidateInitialization(handler)
   823  	return handler, f, err
   824  }
   825  
   826  func validPersistentVolumeClaim(name string, resources api.VolumeResourceRequirements) api.PersistentVolumeClaim {
   827  	pvc := api.PersistentVolumeClaim{
   828  		ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
   829  		Spec: api.PersistentVolumeClaimSpec{
   830  			Resources: resources,
   831  		},
   832  	}
   833  	return pvc
   834  }
   835  
   836  func TestPersistentVolumeClaimLimitFunc(t *testing.T) {
   837  	type testCase struct {
   838  		pvc        api.PersistentVolumeClaim
   839  		limitRange corev1.LimitRange
   840  	}
   841  
   842  	successCases := []testCase{
   843  		{
   844  			pvc:        validPersistentVolumeClaim("pvc-is-min-storage-request", getVolumeResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
   845  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   846  		},
   847  		{
   848  			pvc:        validPersistentVolumeClaim("pvc-is-max-storage-request", getVolumeResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
   849  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, api.ResourceList{}, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   850  		},
   851  		{
   852  			pvc:        validPersistentVolumeClaim("pvc-no-minmax-storage-request", getVolumeResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
   853  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList(""), getStorageResourceList(""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   854  		},
   855  		{
   856  			pvc:        validPersistentVolumeClaim("pvc-within-minmax-storage-request", getVolumeResourceRequirements(getStorageResourceList("5Gi"), getStorageResourceList(""))),
   857  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("10Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   858  		},
   859  	}
   860  	for i := range successCases {
   861  		test := successCases[i]
   862  		err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
   863  		if err != nil {
   864  			t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err)
   865  		}
   866  	}
   867  
   868  	errorCases := []testCase{
   869  		{
   870  			pvc:        validPersistentVolumeClaim("pvc-below-min-storage-request", getVolumeResourceRequirements(getStorageResourceList("500Mi"), getStorageResourceList(""))),
   871  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   872  		},
   873  		{
   874  			pvc:        validPersistentVolumeClaim("pvc-exceeds-max-storage-request", getVolumeResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
   875  			limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
   876  		},
   877  	}
   878  	for i := range errorCases {
   879  		test := errorCases[i]
   880  		err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
   881  		if err == nil {
   882  			t.Errorf("Expected error for pvc: %s", test.pvc.Name)
   883  		}
   884  	}
   885  }
   886  
   887  // TestLimitRanger_GetLimitRangesFixed22422 Fixed Admission controllers can cause unnecessary significant load on apiserver #22422
   888  func TestLimitRanger_GetLimitRangesFixed22422(t *testing.T) {
   889  	limitRange := validLimitRangeNoDefaults()
   890  	limitRanges := []corev1.LimitRange{limitRange}
   891  
   892  	mockClient := &fake.Clientset{}
   893  
   894  	var (
   895  		testCount  int64
   896  		test1Count int64
   897  	)
   898  	mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
   899  		switch action.GetNamespace() {
   900  		case "test":
   901  			atomic.AddInt64(&testCount, 1)
   902  		case "test1":
   903  			atomic.AddInt64(&test1Count, 1)
   904  		default:
   905  			t.Error("unexpected namespace")
   906  		}
   907  
   908  		limitRangeList := &corev1.LimitRangeList{
   909  			ListMeta: metav1.ListMeta{
   910  				ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
   911  			},
   912  		}
   913  		for index, value := range limitRanges {
   914  			value.ResourceVersion = fmt.Sprintf("%d", index)
   915  			value.Namespace = action.GetNamespace()
   916  			limitRangeList.Items = append(limitRangeList.Items, value)
   917  		}
   918  		// make the handler slow so concurrent calls exercise the singleflight
   919  		time.Sleep(time.Second)
   920  		return true, limitRangeList, nil
   921  	})
   922  
   923  	handler, _, err := newHandlerForTest(mockClient)
   924  	if err != nil {
   925  		t.Errorf("unexpected error initializing handler: %v", err)
   926  	}
   927  
   928  	attributes := admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "test", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, &metav1.CreateOptions{}, false, nil)
   929  
   930  	attributesTest1 := admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "test1", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, &metav1.CreateOptions{}, false, nil)
   931  
   932  	wg := sync.WaitGroup{}
   933  	for i := 0; i < 10; i++ {
   934  		wg.Add(2)
   935  		// simulating concurrent calls after a cache failure
   936  		go func() {
   937  			defer wg.Done()
   938  			ret, err := handler.GetLimitRanges(attributes)
   939  			if err != nil {
   940  				t.Errorf("unexpected error: %v", err)
   941  			}
   942  			for _, c := range ret {
   943  				if c.Namespace != attributes.GetNamespace() {
   944  					t.Errorf("Expected %s namespace, got %s", attributes.GetNamespace(), c.Namespace)
   945  				}
   946  			}
   947  		}()
   948  
   949  		// simulation of different namespaces is not a call
   950  		go func() {
   951  			defer wg.Done()
   952  			ret, err := handler.GetLimitRanges(attributesTest1)
   953  			if err != nil {
   954  				t.Errorf("unexpected error: %v", err)
   955  			}
   956  			for _, c := range ret {
   957  				if c.Namespace != attributesTest1.GetNamespace() {
   958  					t.Errorf("Expected %s namespace, got %s", attributesTest1.GetNamespace(), c.Namespace)
   959  				}
   960  			}
   961  		}()
   962  	}
   963  
   964  	// and here we wait for all the goroutines
   965  	wg.Wait()
   966  	// since all the calls with the same namespace will be holded, they must be catched on the singleflight group,
   967  	// There are two different sets of namespace calls
   968  	// hence only 2
   969  	if testCount != 1 {
   970  		t.Errorf("Expected 1 limit range call, got %d", testCount)
   971  	}
   972  	if test1Count != 1 {
   973  		t.Errorf("Expected 1 limit range call, got %d", test1Count)
   974  	}
   975  
   976  	// invalidate the cache
   977  	handler.liveLookupCache.Remove(attributes.GetNamespace())
   978  	_, err = handler.GetLimitRanges(attributes)
   979  	if err != nil {
   980  		t.Errorf("unexpected error: %v", err)
   981  	}
   982  
   983  	if testCount != 2 {
   984  		t.Errorf("Expected 2 limit range call, got %d", testCount)
   985  	}
   986  	if test1Count != 1 {
   987  		t.Errorf("Expected 1 limit range call, got %d", test1Count)
   988  	}
   989  }
   990  

View as plain text