sigs.k8s.io/kueue@v0.6.2/test/integration/controller/admissionchecks/provisioning/provisioning_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 package provisioning 17 18 import ( 19 "time" 20 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/onsi/ginkgo/v2" 23 "github.com/onsi/gomega" 24 corev1 "k8s.io/api/core/v1" 25 apimeta "k8s.io/apimachinery/pkg/api/meta" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 autoscaling "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1beta1" 30 "k8s.io/utils/ptr" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 34 "sigs.k8s.io/kueue/pkg/controller/admissionchecks/provisioning" 35 "sigs.k8s.io/kueue/pkg/util/testing" 36 "sigs.k8s.io/kueue/pkg/workload" 37 "sigs.k8s.io/kueue/test/util" 38 ) 39 40 const ( 41 customResourceOne = "example.org/res1" 42 customResourceTwo = "example.org/res2" 43 ) 44 45 var _ = ginkgo.Describe("Provisioning", ginkgo.Ordered, ginkgo.ContinueOnFailure, func() { 46 47 var ( 48 defaultMaxRetries = provisioning.MaxRetries 49 defaultMinBackoffSeconds = provisioning.MinBackoffSeconds 50 ) 51 52 ginkgo.When("A workload is using a provision admission check", func() { 53 var ( 54 ns *corev1.Namespace 55 wlKey types.NamespacedName 56 ac *kueue.AdmissionCheck 57 prc *kueue.ProvisioningRequestConfig 58 prc2 *kueue.ProvisioningRequestConfig 59 rf *kueue.ResourceFlavor 60 admission *kueue.Admission 61 ) 62 ginkgo.BeforeEach(func() { 63 provisioning.MaxRetries = 0 64 65 ns = &corev1.Namespace{ 66 ObjectMeta: metav1.ObjectMeta{ 67 GenerateName: "provisioning-", 68 }, 69 } 70 gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) 71 72 prc = &kueue.ProvisioningRequestConfig{ 73 ObjectMeta: metav1.ObjectMeta{ 74 Name: "prov-config", 75 }, 76 Spec: kueue.ProvisioningRequestConfigSpec{ 77 ProvisioningClassName: "provisioning-class", 78 Parameters: map[string]kueue.Parameter{ 79 "p1": "v1", 80 "p2": "v2", 81 }, 82 }, 83 } 84 gomega.Expect(k8sClient.Create(ctx, prc)).To(gomega.Succeed()) 85 86 prc2 = &kueue.ProvisioningRequestConfig{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: "prov-config2", 89 }, 90 Spec: kueue.ProvisioningRequestConfigSpec{ 91 ProvisioningClassName: "provisioning-class2", 92 Parameters: map[string]kueue.Parameter{ 93 "p1": "v1.2", 94 "p2": "v2.2", 95 }, 96 }, 97 } 98 gomega.Expect(k8sClient.Create(ctx, prc2)).To(gomega.Succeed()) 99 100 ac = testing.MakeAdmissionCheck("ac-prov"). 101 ControllerName(provisioning.ControllerName). 102 Parameters(kueue.GroupVersion.Group, "ProvisioningRequestConfig", prc.Name). 103 Obj() 104 gomega.Expect(k8sClient.Create(ctx, ac)).To(gomega.Succeed()) 105 106 rf = testing.MakeResourceFlavor("rf1").Label("ns1", "ns1v").Obj() 107 gomega.Expect(k8sClient.Create(ctx, rf)).To(gomega.Succeed()) 108 109 wl := testing.MakeWorkload("wl", ns.Name). 110 PodSets( 111 *testing.MakePodSet("ps1", 3). 112 Request(corev1.ResourceCPU, "1"). 113 Image("iamge"). 114 Obj(), 115 *testing.MakePodSet("ps2", 6). 116 Request(corev1.ResourceCPU, "500m"). 117 Request(customResourceOne, "1"). 118 Limit(customResourceOne, "1"). 119 Image("iamge"). 120 Obj(), 121 ). 122 Obj() 123 gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed()) 124 wlKey = client.ObjectKeyFromObject(wl) 125 126 admission = testing.MakeAdmission("q"). 127 PodSets( 128 kueue.PodSetAssignment{ 129 Name: "ps1", 130 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 131 corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name), 132 }, 133 ResourceUsage: map[corev1.ResourceName]resource.Quantity{ 134 corev1.ResourceCPU: resource.MustParse("3"), 135 }, 136 Count: ptr.To[int32](3), 137 }, 138 kueue.PodSetAssignment{ 139 Name: "ps2", 140 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 141 corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name), 142 }, 143 ResourceUsage: map[corev1.ResourceName]resource.Quantity{ 144 corev1.ResourceCPU: resource.MustParse("2"), 145 }, 146 Count: ptr.To[int32](4), 147 }, 148 ). 149 Obj() 150 151 }) 152 153 ginkgo.AfterEach(func() { 154 provisioning.MaxRetries = defaultMaxRetries 155 provisioning.MinBackoffSeconds = defaultMinBackoffSeconds 156 util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, rf, true) 157 util.ExpectAdmissionCheckToBeDeleted(ctx, k8sClient, ac, true) 158 util.ExpectProvisioningRequestConfigToBeDeleted(ctx, k8sClient, prc2, true) 159 util.ExpectProvisioningRequestConfigToBeDeleted(ctx, k8sClient, prc, true) 160 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed()) 161 }) 162 163 ginkgo.It("Should not create provisioning requests before quota is reserved", func() { 164 ginkgo.By("Setting the admission check to the workload", func() { 165 updatedWl := &kueue.Workload{} 166 gomega.Eventually(func() error { 167 err := k8sClient.Get(ctx, wlKey, updatedWl) 168 if err != nil { 169 return err 170 } 171 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 172 return nil 173 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 174 }) 175 176 ginkgo.By("Checking no provision request is created", func() { 177 provReqKey := types.NamespacedName{ 178 Namespace: wlKey.Namespace, 179 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 180 } 181 gomega.Consistently(func() error { 182 request := &autoscaling.ProvisioningRequest{} 183 return k8sClient.Get(ctx, provReqKey, request) 184 }, util.ConsistentDuration, util.Interval).Should(testing.BeNotFoundError()) 185 }) 186 }) 187 188 ginkgo.It("Should create provisioning requests after quota is reserved and remove it when reservation is lost", func() { 189 updatedWl := &kueue.Workload{} 190 ginkgo.By("Setting the admission check to the workload", func() { 191 gomega.Eventually(func() error { 192 err := k8sClient.Get(ctx, wlKey, updatedWl) 193 if err != nil { 194 return err 195 } 196 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 197 return nil 198 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 199 }) 200 201 ginkgo.By("Setting the quota reservation to the workload", func() { 202 updatedWl := &kueue.Workload{} 203 gomega.Eventually(func() error { 204 err := k8sClient.Get(ctx, wlKey, updatedWl) 205 if err != nil { 206 return err 207 } 208 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 209 return nil 210 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 211 }) 212 213 createdRequest := &autoscaling.ProvisioningRequest{} 214 ginkgo.By("Checking that the provision request is created", func() { 215 provReqKey := types.NamespacedName{ 216 Namespace: wlKey.Namespace, 217 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 218 } 219 gomega.Eventually(func() error { 220 return k8sClient.Get(ctx, provReqKey, createdRequest) 221 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 222 }) 223 224 ignoreContainersDefaults := cmpopts.IgnoreFields(corev1.Container{}, "TerminationMessagePath", "TerminationMessagePolicy", "ImagePullPolicy") 225 ginkgo.By("Checking that the provision requests content", func() { 226 gomega.Expect(createdRequest.Spec.ProvisioningClassName).To(gomega.Equal("provisioning-class")) 227 gomega.Expect(createdRequest.Spec.Parameters).To(gomega.BeComparableTo(map[string]autoscaling.Parameter{ 228 "p1": "v1", 229 "p2": "v2", 230 })) 231 gomega.Expect(createdRequest.Spec.PodSets).To(gomega.HaveLen(2)) 232 233 ps1 := createdRequest.Spec.PodSets[0] 234 gomega.Expect(ps1.Count).To(gomega.Equal(int32(3))) 235 gomega.Expect(ps1.PodTemplateRef.Name).NotTo(gomega.BeEmpty()) 236 237 // check the created pod template 238 createdTemplate := &corev1.PodTemplate{} 239 templateKey := types.NamespacedName{ 240 Namespace: createdRequest.Namespace, 241 Name: ps1.PodTemplateRef.Name, 242 } 243 gomega.Eventually(func() error { 244 return k8sClient.Get(ctx, templateKey, createdTemplate) 245 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 246 gomega.Expect(createdTemplate.Template.Spec.Containers).To(gomega.BeComparableTo(updatedWl.Spec.PodSets[0].Template.Spec.Containers, ignoreContainersDefaults)) 247 gomega.Expect(createdTemplate.Template.Spec.NodeSelector).To(gomega.BeComparableTo(map[string]string{"ns1": "ns1v"})) 248 249 ps2 := createdRequest.Spec.PodSets[1] 250 gomega.Expect(ps2.Count).To(gomega.Equal(int32(4))) 251 gomega.Expect(ps2.PodTemplateRef.Name).NotTo(gomega.BeEmpty()) 252 253 // check the created pod template 254 templateKey.Name = ps2.PodTemplateRef.Name 255 gomega.Eventually(func() error { 256 return k8sClient.Get(ctx, templateKey, createdTemplate) 257 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 258 gomega.Expect(createdTemplate.Template.Spec.Containers).To(gomega.BeComparableTo(updatedWl.Spec.PodSets[1].Template.Spec.Containers, ignoreContainersDefaults)) 259 gomega.Expect(createdTemplate.Template.Spec.NodeSelector).To(gomega.BeComparableTo(map[string]string{"ns1": "ns1v"})) 260 }) 261 262 ginkgo.By("Removing the quota reservation from the workload", func() { 263 updatedWl := &kueue.Workload{} 264 gomega.Eventually(func() error { 265 err := k8sClient.Get(ctx, wlKey, updatedWl) 266 if err != nil { 267 return err 268 } 269 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, nil)).To(gomega.Succeed()) 270 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 271 return nil 272 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 273 }) 274 275 ginkgo.By("Checking provision request is deleted", func() { 276 provReqKey := types.NamespacedName{ 277 Namespace: wlKey.Namespace, 278 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 279 } 280 gomega.Eventually(func() error { 281 request := &autoscaling.ProvisioningRequest{} 282 return k8sClient.Get(ctx, provReqKey, request) 283 }, util.Timeout, util.Interval).Should(testing.BeNotFoundError()) 284 }) 285 }) 286 287 ginkgo.It("Should set the condition ready when the provision succeed", func() { 288 ginkgo.By("Setting the admission check to the workload", func() { 289 updatedWl := &kueue.Workload{} 290 gomega.Eventually(func() error { 291 err := k8sClient.Get(ctx, wlKey, updatedWl) 292 if err != nil { 293 return err 294 } 295 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 296 return nil 297 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 298 }) 299 300 ginkgo.By("Setting the quota reservation to the workload", func() { 301 updatedWl := &kueue.Workload{} 302 gomega.Eventually(func() error { 303 err := k8sClient.Get(ctx, wlKey, updatedWl) 304 if err != nil { 305 return err 306 } 307 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 308 return nil 309 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 310 }) 311 312 provReqKey := types.NamespacedName{ 313 Namespace: wlKey.Namespace, 314 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 315 } 316 ginkgo.By("Setting the provision request as Provisioned", func() { 317 createdRequest := &autoscaling.ProvisioningRequest{} 318 gomega.Eventually(func() error { 319 err := k8sClient.Get(ctx, provReqKey, createdRequest) 320 if err != nil { 321 return err 322 } 323 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 324 Type: autoscaling.Provisioned, 325 Status: metav1.ConditionTrue, 326 Reason: autoscaling.Provisioned, 327 }) 328 return k8sClient.Status().Update(ctx, createdRequest) 329 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 330 }) 331 332 ginkgo.By("Checking the admission check", func() { 333 updatedWl := &kueue.Workload{} 334 gomega.Eventually(func(g gomega.Gomega) { 335 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 336 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 337 g.Expect(state).NotTo(gomega.BeNil()) 338 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateReady)) 339 g.Expect(state.PodSetUpdates).To(gomega.BeComparableTo([]kueue.PodSetUpdate{ 340 { 341 Name: "ps1", 342 Annotations: map[string]string{ 343 provisioning.ConsumesAnnotationKey: provReqKey.Name, 344 }, 345 }, 346 { 347 Name: "ps2", 348 Annotations: map[string]string{ 349 provisioning.ConsumesAnnotationKey: provReqKey.Name, 350 }, 351 }, 352 })) 353 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 354 }) 355 }) 356 357 ginkgo.It("Should set the condition rejected when the provision fails", func() { 358 ginkgo.By("Setting the admission check to the workload", func() { 359 updatedWl := &kueue.Workload{} 360 gomega.Eventually(func() error { 361 err := k8sClient.Get(ctx, wlKey, updatedWl) 362 if err != nil { 363 return err 364 } 365 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 366 return nil 367 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 368 }) 369 370 ginkgo.By("Setting the quota reservation to the workload", func() { 371 updatedWl := &kueue.Workload{} 372 gomega.Eventually(func() error { 373 err := k8sClient.Get(ctx, wlKey, updatedWl) 374 if err != nil { 375 return err 376 } 377 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 378 return nil 379 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 380 }) 381 382 ginkgo.By("Setting the provision request as Failed", func() { 383 createdRequest := &autoscaling.ProvisioningRequest{} 384 provReqKey := types.NamespacedName{ 385 Namespace: wlKey.Namespace, 386 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 387 } 388 gomega.Eventually(func() error { 389 err := k8sClient.Get(ctx, provReqKey, createdRequest) 390 if err != nil { 391 return err 392 } 393 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 394 Type: autoscaling.Failed, 395 Status: metav1.ConditionTrue, 396 Reason: autoscaling.Failed, 397 }) 398 return k8sClient.Status().Update(ctx, createdRequest) 399 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 400 }) 401 402 ginkgo.By("Checking the admission check", func() { 403 updatedWl := &kueue.Workload{} 404 gomega.Eventually(func(g gomega.Gomega) { 405 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 406 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 407 g.Expect(state).NotTo(gomega.BeNil()) 408 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateRejected)) 409 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 410 }) 411 }) 412 413 ginkgo.It("Should keep the provisioning config in sync", func() { 414 updatedWl := &kueue.Workload{} 415 ginkgo.By("Setting the admission check to the workload", func() { 416 gomega.Eventually(func() error { 417 err := k8sClient.Get(ctx, wlKey, updatedWl) 418 if err != nil { 419 return err 420 } 421 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 422 return nil 423 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 424 }) 425 426 ginkgo.By("Setting the quota reservation to the workload", func() { 427 updatedWl := &kueue.Workload{} 428 gomega.Eventually(func() error { 429 err := k8sClient.Get(ctx, wlKey, updatedWl) 430 if err != nil { 431 return err 432 } 433 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 434 return nil 435 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 436 }) 437 438 createdRequest := &autoscaling.ProvisioningRequest{} 439 provReqKey := types.NamespacedName{ 440 Namespace: wlKey.Namespace, 441 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 442 } 443 ginkgo.By("Checking that the provision request is created", func() { 444 gomega.Eventually(func() error { 445 return k8sClient.Get(ctx, provReqKey, createdRequest) 446 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 447 }) 448 449 ginkgo.By("Checking that the provision requests content", func() { 450 gomega.Expect(createdRequest.Spec.ProvisioningClassName).To(gomega.Equal("provisioning-class")) 451 gomega.Expect(createdRequest.Spec.Parameters).To(gomega.BeComparableTo(map[string]autoscaling.Parameter{ 452 "p1": "v1", 453 "p2": "v2", 454 })) 455 }) 456 457 ginkgo.By("Changing the provisioning request config content", func() { 458 updatedPRC := &kueue.ProvisioningRequestConfig{} 459 prcKey := types.NamespacedName{Name: prc.Name} 460 gomega.Eventually(func() error { 461 err := k8sClient.Get(ctx, prcKey, updatedPRC) 462 if err != nil { 463 return err 464 } 465 updatedPRC.Spec.ProvisioningClassName = "provisioning-class-updated" 466 updatedPRC.Spec.Parameters = map[string]kueue.Parameter{ 467 "p1": "v1updated", 468 "p3": "v3", 469 } 470 return k8sClient.Update(ctx, updatedPRC) 471 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 472 }) 473 474 ginkgo.By("Checking that the config values are propagated", func() { 475 gomega.Eventually(func(g gomega.Gomega) { 476 err := k8sClient.Get(ctx, provReqKey, createdRequest) 477 g.Expect(err).To(gomega.Succeed()) 478 g.Expect(createdRequest.Spec.ProvisioningClassName).To(gomega.Equal("provisioning-class-updated")) 479 g.Expect(createdRequest.Spec.Parameters).To(gomega.BeComparableTo(map[string]autoscaling.Parameter{ 480 "p1": "v1updated", 481 "p3": "v3", 482 })) 483 484 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 485 }) 486 487 ginkgo.By("Changing the provisioning request config used by the admission check", func() { 488 updatedAC := &kueue.AdmissionCheck{} 489 acKey := types.NamespacedName{Name: ac.Name} 490 gomega.Eventually(func() error { 491 err := k8sClient.Get(ctx, acKey, updatedAC) 492 if err != nil { 493 return err 494 } 495 updatedAC.Spec.Parameters.Name = "prov-config2" 496 return k8sClient.Update(ctx, updatedAC) 497 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 498 }) 499 500 ginkgo.By("Checking that the config values are propagated", func() { 501 gomega.Eventually(func(g gomega.Gomega) { 502 err := k8sClient.Get(ctx, provReqKey, createdRequest) 503 g.Expect(err).To(gomega.Succeed()) 504 g.Expect(createdRequest.Spec.ProvisioningClassName).To(gomega.Equal("provisioning-class2")) 505 g.Expect(createdRequest.Spec.Parameters).To(gomega.BeComparableTo(map[string]autoscaling.Parameter{ 506 "p1": "v1.2", 507 "p2": "v2.2", 508 })) 509 510 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 511 }) 512 513 ginkgo.By("Changing the provisioning request config used by the admission check to a missing one", func() { 514 updatedAC := &kueue.AdmissionCheck{} 515 acKey := types.NamespacedName{Name: ac.Name} 516 gomega.Eventually(func() error { 517 err := k8sClient.Get(ctx, acKey, updatedAC) 518 if err != nil { 519 return err 520 } 521 updatedAC.Spec.Parameters.Name = "prov-config-missing" 522 return k8sClient.Update(ctx, updatedAC) 523 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 524 }) 525 526 ginkgo.By("Checking no provision request is deleted", func() { 527 provReqKey := types.NamespacedName{ 528 Namespace: wlKey.Namespace, 529 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 530 } 531 gomega.Eventually(func() error { 532 request := &autoscaling.ProvisioningRequest{} 533 return k8sClient.Get(ctx, provReqKey, request) 534 }, util.Timeout, util.Interval).Should(testing.BeNotFoundError()) 535 }) 536 537 ginkgo.By("Checking the admission check state indicates an inactive check", func() { 538 gomega.Eventually(func(g gomega.Gomega) { 539 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 540 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 541 g.Expect(state).NotTo(gomega.BeNil()) 542 g.Expect(state.Message).To(gomega.Equal(provisioning.CheckInactiveMessage)) 543 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 544 }) 545 }) 546 547 ginkgo.It("Should let a running workload to continue after the provisioning request deleted", func() { 548 updatedWl := &kueue.Workload{} 549 ginkgo.By("Setting the admission check to the workload", func() { 550 gomega.Eventually(func() error { 551 err := k8sClient.Get(ctx, wlKey, updatedWl) 552 if err != nil { 553 return err 554 } 555 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 556 return nil 557 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 558 }) 559 560 ginkgo.By("Setting the quota reservation to the workload", func() { 561 gomega.Eventually(func() error { 562 err := k8sClient.Get(ctx, wlKey, updatedWl) 563 if err != nil { 564 return err 565 } 566 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 567 return nil 568 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 569 }) 570 571 createdRequest := &autoscaling.ProvisioningRequest{} 572 provReqKey := types.NamespacedName{ 573 Namespace: wlKey.Namespace, 574 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 575 } 576 577 ginkgo.By("Checking that the provision request is created", func() { 578 gomega.Eventually(func() error { 579 return k8sClient.Get(ctx, provReqKey, createdRequest) 580 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 581 }) 582 583 ginkgo.By("Setting the provision request as Provisioned", func() { 584 gomega.Eventually(func(g gomega.Gomega) { 585 g.Expect(k8sClient.Get(ctx, provReqKey, createdRequest)).To(gomega.Succeed()) 586 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 587 Type: autoscaling.Provisioned, 588 Status: metav1.ConditionTrue, 589 Reason: autoscaling.Provisioned, 590 }) 591 g.Expect(k8sClient.Status().Update(ctx, createdRequest)).To(gomega.Succeed()) 592 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 593 }) 594 595 ginkgo.By("Checking the admission check is ready", func() { 596 gomega.Eventually(func(g gomega.Gomega) { 597 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 598 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 599 g.Expect(state).NotTo(gomega.BeNil()) 600 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateReady)) 601 g.Expect(state.PodSetUpdates).To(gomega.BeComparableTo([]kueue.PodSetUpdate{ 602 { 603 Name: "ps1", 604 Annotations: map[string]string{ 605 provisioning.ConsumesAnnotationKey: provReqKey.Name, 606 }, 607 }, 608 { 609 Name: "ps2", 610 Annotations: map[string]string{ 611 provisioning.ConsumesAnnotationKey: provReqKey.Name, 612 }, 613 }, 614 })) 615 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 616 }) 617 618 ginkgo.By("Check the workload is admitted", func() { 619 util.SyncAdmittedConditionForWorkloads(ctx, k8sClient, updatedWl) 620 gomega.Eventually(func(g gomega.Gomega) { 621 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 622 util.ExpectWorkloadsToBeAdmitted(ctx, k8sClient, updatedWl) 623 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 624 }) 625 626 ginkgo.By("Deleting the provision request", func() { 627 gomega.Eventually(func() error { 628 return k8sClient.Delete(ctx, createdRequest) 629 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 630 }) 631 632 ginkgo.By("Checking provision request is deleted", func() { 633 gomega.Eventually(func() error { 634 return k8sClient.Get(ctx, provReqKey, createdRequest) 635 }, util.Timeout, util.Interval).Should(testing.BeNotFoundError()) 636 }) 637 638 // We use this as a proxy check to verify that the workload remains admitted, 639 // because the test suite does not run the workload controller 640 ginkgo.By("Checking the admission check remains ready", func() { 641 gomega.Eventually(func(g gomega.Gomega) { 642 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 643 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 644 g.Expect(state).NotTo(gomega.BeNil()) 645 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateReady)) 646 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 647 }) 648 649 ginkgo.By("Checking the provisioning request remains deleted", func() { 650 gomega.Eventually(func() error { 651 return k8sClient.Get(ctx, provReqKey, createdRequest) 652 }, util.Timeout, util.Interval).Should(testing.BeNotFoundError()) 653 }) 654 }) 655 }) 656 657 ginkgo.When("A workload is using a provision admission check with retry", func() { 658 var ( 659 ns *corev1.Namespace 660 wlKey types.NamespacedName 661 ac *kueue.AdmissionCheck 662 prc *kueue.ProvisioningRequestConfig 663 rf *kueue.ResourceFlavor 664 admission *kueue.Admission 665 ) 666 ginkgo.BeforeEach(func() { 667 provisioning.MaxRetries = 1 668 provisioning.MinBackoffSeconds = 1 669 670 ns = &corev1.Namespace{ 671 ObjectMeta: metav1.ObjectMeta{ 672 GenerateName: "provisioning-", 673 }, 674 } 675 gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) 676 677 prc = &kueue.ProvisioningRequestConfig{ 678 ObjectMeta: metav1.ObjectMeta{ 679 Name: "prov-config", 680 }, 681 Spec: kueue.ProvisioningRequestConfigSpec{ 682 ProvisioningClassName: "provisioning-class", 683 Parameters: map[string]kueue.Parameter{ 684 "p1": "v1", 685 "p2": "v2", 686 }, 687 }, 688 } 689 gomega.Expect(k8sClient.Create(ctx, prc)).To(gomega.Succeed()) 690 691 ac = testing.MakeAdmissionCheck("ac-prov"). 692 ControllerName(provisioning.ControllerName). 693 Parameters(kueue.GroupVersion.Group, "ProvisioningRequestConfig", prc.Name). 694 Obj() 695 gomega.Expect(k8sClient.Create(ctx, ac)).To(gomega.Succeed()) 696 697 rf = testing.MakeResourceFlavor("rf1").Label("ns1", "ns1v").Obj() 698 gomega.Expect(k8sClient.Create(ctx, rf)).To(gomega.Succeed()) 699 700 wl := testing.MakeWorkload("wl", ns.Name). 701 PodSets( 702 *testing.MakePodSet("ps1", 3). 703 Request(corev1.ResourceCPU, "1"). 704 Image("iamge"). 705 Obj(), 706 *testing.MakePodSet("ps2", 6). 707 Request(corev1.ResourceCPU, "500m"). 708 Request(customResourceOne, "1"). 709 Limit(customResourceOne, "1"). 710 Image("iamge"). 711 Obj(), 712 ). 713 Obj() 714 gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed()) 715 wlKey = client.ObjectKeyFromObject(wl) 716 717 admission = testing.MakeAdmission("q"). 718 PodSets( 719 kueue.PodSetAssignment{ 720 Name: "ps1", 721 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 722 corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name), 723 }, 724 ResourceUsage: map[corev1.ResourceName]resource.Quantity{ 725 corev1.ResourceCPU: resource.MustParse("3"), 726 }, 727 Count: ptr.To[int32](3), 728 }, 729 kueue.PodSetAssignment{ 730 Name: "ps2", 731 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 732 corev1.ResourceCPU: kueue.ResourceFlavorReference(rf.Name), 733 }, 734 ResourceUsage: map[corev1.ResourceName]resource.Quantity{ 735 corev1.ResourceCPU: resource.MustParse("2"), 736 }, 737 Count: ptr.To[int32](4), 738 }, 739 ). 740 Obj() 741 742 }) 743 744 ginkgo.AfterEach(func() { 745 provisioning.MaxRetries = defaultMaxRetries 746 provisioning.MinBackoffSeconds = defaultMinBackoffSeconds 747 util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, rf, true) 748 util.ExpectAdmissionCheckToBeDeleted(ctx, k8sClient, ac, true) 749 util.ExpectProvisioningRequestConfigToBeDeleted(ctx, k8sClient, prc, true) 750 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed()) 751 }) 752 753 ginkgo.It("Should retry when ProvisioningRequestConfig has MaxRetries=2, the succeeded if the second Provisioning request succeeds", func() { 754 ginkgo.By("Setting the admission check to the workload", func() { 755 updatedWl := &kueue.Workload{} 756 gomega.Eventually(func() error { 757 err := k8sClient.Get(ctx, wlKey, updatedWl) 758 if err != nil { 759 return err 760 } 761 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 762 return nil 763 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 764 }) 765 766 ginkgo.By("Setting the quota reservation to the workload", func() { 767 updatedWl := &kueue.Workload{} 768 gomega.Eventually(func() error { 769 err := k8sClient.Get(ctx, wlKey, updatedWl) 770 if err != nil { 771 return err 772 } 773 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 774 return nil 775 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 776 }) 777 778 ginkgo.By("Setting the provision request-1 as Failed", func() { 779 createdRequest := &autoscaling.ProvisioningRequest{} 780 provReqKey := types.NamespacedName{ 781 Namespace: wlKey.Namespace, 782 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 783 } 784 gomega.Eventually(func() error { 785 err := k8sClient.Get(ctx, provReqKey, createdRequest) 786 if err != nil { 787 return err 788 } 789 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 790 Type: autoscaling.Failed, 791 Status: metav1.ConditionTrue, 792 Reason: autoscaling.Failed, 793 }) 794 return k8sClient.Status().Update(ctx, createdRequest) 795 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 796 }) 797 798 ginkgo.By("Checking the admission check is pending", func() { 799 updatedWl := &kueue.Workload{} 800 // use consistently with short interval to make sure it does not 801 // flip to a short period of time. 802 gomega.Consistently(func(g gomega.Gomega) { 803 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 804 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 805 g.Expect(state).NotTo(gomega.BeNil()) 806 g.Expect(state.State).To(gomega.Equal(kueue.CheckStatePending)) 807 }, time.Second, 10*time.Millisecond).Should(gomega.Succeed()) 808 }) 809 810 ginkgo.By("Setting the provision request-2 as Provisioned", func() { 811 createdRequest := &autoscaling.ProvisioningRequest{} 812 provReqKey := types.NamespacedName{ 813 Namespace: wlKey.Namespace, 814 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 2), 815 } 816 gomega.Eventually(func() error { 817 err := k8sClient.Get(ctx, provReqKey, createdRequest) 818 if err != nil { 819 return err 820 } 821 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 822 Type: autoscaling.Provisioned, 823 Status: metav1.ConditionTrue, 824 Reason: autoscaling.Provisioned, 825 }) 826 return k8sClient.Status().Update(ctx, createdRequest) 827 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 828 }) 829 830 ginkgo.By("Checking the admission check is ready", func() { 831 updatedWl := &kueue.Workload{} 832 gomega.Eventually(func(g gomega.Gomega) { 833 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 834 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 835 g.Expect(state).NotTo(gomega.BeNil()) 836 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateReady)) 837 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 838 }) 839 }) 840 841 ginkgo.It("Should retry when ProvisioningRequestConfig has MaxRetries>o, and every Provisioning request retry fails", func() { 842 843 ginkgo.By("Setting the admission check to the workload", func() { 844 updatedWl := &kueue.Workload{} 845 gomega.Eventually(func() error { 846 err := k8sClient.Get(ctx, wlKey, updatedWl) 847 if err != nil { 848 return err 849 } 850 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, updatedWl, ac.Name, kueue.CheckStatePending, false) 851 return nil 852 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 853 }) 854 855 ginkgo.By("Setting the quota reservation to the workload", func() { 856 updatedWl := &kueue.Workload{} 857 gomega.Eventually(func() error { 858 err := k8sClient.Get(ctx, wlKey, updatedWl) 859 if err != nil { 860 return err 861 } 862 gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, updatedWl, admission)).To(gomega.Succeed()) 863 return nil 864 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 865 }) 866 867 ginkgo.By("Setting the provision request-1 as Failed", func() { 868 createdRequest := &autoscaling.ProvisioningRequest{} 869 provReqKey := types.NamespacedName{ 870 Namespace: wlKey.Namespace, 871 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 1), 872 } 873 gomega.Eventually(func() error { 874 err := k8sClient.Get(ctx, provReqKey, createdRequest) 875 if err != nil { 876 return err 877 } 878 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 879 Type: autoscaling.Failed, 880 Status: metav1.ConditionTrue, 881 Reason: autoscaling.Failed, 882 }) 883 return k8sClient.Status().Update(ctx, createdRequest) 884 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 885 }) 886 887 ginkgo.By("Checking the admission check is pending", func() { 888 updatedWl := &kueue.Workload{} 889 gomega.Eventually(func(g gomega.Gomega) { 890 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 891 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 892 g.Expect(state).NotTo(gomega.BeNil()) 893 g.Expect(state.State).To(gomega.Equal(kueue.CheckStatePending)) 894 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 895 }) 896 897 ginkgo.By("Setting the provision request-2 as Failed", func() { 898 createdRequest := &autoscaling.ProvisioningRequest{} 899 provReqKey := types.NamespacedName{ 900 Namespace: wlKey.Namespace, 901 Name: provisioning.GetProvisioningRequestName(wlKey.Name, ac.Name, 2), 902 } 903 gomega.Eventually(func() error { 904 err := k8sClient.Get(ctx, provReqKey, createdRequest) 905 if err != nil { 906 return err 907 } 908 apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{ 909 Type: autoscaling.Failed, 910 Status: metav1.ConditionTrue, 911 Reason: autoscaling.Failed, 912 }) 913 return k8sClient.Status().Update(ctx, createdRequest) 914 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 915 }) 916 917 ginkgo.By("Checking the admission check is rejected", func() { 918 updatedWl := &kueue.Workload{} 919 gomega.Eventually(func(g gomega.Gomega) { 920 g.Expect(k8sClient.Get(ctx, wlKey, updatedWl)).To(gomega.Succeed()) 921 state := workload.FindAdmissionCheck(updatedWl.Status.AdmissionChecks, ac.Name) 922 g.Expect(state).NotTo(gomega.BeNil()) 923 g.Expect(state.State).To(gomega.Equal(kueue.CheckStateRejected)) 924 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 925 }) 926 }) 927 }) 928 })