1
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
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
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
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
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
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
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
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
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
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
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
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
965 wg.Wait()
966
967
968
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
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