k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/limitranger/admission_test.go (about) 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 }