sigs.k8s.io/kueue@v0.6.2/pkg/util/limitrange/limitrange_test.go (about) 1 /* 2 Copyright 2023 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 limitrange 18 19 import ( 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 corev1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/resource" 25 "k8s.io/utils/field" 26 27 testingutil "sigs.k8s.io/kueue/pkg/util/testing" 28 ) 29 30 var ( 31 ar1 = resource.MustParse("1") 32 ar2 = resource.MustParse("2") 33 ar500m = resource.MustParse("500m") 34 ar1Gi = resource.MustParse("1Gi") 35 ar2Gi = resource.MustParse("2Gi") 36 ) 37 38 func TestSummarize(t *testing.T) { 39 cases := map[string]struct { 40 ranges []corev1.LimitRange 41 expected Summary 42 }{ 43 "empty": { 44 ranges: []corev1.LimitRange{}, 45 expected: map[corev1.LimitType]corev1.LimitRangeItem{}, 46 }, 47 "podDefaults": { 48 ranges: []corev1.LimitRange{ 49 { 50 Spec: corev1.LimitRangeSpec{ 51 Limits: []corev1.LimitRangeItem{ 52 { 53 Type: corev1.LimitTypePod, 54 Default: corev1.ResourceList{ 55 corev1.ResourceCPU: ar2, 56 }, 57 DefaultRequest: corev1.ResourceList{ 58 corev1.ResourceCPU: ar500m, 59 corev1.ResourceMemory: ar1Gi, 60 }, 61 }, 62 }, 63 }, 64 }, 65 { 66 Spec: corev1.LimitRangeSpec{ 67 Limits: []corev1.LimitRangeItem{ 68 { 69 Type: corev1.LimitTypePod, 70 Default: corev1.ResourceList{ 71 corev1.ResourceMemory: ar2Gi, 72 }, 73 DefaultRequest: corev1.ResourceList{ 74 corev1.ResourceCPU: ar1, 75 }, 76 }, 77 }, 78 }, 79 }, 80 }, 81 expected: map[corev1.LimitType]corev1.LimitRangeItem{ 82 corev1.LimitTypePod: { 83 Default: corev1.ResourceList{ 84 corev1.ResourceCPU: ar2, 85 corev1.ResourceMemory: ar2Gi, 86 }, 87 DefaultRequest: corev1.ResourceList{ 88 corev1.ResourceCPU: ar500m, 89 corev1.ResourceMemory: ar1Gi, 90 }, 91 }, 92 }, 93 }, 94 "limits": { 95 ranges: []corev1.LimitRange{ 96 { 97 Spec: corev1.LimitRangeSpec{ 98 Limits: []corev1.LimitRangeItem{ 99 { 100 Type: corev1.LimitTypePod, 101 Max: corev1.ResourceList{ 102 corev1.ResourceCPU: ar2, 103 }, 104 Min: corev1.ResourceList{ 105 corev1.ResourceCPU: ar500m, 106 corev1.ResourceMemory: ar1Gi, 107 }, 108 MaxLimitRequestRatio: corev1.ResourceList{ 109 corev1.ResourceCPU: ar2, 110 }, 111 }, 112 }, 113 }, 114 }, 115 { 116 Spec: corev1.LimitRangeSpec{ 117 Limits: []corev1.LimitRangeItem{ 118 { 119 Type: corev1.LimitTypePod, 120 Max: corev1.ResourceList{ 121 corev1.ResourceMemory: ar2Gi, 122 }, 123 Min: corev1.ResourceList{ 124 corev1.ResourceCPU: ar1, 125 }, 126 MaxLimitRequestRatio: corev1.ResourceList{ 127 corev1.ResourceCPU: ar500m, 128 }, 129 }, 130 }, 131 }, 132 }, 133 }, 134 expected: Summary{ 135 corev1.LimitTypePod: { 136 Max: corev1.ResourceList{ 137 corev1.ResourceCPU: ar2, 138 corev1.ResourceMemory: ar2Gi, 139 }, 140 Min: corev1.ResourceList{ 141 corev1.ResourceCPU: ar1, 142 corev1.ResourceMemory: ar1Gi, 143 }, 144 MaxLimitRequestRatio: corev1.ResourceList{ 145 corev1.ResourceCPU: ar500m, 146 }, 147 }, 148 }, 149 }, 150 } 151 152 for name, tc := range cases { 153 t.Run(name, func(t *testing.T) { 154 result := Summarize(tc.ranges...) 155 if diff := cmp.Diff(tc.expected, result); diff != "" { 156 t.Errorf("Unexpected result (-want,+got):\n%s", diff) 157 } 158 }) 159 } 160 } 161 162 func TestTotalRequest(t *testing.T) { 163 containers := []corev1.Container{ 164 { 165 Resources: corev1.ResourceRequirements{ 166 Requests: corev1.ResourceList{ 167 corev1.ResourceCPU: resource.MustParse("1"), 168 corev1.ResourceMemory: resource.MustParse("2Gi"), 169 }, 170 }, 171 }, 172 { 173 Resources: corev1.ResourceRequirements{ 174 Requests: corev1.ResourceList{ 175 corev1.ResourceCPU: resource.MustParse("1500m"), 176 "example.com/gpu": resource.MustParse("2"), 177 }, 178 }, 179 }, 180 { 181 Resources: corev1.ResourceRequirements{ 182 Requests: corev1.ResourceList{ 183 corev1.ResourceCPU: resource.MustParse("4"), 184 corev1.ResourceMemory: resource.MustParse("2Gi"), 185 }, 186 }, 187 }, 188 { 189 Resources: corev1.ResourceRequirements{ 190 Requests: corev1.ResourceList{ 191 corev1.ResourceCPU: resource.MustParse("1500m"), 192 "example.com/gpu": resource.MustParse("2"), 193 }, 194 }, 195 }, 196 } 197 cases := map[string]struct { 198 podSpec *corev1.PodSpec 199 want corev1.ResourceList 200 }{ 201 "sum up main containers": { 202 podSpec: &corev1.PodSpec{ 203 Containers: containers[:2], 204 }, 205 want: corev1.ResourceList{ 206 corev1.ResourceCPU: resource.MustParse("2500m"), 207 corev1.ResourceMemory: resource.MustParse("2Gi"), 208 "example.com/gpu": resource.MustParse("2"), 209 }, 210 }, 211 "one init wants more": { 212 podSpec: &corev1.PodSpec{ 213 InitContainers: containers[2:], 214 Containers: containers[:2], 215 }, 216 want: corev1.ResourceList{ 217 corev1.ResourceCPU: resource.MustParse("4000m"), 218 corev1.ResourceMemory: resource.MustParse("2Gi"), 219 "example.com/gpu": resource.MustParse("2"), 220 }, 221 }, 222 "adds overhead": { 223 podSpec: &corev1.PodSpec{ 224 InitContainers: containers[2:], 225 Containers: containers[:2], 226 Overhead: corev1.ResourceList{ 227 corev1.ResourceCPU: resource.MustParse("1"), 228 corev1.ResourceMemory: resource.MustParse("1Gi"), 229 "example.com/gpu": resource.MustParse("1"), 230 }, 231 }, 232 want: corev1.ResourceList{ 233 corev1.ResourceCPU: resource.MustParse("5000m"), 234 corev1.ResourceMemory: resource.MustParse("3Gi"), 235 "example.com/gpu": resource.MustParse("3"), 236 }, 237 }, 238 } 239 240 for name, tc := range cases { 241 t.Run(name, func(t *testing.T) { 242 result := TotalRequests(tc.podSpec) 243 if diff := cmp.Diff(tc.want, result); diff != "" { 244 t.Errorf("Unexpected result (-want,+got):\n%s", diff) 245 } 246 }) 247 } 248 } 249 func TestValidatePodSpec(t *testing.T) { 250 podSpec := &corev1.PodSpec{ 251 Containers: []corev1.Container{ 252 { 253 Resources: corev1.ResourceRequirements{ 254 Requests: corev1.ResourceList{ 255 corev1.ResourceCPU: resource.MustParse("1"), 256 corev1.ResourceMemory: resource.MustParse("2Gi"), 257 "example.com/mainContainerGpu": resource.MustParse("2"), 258 }, 259 }, 260 }, 261 { 262 Resources: corev1.ResourceRequirements{ 263 Requests: corev1.ResourceList{ 264 corev1.ResourceCPU: resource.MustParse("1500m"), 265 "example.com/gpu": resource.MustParse("2"), 266 }, 267 }, 268 }, 269 }, 270 InitContainers: []corev1.Container{ 271 { 272 Resources: corev1.ResourceRequirements{ 273 Requests: corev1.ResourceList{ 274 corev1.ResourceCPU: resource.MustParse("4"), 275 corev1.ResourceMemory: resource.MustParse("2Gi"), 276 }, 277 }, 278 }, 279 { 280 Resources: corev1.ResourceRequirements{ 281 Requests: corev1.ResourceList{ 282 corev1.ResourceCPU: resource.MustParse("1500m"), 283 "example.com/gpu": resource.MustParse("2"), 284 "example.com/initContainerGpu": resource.MustParse("2"), 285 }, 286 }, 287 }, 288 }, 289 Overhead: corev1.ResourceList{ 290 corev1.ResourceCPU: resource.MustParse("1"), 291 corev1.ResourceMemory: resource.MustParse("1Gi"), 292 "example.com/gpu": resource.MustParse("1"), 293 }, 294 } 295 cases := map[string]struct { 296 summary Summary 297 want []string 298 }{ 299 "empty": { 300 summary: Summary{}, 301 want: []string{}, 302 }, 303 "init container over": { 304 summary: Summarize(*testingutil.MakeLimitRange("", ""). 305 WithType(corev1.LimitTypeContainer). 306 WithValue("Max", "example.com/initContainerGpu", "1"). 307 Obj()), 308 want: []string{ 309 violateMaxMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"), 310 }, 311 }, 312 "init container under": { 313 summary: Summarize(*testingutil.MakeLimitRange("", ""). 314 WithType(corev1.LimitTypeContainer). 315 WithValue("Min", "example.com/initContainerGpu", "3"). 316 Obj()), 317 want: []string{ 318 violateMinMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"), 319 }, 320 }, 321 "container over": { 322 summary: Summarize(*testingutil.MakeLimitRange("", ""). 323 WithType(corev1.LimitTypeContainer). 324 WithValue("Max", "example.com/mainContainerGpu", "1"). 325 Obj()), 326 want: []string{ 327 violateMaxMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"), 328 }, 329 }, 330 "container under": { 331 summary: Summarize(*testingutil.MakeLimitRange("", ""). 332 WithType(corev1.LimitTypeContainer). 333 WithValue("Min", "example.com/mainContainerGpu", "3"). 334 Obj()), 335 want: []string{ 336 violateMinMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"), 337 }, 338 }, 339 "pod over": { 340 summary: Summarize(*testingutil.MakeLimitRange("", ""). 341 WithType(corev1.LimitTypePod). 342 WithValue("Max", corev1.ResourceCPU, "4"). 343 Obj()), 344 want: []string{ 345 violateMaxMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)), 346 }, 347 }, 348 "pod under": { 349 summary: Summarize(*testingutil.MakeLimitRange("", ""). 350 WithType(corev1.LimitTypePod). 351 WithValue("Min", corev1.ResourceCPU, "6"). 352 Obj()), 353 want: []string{ 354 violateMinMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)), 355 }, 356 }, 357 "multiple": { 358 summary: Summarize( 359 *testingutil.MakeLimitRange("", ""). 360 WithType(corev1.LimitTypePod). 361 WithValue("Max", corev1.ResourceCPU, "4"). 362 Obj(), 363 *testingutil.MakeLimitRange("", ""). 364 WithType(corev1.LimitTypeContainer). 365 WithValue("Min", "example.com/mainContainerGpu", "3"). 366 Obj(), 367 *testingutil.MakeLimitRange("", ""). 368 WithType(corev1.LimitTypeContainer). 369 WithValue("Max", "example.com/initContainerGpu", "1"). 370 Obj(), 371 ), 372 want: []string{ 373 violateMaxMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"), 374 violateMinMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"), 375 violateMaxMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)), 376 }, 377 }, 378 "multiple valid": { 379 summary: Summarize( 380 *testingutil.MakeLimitRange("", ""). 381 WithType(corev1.LimitTypePod). 382 WithValue("Max", corev1.ResourceCPU, "5"). 383 Obj(), 384 *testingutil.MakeLimitRange("", ""). 385 WithType(corev1.LimitTypeContainer). 386 WithValue("Min", "example.com/mainContainerGpu", "1"). 387 Obj(), 388 *testingutil.MakeLimitRange("", ""). 389 WithType(corev1.LimitTypeContainer). 390 WithValue("Max", "example.com/initContainerGpu", "2"). 391 Obj(), 392 ), 393 want: []string{}, 394 }, 395 } 396 for name, tc := range cases { 397 t.Run(name, func(t *testing.T) { 398 result := tc.summary.ValidatePodSpec(podSpec, field.NewPath("testPodSet")) 399 if diff := cmp.Diff(tc.want, result); diff != "" { 400 t.Errorf("Unexpected result (-want,+got):\n%s", diff) 401 } 402 }) 403 } 404 }