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