sigs.k8s.io/kueue@v0.6.2/test/util/util.go (about)

     1  /*
     2  Copyright 2022 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 util
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  	"github.com/onsi/ginkgo/v2"
    25  	"github.com/onsi/gomega"
    26  	batchv1 "k8s.io/api/batch/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	nodev1 "k8s.io/api/node/v1"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	apimeta "k8s.io/apimachinery/pkg/api/meta"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/component-base/metrics/testutil"
    35  	"k8s.io/utils/ptr"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    38  
    39  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    40  	"sigs.k8s.io/kueue/pkg/controller/jobs/pod"
    41  	"sigs.k8s.io/kueue/pkg/metrics"
    42  	"sigs.k8s.io/kueue/pkg/util/testing"
    43  	"sigs.k8s.io/kueue/pkg/workload"
    44  )
    45  
    46  func DeleteAdmissionCheck(ctx context.Context, c client.Client, ac *kueue.AdmissionCheck) error {
    47  	if ac != nil {
    48  		if err := c.Delete(ctx, ac); err != nil && !apierrors.IsNotFound(err) {
    49  			return err
    50  		}
    51  	}
    52  	return nil
    53  }
    54  
    55  func DeleteProvisioningRequestConfig(ctx context.Context, c client.Client, ac *kueue.ProvisioningRequestConfig) error {
    56  	if ac != nil {
    57  		if err := c.Delete(ctx, ac); err != nil && !apierrors.IsNotFound(err) {
    58  			return err
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  func DeleteWorkload(ctx context.Context, c client.Client, wl *kueue.Workload) error {
    65  	if wl != nil {
    66  		if err := c.Delete(ctx, wl); err != nil && !apierrors.IsNotFound(err) {
    67  			return err
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  func DeleteClusterQueue(ctx context.Context, c client.Client, cq *kueue.ClusterQueue) error {
    74  	if cq == nil {
    75  		return nil
    76  	}
    77  	if err := c.Delete(ctx, cq); err != nil && !apierrors.IsNotFound(err) {
    78  		return err
    79  	}
    80  	return nil
    81  }
    82  
    83  func DeleteResourceFlavor(ctx context.Context, c client.Client, rf *kueue.ResourceFlavor) error {
    84  	if rf == nil {
    85  		return nil
    86  	}
    87  	if err := c.Delete(ctx, rf); err != nil && !apierrors.IsNotFound(err) {
    88  		return err
    89  	}
    90  	return nil
    91  }
    92  
    93  func DeleteLocalQueue(ctx context.Context, c client.Client, q *kueue.LocalQueue) error {
    94  	if q == nil {
    95  		return nil
    96  	}
    97  	if err := c.Delete(ctx, q); err != nil && !apierrors.IsNotFound(err) {
    98  		return err
    99  	}
   100  	return nil
   101  }
   102  
   103  // DeleteNamespace deletes all objects the tests typically create in the namespace.
   104  func DeleteNamespace(ctx context.Context, c client.Client, ns *corev1.Namespace) error {
   105  	if ns == nil {
   106  		return nil
   107  	}
   108  	if err := DeleteAllJobsInNamespace(ctx, c, ns); err != nil {
   109  		return err
   110  	}
   111  	if err := c.DeleteAllOf(ctx, &kueue.LocalQueue{}, client.InNamespace(ns.Name)); err != nil && !apierrors.IsNotFound(err) {
   112  		return err
   113  	}
   114  	if err := DeleteAllPodsInNamespace(ctx, c, ns); err != nil {
   115  		return err
   116  	}
   117  	if err := DeleteWorkloadsInNamespace(ctx, c, ns); err != nil {
   118  		return err
   119  	}
   120  	err := c.DeleteAllOf(ctx, &corev1.LimitRange{}, client.InNamespace(ns.Name), client.PropagationPolicy(metav1.DeletePropagationBackground))
   121  	if err != nil && !apierrors.IsNotFound(err) {
   122  		return err
   123  	}
   124  	if err := c.Delete(ctx, ns); err != nil && !apierrors.IsNotFound(err) {
   125  		return err
   126  	}
   127  	return nil
   128  }
   129  
   130  func DeleteAllJobsInNamespace(ctx context.Context, c client.Client, ns *corev1.Namespace) error {
   131  	err := c.DeleteAllOf(ctx, &batchv1.Job{}, client.InNamespace(ns.Name), client.PropagationPolicy(metav1.DeletePropagationBackground))
   132  	if err != nil && !apierrors.IsNotFound(err) {
   133  		return err
   134  	}
   135  	return nil
   136  }
   137  
   138  func DeleteAllPodsInNamespace(ctx context.Context, c client.Client, ns *corev1.Namespace) error {
   139  	err := c.DeleteAllOf(ctx, &corev1.Pod{}, client.InNamespace(ns.Name))
   140  	if err != nil && !apierrors.IsNotFound(err) {
   141  		return fmt.Errorf("deleting all Pods in namespace %q: %w", ns.Name, err)
   142  	}
   143  
   144  	gomega.Eventually(func() error {
   145  		lst := corev1.PodList{}
   146  		err := c.List(ctx, &lst, client.InNamespace(ns.Name))
   147  		if err != nil && !errors.IsNotFound(err) {
   148  			return fmt.Errorf("listing Pods with a finalizer in namespace %q: %w", ns.Name, err)
   149  		}
   150  
   151  		for _, p := range lst.Items {
   152  			if controllerutil.RemoveFinalizer(&p, pod.PodFinalizer) {
   153  				err = c.Update(ctx, &p)
   154  				if err != nil && !apierrors.IsNotFound(err) {
   155  					return fmt.Errorf("removing finalizer: %w", err)
   156  				}
   157  			}
   158  		}
   159  
   160  		return nil
   161  	}, LongTimeout, Interval).Should(gomega.Succeed())
   162  
   163  	return nil
   164  }
   165  
   166  func DeleteWorkloadsInNamespace(ctx context.Context, c client.Client, ns *corev1.Namespace) error {
   167  	if err := c.DeleteAllOf(ctx, &kueue.Workload{}, client.InNamespace(ns.Name)); err != nil && !apierrors.IsNotFound(err) {
   168  		return err
   169  	}
   170  
   171  	gomega.Eventually(func() error {
   172  		lst := kueue.WorkloadList{}
   173  		err := c.List(ctx, &lst, client.InNamespace(ns.Name))
   174  		if err != nil && !errors.IsNotFound(err) {
   175  			return fmt.Errorf("listing Workloads with a finalizer in namespace %q: %w", ns.Name, err)
   176  		}
   177  
   178  		for _, wl := range lst.Items {
   179  			if controllerutil.RemoveFinalizer(&wl, kueue.ResourceInUseFinalizerName) {
   180  				err = c.Update(ctx, &wl)
   181  				if err != nil && !apierrors.IsNotFound(err) {
   182  					return fmt.Errorf("removing finalizer: %w", err)
   183  				}
   184  			}
   185  		}
   186  
   187  		return nil
   188  	}, LongTimeout, Interval).Should(gomega.Succeed())
   189  
   190  	return nil
   191  }
   192  
   193  func DeleteRuntimeClass(ctx context.Context, c client.Client, runtimeClass *nodev1.RuntimeClass) error {
   194  	if runtimeClass == nil {
   195  		return nil
   196  	}
   197  	if err := c.Delete(ctx, runtimeClass); err != nil && !apierrors.IsNotFound(err) {
   198  		return err
   199  	}
   200  	return nil
   201  }
   202  
   203  func UnholdQueue(ctx context.Context, k8sClient client.Client, cq *kueue.ClusterQueue) {
   204  	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
   205  		var cqCopy kueue.ClusterQueue
   206  		g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(cq), &cqCopy)).To(gomega.Succeed())
   207  		if ptr.Deref(cqCopy.Spec.StopPolicy, kueue.None) == kueue.None {
   208  			return
   209  		}
   210  		cqCopy.Spec.StopPolicy = ptr.To(kueue.None)
   211  		g.Expect(k8sClient.Update(ctx, &cqCopy)).To(gomega.Succeed())
   212  	}, Timeout, Interval).Should(gomega.Succeed())
   213  }
   214  
   215  func FinishWorkloads(ctx context.Context, k8sClient client.Client, workloads ...*kueue.Workload) {
   216  	for _, w := range workloads {
   217  		gomega.EventuallyWithOffset(1, func() error {
   218  			var newWL kueue.Workload
   219  			gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(w), &newWL)).To(gomega.Succeed())
   220  			newWL.Status.Conditions = append(w.Status.Conditions, metav1.Condition{
   221  				Type:               kueue.WorkloadFinished,
   222  				Status:             metav1.ConditionTrue,
   223  				LastTransitionTime: metav1.Now(),
   224  				Reason:             "ByTest",
   225  				Message:            "Finished by test",
   226  			})
   227  			return k8sClient.Status().Update(ctx, &newWL)
   228  		}, Timeout, Interval).Should(gomega.Succeed())
   229  	}
   230  }
   231  
   232  func ExpectWorkloadsToHaveQuotaReservation(ctx context.Context, k8sClient client.Client, cqName string, wls ...*kueue.Workload) {
   233  	gomega.EventuallyWithOffset(1, func() int {
   234  		admitted := 0
   235  		var updatedWorkload kueue.Workload
   236  		for _, wl := range wls {
   237  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   238  			if workload.HasQuotaReservation(&updatedWorkload) && string(updatedWorkload.Status.Admission.ClusterQueue) == cqName {
   239  				admitted++
   240  			}
   241  		}
   242  		return admitted
   243  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads were admitted")
   244  }
   245  
   246  func FilterAdmittedWorkloads(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) []*kueue.Workload {
   247  	return filterWorkloads(ctx, k8sClient, workload.HasQuotaReservation, wls...)
   248  }
   249  
   250  func FilterEvictedWorkloads(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) []*kueue.Workload {
   251  	return filterWorkloads(ctx, k8sClient, func(wl *kueue.Workload) bool {
   252  		return apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadEvicted)
   253  	}, wls...)
   254  }
   255  
   256  func filterWorkloads(ctx context.Context, k8sClient client.Client, filter func(*kueue.Workload) bool, wls ...*kueue.Workload) []*kueue.Workload {
   257  	ret := make([]*kueue.Workload, 0, len(wls))
   258  	var updatedWorkload kueue.Workload
   259  	for _, wl := range wls {
   260  		err := k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)
   261  		if err == nil && filter(&updatedWorkload) {
   262  			ret = append(ret, wl)
   263  		}
   264  	}
   265  	return ret
   266  }
   267  
   268  func ExpectWorkloadsToBePending(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   269  	gomega.EventuallyWithOffset(1, func() int {
   270  		pending := 0
   271  		var updatedWorkload kueue.Workload
   272  		for _, wl := range wls {
   273  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   274  			cond := apimeta.FindStatusCondition(updatedWorkload.Status.Conditions, kueue.WorkloadQuotaReserved)
   275  			if cond == nil {
   276  				continue
   277  			}
   278  			if cond.Status == metav1.ConditionFalse && cond.Reason == "Pending" {
   279  				pending++
   280  			}
   281  		}
   282  		return pending
   283  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads are pending")
   284  }
   285  
   286  func ExpectWorkloadsToBeAdmitted(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   287  	expectWorkloadsToBeAdmittedCountWithOffset(ctx, 2, k8sClient, len(wls), wls...)
   288  }
   289  
   290  func ExpectWorkloadsToBeAdmittedCount(ctx context.Context, k8sClient client.Client, count int, wls ...*kueue.Workload) {
   291  	expectWorkloadsToBeAdmittedCountWithOffset(ctx, 2, k8sClient, count, wls...)
   292  }
   293  
   294  func expectWorkloadsToBeAdmittedCountWithOffset(ctx context.Context, offset int, k8sClient client.Client, count int, wls ...*kueue.Workload) {
   295  	gomega.EventuallyWithOffset(offset, func() int {
   296  		admitted := 0
   297  		var updatedWorkload kueue.Workload
   298  		for _, wl := range wls {
   299  			gomega.ExpectWithOffset(offset, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   300  			if apimeta.IsStatusConditionTrue(updatedWorkload.Status.Conditions, kueue.WorkloadAdmitted) {
   301  				admitted++
   302  			}
   303  		}
   304  		return admitted
   305  	}, Timeout, Interval).Should(gomega.Equal(count), "Not enough workloads are admitted")
   306  }
   307  
   308  func ExpectWorkloadToFinish(ctx context.Context, k8sClient client.Client, wlKey client.ObjectKey) {
   309  	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
   310  		var wl kueue.Workload
   311  		g.Expect(k8sClient.Get(ctx, wlKey, &wl)).To(gomega.Succeed())
   312  		g.Expect(apimeta.IsStatusConditionTrue(wl.Status.Conditions, kueue.WorkloadFinished)).
   313  			To(gomega.BeTrueBecause("it's finished"))
   314  	}, LongTimeout, Interval).Should(gomega.Succeed())
   315  }
   316  
   317  func ExpectWorkloadToHaveRequeueCount(ctx context.Context, k8sClient client.Client, wlKey client.ObjectKey, requeueCount *int32) {
   318  	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
   319  		var wl kueue.Workload
   320  		g.Expect(k8sClient.Get(ctx, wlKey, &wl)).Should(gomega.Succeed())
   321  		g.Expect(ptr.Deref(wl.Status.RequeueState, kueue.RequeueState{})).Should(gomega.BeComparableTo(kueue.RequeueState{
   322  			Count: requeueCount,
   323  		}, cmpopts.IgnoreFields(kueue.RequeueState{}, "RequeueAt")))
   324  		if requeueCount != nil {
   325  			g.Expect(wl.Status.RequeueState.RequeueAt).ShouldNot(gomega.BeNil())
   326  		}
   327  	}, Timeout, Interval).Should(gomega.Succeed())
   328  }
   329  
   330  func ExpectWorkloadsToBePreempted(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   331  	gomega.EventuallyWithOffset(1, func() int {
   332  		preempted := 0
   333  		var updatedWorkload kueue.Workload
   334  		for _, wl := range wls {
   335  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   336  			cond := apimeta.FindStatusCondition(updatedWorkload.Status.Conditions, kueue.WorkloadEvicted)
   337  			if cond == nil {
   338  				continue
   339  			}
   340  			if cond.Status == metav1.ConditionTrue {
   341  				preempted++
   342  			}
   343  		}
   344  		return preempted
   345  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads are preempted")
   346  }
   347  
   348  func ExpectWorkloadsToBeWaiting(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   349  	gomega.EventuallyWithOffset(1, func() int {
   350  		pending := 0
   351  		var updatedWorkload kueue.Workload
   352  		for _, wl := range wls {
   353  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   354  			cond := apimeta.FindStatusCondition(updatedWorkload.Status.Conditions, kueue.WorkloadQuotaReserved)
   355  			if cond == nil {
   356  				continue
   357  			}
   358  			if cond.Status == metav1.ConditionFalse && cond.Reason == "Waiting" {
   359  				pending++
   360  			}
   361  		}
   362  		return pending
   363  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads are waiting")
   364  }
   365  
   366  func ExpectWorkloadsToBeFrozen(ctx context.Context, k8sClient client.Client, cq string, wls ...*kueue.Workload) {
   367  	gomega.EventuallyWithOffset(1, func() int {
   368  		frozen := 0
   369  		var updatedWorkload kueue.Workload
   370  		for _, wl := range wls {
   371  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   372  			cond := apimeta.FindStatusCondition(updatedWorkload.Status.Conditions, kueue.WorkloadQuotaReserved)
   373  			if cond == nil {
   374  				continue
   375  			}
   376  			msg := fmt.Sprintf("ClusterQueue %s is inactive", cq)
   377  			if cond.Status == metav1.ConditionFalse && cond.Reason == "Inadmissible" && cond.Message == msg {
   378  				frozen++
   379  			}
   380  		}
   381  		return frozen
   382  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads are frozen")
   383  }
   384  
   385  func ExpectWorkloadToBeAdmittedAs(ctx context.Context, k8sClient client.Client, wl *kueue.Workload, admission *kueue.Admission) {
   386  	var updatedWorkload kueue.Workload
   387  	gomega.EventuallyWithOffset(1, func() *kueue.Admission {
   388  		gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   389  		return updatedWorkload.Status.Admission
   390  	}, Timeout, Interval).Should(gomega.BeComparableTo(admission))
   391  }
   392  
   393  var attemptStatuses = []metrics.AdmissionResult{metrics.AdmissionResultInadmissible, metrics.AdmissionResultSuccess}
   394  
   395  func ExpectAdmissionAttemptsMetric(pending, admitted int) {
   396  	vals := []int{pending, admitted}
   397  
   398  	for i, status := range attemptStatuses {
   399  		metric := metrics.AdmissionAttemptsTotal.WithLabelValues(string(status))
   400  		gomega.EventuallyWithOffset(1, func() int {
   401  			v, err := testutil.GetCounterMetricValue(metric)
   402  			gomega.Expect(err).ToNot(gomega.HaveOccurred())
   403  			return int(v)
   404  		}, Timeout, Interval).Should(gomega.Equal(vals[i]), "pending_workloads with status=%s", status)
   405  	}
   406  }
   407  
   408  var pendingStatuses = []string{metrics.PendingStatusActive, metrics.PendingStatusInadmissible}
   409  
   410  func ExpectPendingWorkloadsMetric(cq *kueue.ClusterQueue, active, inadmissible int) {
   411  	vals := []int{active, inadmissible}
   412  	for i, status := range pendingStatuses {
   413  		metric := metrics.PendingWorkloads.WithLabelValues(cq.Name, status)
   414  		gomega.EventuallyWithOffset(1, func() int {
   415  			v, err := testutil.GetGaugeMetricValue(metric)
   416  			gomega.Expect(err).ToNot(gomega.HaveOccurred())
   417  			return int(v)
   418  		}, Timeout, Interval).Should(gomega.Equal(vals[i]), "pending_workloads with status=%s", status)
   419  	}
   420  }
   421  
   422  func ExpectReservingActiveWorkloadsMetric(cq *kueue.ClusterQueue, v int) {
   423  	metric := metrics.ReservingActiveWorkloads.WithLabelValues(cq.Name)
   424  	gomega.EventuallyWithOffset(1, func() int {
   425  		v, err := testutil.GetGaugeMetricValue(metric)
   426  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   427  		return int(v)
   428  	}, Timeout, Interval).Should(gomega.Equal(v))
   429  }
   430  
   431  func ExpectAdmittedWorkloadsTotalMetric(cq *kueue.ClusterQueue, v int) {
   432  	metric := metrics.AdmittedWorkloadsTotal.WithLabelValues(cq.Name)
   433  	gomega.EventuallyWithOffset(1, func() int {
   434  		v, err := testutil.GetCounterMetricValue(metric)
   435  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   436  		return int(v)
   437  	}, Timeout, Interval).Should(gomega.Equal(v))
   438  }
   439  
   440  func ExpectClusterQueueStatusMetric(cq *kueue.ClusterQueue, status metrics.ClusterQueueStatus) {
   441  	for i, s := range metrics.CQStatuses {
   442  		var wantV float64
   443  		if metrics.CQStatuses[i] == status {
   444  			wantV = 1
   445  		}
   446  		metric := metrics.ClusterQueueByStatus.WithLabelValues(cq.Name, string(s))
   447  		gomega.EventuallyWithOffset(1, func() float64 {
   448  			v, err := testutil.GetGaugeMetricValue(metric)
   449  			gomega.Expect(err).ToNot(gomega.HaveOccurred())
   450  			return v
   451  		}, Timeout, Interval).Should(gomega.Equal(wantV), "cluster_queue_status with status=%s", s)
   452  	}
   453  }
   454  
   455  func ExpectAdmissionCheckToBeDeleted(ctx context.Context, k8sClient client.Client, ac *kueue.AdmissionCheck, deleteAC bool) {
   456  	if ac == nil {
   457  		return
   458  	}
   459  	if deleteAC {
   460  		gomega.Expect(client.IgnoreNotFound(DeleteAdmissionCheck(ctx, k8sClient, ac))).To(gomega.Succeed())
   461  	}
   462  	gomega.EventuallyWithOffset(1, func() error {
   463  		var newAC kueue.AdmissionCheck
   464  		return k8sClient.Get(ctx, client.ObjectKeyFromObject(ac), &newAC)
   465  	}, Timeout, Interval).Should(testing.BeNotFoundError())
   466  }
   467  
   468  func ExpectProvisioningRequestConfigToBeDeleted(ctx context.Context, k8sClient client.Client, prc *kueue.ProvisioningRequestConfig, deleteAC bool) {
   469  	if prc == nil {
   470  		return
   471  	}
   472  	if deleteAC {
   473  		gomega.ExpectWithOffset(1, client.IgnoreNotFound(DeleteProvisioningRequestConfig(ctx, k8sClient, prc))).To(gomega.Succeed())
   474  	}
   475  	gomega.EventuallyWithOffset(1, func() error {
   476  		var newAC kueue.AdmissionCheck
   477  		return k8sClient.Get(ctx, client.ObjectKeyFromObject(prc), &newAC)
   478  	}, Timeout, Interval).Should(testing.BeNotFoundError())
   479  }
   480  
   481  func ExpectClusterQueueToBeDeleted(ctx context.Context, k8sClient client.Client, cq *kueue.ClusterQueue, deleteCq bool) {
   482  	if deleteCq {
   483  		gomega.Expect(DeleteClusterQueue(ctx, k8sClient, cq)).ToNot(gomega.HaveOccurred())
   484  	}
   485  	gomega.EventuallyWithOffset(1, func() error {
   486  		var newCQ kueue.ClusterQueue
   487  		return k8sClient.Get(ctx, client.ObjectKeyFromObject(cq), &newCQ)
   488  	}, Timeout, Interval).Should(testing.BeNotFoundError())
   489  }
   490  
   491  func ExpectResourceFlavorToBeDeleted(ctx context.Context, k8sClient client.Client, rf *kueue.ResourceFlavor, deleteRf bool) {
   492  	if rf == nil {
   493  		return
   494  	}
   495  	if deleteRf {
   496  		gomega.Expect(DeleteResourceFlavor(ctx, k8sClient, rf)).To(gomega.Succeed())
   497  	}
   498  	gomega.EventuallyWithOffset(1, func() error {
   499  		var newRF kueue.ResourceFlavor
   500  		return k8sClient.Get(ctx, client.ObjectKeyFromObject(rf), &newRF)
   501  	}, Timeout, Interval).Should(testing.BeNotFoundError())
   502  }
   503  
   504  func ExpectCQResourceNominalQuota(cq *kueue.ClusterQueue, flavor, resource string, v float64) {
   505  	metric := metrics.ClusterQueueResourceNominalQuota.WithLabelValues(cq.Spec.Cohort, cq.Name, flavor, resource)
   506  	gomega.EventuallyWithOffset(1, func() float64 {
   507  		v, err := testutil.GetGaugeMetricValue(metric)
   508  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   509  		return v
   510  	}, Timeout, Interval).Should(gomega.Equal(v))
   511  }
   512  
   513  func ExpectCQResourceBorrowingQuota(cq *kueue.ClusterQueue, flavor, resource string, v float64) {
   514  	metric := metrics.ClusterQueueResourceBorrowingLimit.WithLabelValues(cq.Spec.Cohort, cq.Name, flavor, resource)
   515  	gomega.EventuallyWithOffset(1, func() float64 {
   516  		v, err := testutil.GetGaugeMetricValue(metric)
   517  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   518  		return v
   519  	}, Timeout, Interval).Should(gomega.Equal(v))
   520  }
   521  
   522  func ExpectCQResourceLendingQuota(cq *kueue.ClusterQueue, flavor, resource string, v float64) {
   523  	metric := metrics.ClusterQueueResourceLendingLimit.WithLabelValues(cq.Spec.Cohort, cq.Name, flavor, resource)
   524  	gomega.EventuallyWithOffset(1, func() float64 {
   525  		v, err := testutil.GetGaugeMetricValue(metric)
   526  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   527  		return v
   528  	}, Timeout, Interval).Should(gomega.Equal(v))
   529  }
   530  
   531  func ExpectCQResourceReservations(cq *kueue.ClusterQueue, flavor, resource string, v float64) {
   532  	metric := metrics.ClusterQueueResourceReservations.WithLabelValues(cq.Spec.Cohort, cq.Name, flavor, resource)
   533  	gomega.EventuallyWithOffset(1, func() float64 {
   534  		v, err := testutil.GetGaugeMetricValue(metric)
   535  		gomega.Expect(err).ToNot(gomega.HaveOccurred())
   536  		return v
   537  	}, Timeout, Interval).Should(gomega.Equal(v))
   538  }
   539  
   540  func SetQuotaReservation(ctx context.Context, k8sClient client.Client, wl *kueue.Workload, admission *kueue.Admission) error {
   541  	wl = wl.DeepCopy()
   542  	if admission == nil {
   543  		workload.UnsetQuotaReservationWithCondition(wl, "EvictedByTest", "Evicted By Test")
   544  	} else {
   545  		workload.SetQuotaReservation(wl, admission)
   546  	}
   547  	return workload.ApplyAdmissionStatus(ctx, k8sClient, wl, false)
   548  }
   549  
   550  // SyncAdmittedConditionForWorkloads sets the Admission condition of the provided workloads based on
   551  // the state of quota reservation and admission checks. It should be use in tests that are not running
   552  // the workload controller.
   553  func SyncAdmittedConditionForWorkloads(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   554  	var updatedWorkload kueue.Workload
   555  	for _, wl := range wls {
   556  		gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   557  		if workload.SyncAdmittedCondition(&updatedWorkload) {
   558  			gomega.ExpectWithOffset(1, workload.ApplyAdmissionStatus(ctx, k8sClient, &updatedWorkload, false)).To(gomega.Succeed())
   559  		}
   560  	}
   561  }
   562  
   563  func FinishEvictionForWorkloads(ctx context.Context, k8sClient client.Client, wls ...*kueue.Workload) {
   564  	gomega.EventuallyWithOffset(1, func() int {
   565  		evicting := 0
   566  		var updatedWorkload kueue.Workload
   567  		for _, wl := range wls {
   568  			gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   569  			if cond := apimeta.FindStatusCondition(updatedWorkload.Status.Conditions, kueue.WorkloadEvicted); cond != nil && cond.Status == metav1.ConditionTrue {
   570  				evicting++
   571  			}
   572  		}
   573  		return evicting
   574  	}, Timeout, Interval).Should(gomega.Equal(len(wls)), "Not enough workloads were marked for eviction")
   575  	// unset the quota reservation
   576  	for i := range wls {
   577  		key := client.ObjectKeyFromObject(wls[i])
   578  		gomega.EventuallyWithOffset(1, func() error {
   579  			var updatedWorkload kueue.Workload
   580  			if err := k8sClient.Get(ctx, key, &updatedWorkload); err != nil {
   581  				return err
   582  			}
   583  
   584  			if apimeta.IsStatusConditionTrue(updatedWorkload.Status.Conditions, kueue.WorkloadQuotaReserved) {
   585  				workload.UnsetQuotaReservationWithCondition(&updatedWorkload, "Pending", "By test")
   586  				return workload.ApplyAdmissionStatus(ctx, k8sClient, &updatedWorkload, true)
   587  			}
   588  			return nil
   589  		}, Timeout, Interval).Should(gomega.Succeed(), fmt.Sprintf("Unable to unset quota reservation for %q", key))
   590  	}
   591  }
   592  
   593  func SetAdmissionCheckActive(ctx context.Context, k8sClient client.Client, admissionCheck *kueue.AdmissionCheck, status metav1.ConditionStatus) {
   594  	gomega.EventuallyWithOffset(1, func() error {
   595  		var updatedAc kueue.AdmissionCheck
   596  		err := k8sClient.Get(ctx, client.ObjectKeyFromObject(admissionCheck), &updatedAc)
   597  		if err != nil {
   598  			return err
   599  		}
   600  		apimeta.SetStatusCondition(&updatedAc.Status.Conditions, metav1.Condition{
   601  			Type:    kueue.AdmissionCheckActive,
   602  			Status:  status,
   603  			Reason:  "ByTest",
   604  			Message: "by test",
   605  		})
   606  		return k8sClient.Status().Update(ctx, &updatedAc)
   607  	}, Timeout, Interval).Should(gomega.Succeed())
   608  }
   609  
   610  func SetWorkloadsAdmissionCheck(ctx context.Context, k8sClient client.Client, wl *kueue.Workload, check string, state kueue.CheckState, expectExisting bool) {
   611  	var updatedWorkload kueue.Workload
   612  	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
   613  		g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedWorkload)).To(gomega.Succeed())
   614  		if expectExisting {
   615  			currentCheck := workload.FindAdmissionCheck(updatedWorkload.Status.AdmissionChecks, check)
   616  			g.Expect(currentCheck).NotTo(gomega.BeNil(), "the check %s was not found in %s", check, workload.Key(wl))
   617  			currentCheck.State = state
   618  		} else {
   619  			workload.SetAdmissionCheckState(&updatedWorkload.Status.AdmissionChecks, kueue.AdmissionCheckState{
   620  				Name:  check,
   621  				State: state,
   622  			})
   623  		}
   624  		g.Expect(k8sClient.Status().Update(ctx, &updatedWorkload)).To(gomega.Succeed())
   625  	}, Timeout, Interval).Should(gomega.Succeed())
   626  }
   627  
   628  func AwaitAndVerifyWorkloadQueueName(ctx context.Context, client client.Client, createdWorkload *kueue.Workload, wlLookupKey types.NamespacedName, jobQueueName string) {
   629  	gomega.EventuallyWithOffset(1, func() bool {
   630  		if err := client.Get(ctx, wlLookupKey, createdWorkload); err != nil {
   631  			return false
   632  		}
   633  		return createdWorkload.Spec.QueueName == jobQueueName
   634  	}, Timeout, Interval).Should(gomega.BeTrue())
   635  }
   636  
   637  func AwaitAndVerifyCreatedWorkload(ctx context.Context, client client.Client, wlLookupKey types.NamespacedName, createdJob metav1.Object) *kueue.Workload {
   638  	createdWorkload := &kueue.Workload{}
   639  	gomega.EventuallyWithOffset(1, func() error {
   640  		return client.Get(ctx, wlLookupKey, createdWorkload)
   641  	}, Timeout, Interval).Should(gomega.Succeed())
   642  	gomega.ExpectWithOffset(1, metav1.IsControlledBy(createdWorkload, createdJob)).To(gomega.BeTrue(), "The Workload should be owned by the Job")
   643  	return createdWorkload
   644  }
   645  
   646  func VerifyWorkloadPriority(createdWorkload *kueue.Workload, priorityClassName string, priorityValue int32) {
   647  	ginkgo.By("checking the workload is created with priority and priorityName")
   648  	gomega.ExpectWithOffset(1, createdWorkload.Spec.PriorityClassName).Should(gomega.Equal(priorityClassName))
   649  	gomega.ExpectWithOffset(1, *createdWorkload.Spec.Priority).Should(gomega.Equal(int32(priorityValue)))
   650  }
   651  
   652  func SetPodsPhase(ctx context.Context, k8sClient client.Client, phase corev1.PodPhase, pods ...*corev1.Pod) {
   653  	for _, p := range pods {
   654  		updatedPod := corev1.Pod{}
   655  		gomega.ExpectWithOffset(1, k8sClient.Get(ctx, client.ObjectKeyFromObject(p), &updatedPod)).To(gomega.Succeed())
   656  		updatedPod.Status.Phase = phase
   657  		gomega.ExpectWithOffset(1, k8sClient.Status().Update(ctx, &updatedPod)).To(gomega.Succeed())
   658  	}
   659  }
   660  
   661  func ExpectPodUnsuspendedWithNodeSelectors(ctx context.Context, k8sClient client.Client, key types.NamespacedName, ns map[string]string) {
   662  	createdPod := &corev1.Pod{}
   663  	gomega.EventuallyWithOffset(1, func(g gomega.Gomega) {
   664  		g.Expect(k8sClient.Get(ctx, key, createdPod)).To(gomega.Succeed())
   665  		g.Expect(createdPod.Spec.SchedulingGates).NotTo(gomega.ContainElement(corev1.PodSchedulingGate{Name: pod.SchedulingGateName}))
   666  		g.Expect(createdPod.Spec.NodeSelector).To(gomega.BeComparableTo(ns))
   667  	}, Timeout, Interval).Should(gomega.Succeed())
   668  }
   669  
   670  func ExpectPodsFinalized(ctx context.Context, k8sClient client.Client, keys ...types.NamespacedName) {
   671  	for _, key := range keys {
   672  		createdPod := &corev1.Pod{}
   673  		gomega.EventuallyWithOffset(1, func(g gomega.Gomega) []string {
   674  			g.Expect(k8sClient.Get(ctx, key, createdPod)).To(gomega.Succeed())
   675  			return createdPod.Finalizers
   676  		}, Timeout, Interval).Should(gomega.BeEmpty(), "Expected pod to be finalized")
   677  	}
   678  }