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  })