sigs.k8s.io/kueue@v0.6.2/test/integration/webhook/workload_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  package webhook
    15  
    16  import (
    17  	"fmt"
    18  
    19  	"github.com/onsi/ginkgo/v2"
    20  	"github.com/onsi/gomega"
    21  	corev1 "k8s.io/api/core/v1"
    22  	schedulingv1 "k8s.io/api/scheduling/v1"
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/utils/ptr"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    30  	"sigs.k8s.io/kueue/pkg/constants"
    31  	"sigs.k8s.io/kueue/pkg/util/testing"
    32  	"sigs.k8s.io/kueue/test/util"
    33  )
    34  
    35  var ns *corev1.Namespace
    36  
    37  const (
    38  	workloadName    = "workload-test"
    39  	podSetsMaxItems = 8
    40  )
    41  
    42  var _ = ginkgo.BeforeEach(func() {
    43  	ns = &corev1.Namespace{
    44  		ObjectMeta: metav1.ObjectMeta{
    45  			GenerateName: "core-",
    46  		},
    47  	}
    48  	gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
    49  })
    50  
    51  var _ = ginkgo.AfterEach(func() {
    52  	gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
    53  })
    54  
    55  var _ = ginkgo.Describe("Workload defaulting webhook", func() {
    56  	ginkgo.Context("When creating a Workload", func() {
    57  		ginkgo.It("Should set default podSet name", func() {
    58  			ginkgo.By("Creating a new Workload")
    59  			// Not using the wrappers to avoid hiding any defaulting.
    60  			workload := kueue.Workload{
    61  				ObjectMeta: metav1.ObjectMeta{Name: workloadName, Namespace: ns.Name},
    62  				Spec: kueue.WorkloadSpec{
    63  					PodSets: []kueue.PodSet{
    64  						{
    65  							Count: 1,
    66  							Template: corev1.PodTemplateSpec{
    67  								Spec: corev1.PodSpec{
    68  									Containers: []corev1.Container{},
    69  								},
    70  							},
    71  						},
    72  					},
    73  				},
    74  			}
    75  			gomega.Expect(k8sClient.Create(ctx, &workload)).Should(gomega.Succeed())
    76  
    77  			created := &kueue.Workload{}
    78  			gomega.Expect(k8sClient.Get(ctx, types.NamespacedName{
    79  				Name:      workload.Name,
    80  				Namespace: workload.Namespace,
    81  			}, created)).Should(gomega.Succeed())
    82  
    83  			gomega.Expect(created.Spec.PodSets[0].Name).Should(gomega.Equal(kueue.DefaultPodSetName))
    84  		})
    85  	})
    86  })
    87  
    88  var _ = ginkgo.Describe("Workload validating webhook", func() {
    89  	ginkgo.Context("When creating a Workload", func() {
    90  
    91  		ginkgo.It("Should have valid PriorityClassName when creating", func() {
    92  			ginkgo.By("Creating a new Workload")
    93  			workload := testing.MakeWorkload(workloadName, ns.Name).
    94  				PriorityClass("invalid_class").
    95  				Obj()
    96  			err := k8sClient.Create(ctx, workload)
    97  			gomega.Expect(err).Should(gomega.HaveOccurred())
    98  			gomega.Expect(errors.IsForbidden(err)).Should(gomega.BeTrue(), "error: %v", err)
    99  		})
   100  
   101  		ginkgo.DescribeTable("Should have valid PodSet when creating", func(podSetsCapacity int, podSetCount int, isInvalid bool) {
   102  			podSets := make([]kueue.PodSet, podSetsCapacity)
   103  			for i := range podSets {
   104  				podSets[i] = *testing.MakePodSet(fmt.Sprintf("ps%d", i), podSetCount).Obj()
   105  			}
   106  			workload := testing.MakeWorkload(workloadName, ns.Name).PodSets(podSets...).Obj()
   107  			err := k8sClient.Create(ctx, workload)
   108  			if isInvalid {
   109  				gomega.Expect(err).Should(gomega.HaveOccurred())
   110  				gomega.Expect(errors.IsInvalid(err)).Should(gomega.BeTrue(), "error: %v", err)
   111  			} else {
   112  				gomega.Expect(err).Should(gomega.Succeed())
   113  			}
   114  		},
   115  			ginkgo.Entry("podSets count less than 1", 0, 1, true),
   116  			ginkgo.Entry("podSets count more than 8", podSetsMaxItems+1, 1, true),
   117  			ginkgo.Entry("invalid podSet.Count", 3, 0, true),
   118  			ginkgo.Entry("valid podSet", 3, 3, false),
   119  		)
   120  	})
   121  
   122  	ginkgo.Context("When updating a Workload", func() {
   123  		var (
   124  			updatedQueueWorkload  kueue.Workload
   125  			finalQueueWorkload    kueue.Workload
   126  			workloadPriorityClass *kueue.WorkloadPriorityClass
   127  			priorityClass         *schedulingv1.PriorityClass
   128  		)
   129  		ginkgo.BeforeEach(func() {
   130  			workloadPriorityClass = testing.MakeWorkloadPriorityClass("workload-priority-class").PriorityValue(200).Obj()
   131  			priorityClass = testing.MakePriorityClass("priority-class").PriorityValue(100).Obj()
   132  			gomega.Expect(k8sClient.Create(ctx, workloadPriorityClass)).To(gomega.Succeed())
   133  			gomega.Expect(k8sClient.Create(ctx, priorityClass)).To(gomega.Succeed())
   134  
   135  		})
   136  		ginkgo.AfterEach(func() {
   137  			gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
   138  			gomega.Expect(k8sClient.Delete(ctx, workloadPriorityClass)).To(gomega.Succeed())
   139  			gomega.Expect(k8sClient.Delete(ctx, priorityClass)).To(gomega.Succeed())
   140  		})
   141  
   142  		ginkgo.It("Should allow the change of priority", func() {
   143  			ginkgo.By("Creating a new Workload")
   144  			workload := testing.MakeWorkload(workloadName, ns.Name).Obj()
   145  			gomega.Expect(k8sClient.Create(ctx, workload)).Should(gomega.Succeed())
   146  
   147  			ginkgo.By("Updating the priority")
   148  			gomega.Eventually(func() error {
   149  				var newWL kueue.Workload
   150  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   151  				newWL.Spec.Priority = ptr.To[int32](10)
   152  				return k8sClient.Update(ctx, &newWL)
   153  			}, util.Timeout, util.Interval).Should(gomega.Succeed())
   154  		})
   155  
   156  		ginkgo.It("Should forbid the change of spec.podSet", func() {
   157  			ginkgo.By("Creating a new Workload")
   158  			workload := testing.MakeWorkload(workloadName, ns.Name).Obj()
   159  			gomega.Expect(k8sClient.Create(ctx, workload)).Should(gomega.Succeed())
   160  			gomega.Expect(util.SetQuotaReservation(ctx, k8sClient, workload, testing.MakeAdmission("cq").Obj())).Should(gomega.Succeed())
   161  
   162  			ginkgo.By("Updating podSet")
   163  			gomega.Eventually(func() error {
   164  				var newWL kueue.Workload
   165  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   166  				newWL.Spec.PodSets[0].Count = 10
   167  				return k8sClient.Update(ctx, &newWL)
   168  			}, util.Timeout, util.Interval).Should(testing.BeForbiddenError())
   169  		})
   170  
   171  		ginkgo.It("Should forbid the change of spec.queueName of an admitted workload", func() {
   172  			ginkgo.By("Creating and admitting a new Workload")
   173  			workload := testing.MakeWorkload(workloadName, ns.Name).Queue("queue1").Obj()
   174  			gomega.Expect(k8sClient.Create(ctx, workload)).Should(gomega.Succeed())
   175  			gomega.EventuallyWithOffset(1, func() error {
   176  				var newWL kueue.Workload
   177  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   178  				return util.SetQuotaReservation(ctx, k8sClient, &newWL, testing.MakeAdmission("cq").Obj())
   179  			}, util.Timeout, util.Interval).Should(gomega.Succeed())
   180  			ginkgo.By("Updating queueName")
   181  			gomega.Eventually(func() error {
   182  				var newWL kueue.Workload
   183  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   184  				newWL.Spec.QueueName = "queue2"
   185  				return k8sClient.Update(ctx, &newWL)
   186  			}, util.Timeout, util.Interval).Should(testing.BeForbiddenError())
   187  		})
   188  
   189  		ginkgo.It("Should forbid the change of spec.admission", func() {
   190  			ginkgo.By("Creating a new Workload")
   191  			workload := testing.MakeWorkload(workloadName, ns.Name).Obj()
   192  			gomega.Expect(k8sClient.Create(ctx, workload)).Should(gomega.Succeed())
   193  
   194  			ginkgo.By("Admitting the Workload")
   195  			gomega.Eventually(func() error {
   196  				var newWL kueue.Workload
   197  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   198  				newWL.Status.Admission = testing.MakeAdmission("cluster-queue").Obj()
   199  				return k8sClient.Status().Update(ctx, &newWL)
   200  			}, util.Timeout, util.Interval).Should(gomega.Succeed())
   201  
   202  			ginkgo.By("Updating queueName")
   203  			gomega.Eventually(func() error {
   204  				var newWL kueue.Workload
   205  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(workload), &newWL)).To(gomega.Succeed())
   206  				newWL.Status.Admission.ClusterQueue = "foo-cluster-queue"
   207  				return k8sClient.Status().Update(ctx, &newWL)
   208  			}, util.Timeout, util.Interval).Should(testing.BeForbiddenError())
   209  
   210  		})
   211  
   212  		ginkgo.It("Should have priority once priorityClassName is set", func() {
   213  			ginkgo.By("Creating a new Workload")
   214  			workload := testing.MakeWorkload(workloadName, ns.Name).PriorityClass("priority").Obj()
   215  			err := k8sClient.Create(ctx, workload)
   216  			gomega.Expect(err).Should(gomega.HaveOccurred())
   217  			gomega.Expect(errors.IsForbidden(err)).Should(gomega.BeTrue(), "error: %v", err)
   218  		})
   219  
   220  		ginkgo.It("workload's priority should be mutable when referencing WorkloadPriorityClass", func() {
   221  			ginkgo.By("creating workload")
   222  			wl := testing.MakeWorkload("wl", ns.Name).Queue("lq").Request(corev1.ResourceCPU, "1").
   223  				PriorityClass("workload-priority-class").PriorityClassSource(constants.WorkloadPriorityClassSource).Priority(200).Obj()
   224  			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
   225  			gomega.Eventually(func() []metav1.Condition {
   226  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedQueueWorkload)).To(gomega.Succeed())
   227  				return updatedQueueWorkload.Status.Conditions
   228  			}, util.Timeout, util.Interval).ShouldNot(gomega.BeNil())
   229  			initialPriority := int32(200)
   230  			gomega.Expect(updatedQueueWorkload.Spec.Priority).To(gomega.Equal(&initialPriority))
   231  
   232  			ginkgo.By("Updating workload's priority")
   233  			updatedPriority := int32(150)
   234  			updatedQueueWorkload.Spec.Priority = &updatedPriority
   235  			gomega.Expect(k8sClient.Update(ctx, &updatedQueueWorkload)).To(gomega.Succeed())
   236  			gomega.Eventually(func() *int32 {
   237  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&updatedQueueWorkload), &finalQueueWorkload)).To(gomega.Succeed())
   238  				return finalQueueWorkload.Spec.Priority
   239  			}, util.Timeout, util.Interval).Should(gomega.Equal(&updatedPriority))
   240  		})
   241  
   242  		ginkgo.It("workload's priority should be mutable when referencing PriorityClass", func() {
   243  			ginkgo.By("creating workload")
   244  			wl := testing.MakeWorkload("wl", ns.Name).Queue("lq").Request(corev1.ResourceCPU, "1").
   245  				PriorityClass("priority-class").PriorityClassSource(constants.PodPriorityClassSource).Priority(100).Obj()
   246  			gomega.Expect(k8sClient.Create(ctx, wl)).To(gomega.Succeed())
   247  			gomega.Eventually(func() []metav1.Condition {
   248  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(wl), &updatedQueueWorkload)).To(gomega.Succeed())
   249  				return updatedQueueWorkload.Status.Conditions
   250  			}, util.Timeout, util.Interval).ShouldNot(gomega.BeNil())
   251  			initialPriority := int32(100)
   252  			gomega.Expect(updatedQueueWorkload.Spec.Priority).To(gomega.Equal(&initialPriority))
   253  
   254  			ginkgo.By("Updating workload's priority")
   255  			updatedPriority := int32(50)
   256  			updatedQueueWorkload.Spec.Priority = &updatedPriority
   257  			gomega.Expect(k8sClient.Update(ctx, &updatedQueueWorkload)).To(gomega.Succeed())
   258  			gomega.Eventually(func() *int32 {
   259  				gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&updatedQueueWorkload), &finalQueueWorkload)).To(gomega.Succeed())
   260  				return finalQueueWorkload.Spec.Priority
   261  			}, util.Timeout, util.Interval).Should(gomega.Equal(&updatedPriority))
   262  		})
   263  	})
   264  })