sigs.k8s.io/kueue@v0.6.2/test/integration/controller/core/localqueue_controller_test.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 core 18 19 import ( 20 "github.com/onsi/ginkgo/v2" 21 "github.com/onsi/gomega" 22 corev1 "k8s.io/api/core/v1" 23 "k8s.io/apimachinery/pkg/api/resource" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "sigs.k8s.io/controller-runtime/pkg/client" 26 27 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 28 "sigs.k8s.io/kueue/pkg/util/testing" 29 "sigs.k8s.io/kueue/test/integration/framework" 30 "sigs.k8s.io/kueue/test/util" 31 ) 32 33 // +kubebuilder:docs-gen:collapse=Imports 34 35 var _ = ginkgo.Describe("Queue controller", ginkgo.Ordered, ginkgo.ContinueOnFailure, func() { 36 const ( 37 flavorModelC = "model-c" 38 flavorModelD = "model-d" 39 ) 40 var ( 41 ns *corev1.Namespace 42 queue *kueue.LocalQueue 43 clusterQueue *kueue.ClusterQueue 44 resourceFlavors = []kueue.ResourceFlavor{ 45 *testing.MakeResourceFlavor(flavorModelC).Label(resourceGPU.String(), flavorModelC).Obj(), 46 *testing.MakeResourceFlavor(flavorModelD).Label(resourceGPU.String(), flavorModelD).Obj(), 47 } 48 ac *kueue.AdmissionCheck 49 ) 50 51 ginkgo.BeforeAll(func() { 52 fwk = &framework.Framework{CRDPath: crdPath, WebhookPath: webhookPath} 53 cfg = fwk.Init() 54 ctx, k8sClient = fwk.RunManager(cfg, managerSetup) 55 }) 56 ginkgo.AfterAll(func() { 57 fwk.Teardown() 58 }) 59 60 ginkgo.BeforeEach(func() { 61 ns = &corev1.Namespace{ 62 ObjectMeta: metav1.ObjectMeta{ 63 GenerateName: "core-queue-", 64 }, 65 } 66 gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) 67 }) 68 69 ginkgo.BeforeEach(func() { 70 ac = testing.MakeAdmissionCheck("ac").ControllerName("ac-controller").Obj() 71 gomega.Expect(k8sClient.Create(ctx, ac)).To(gomega.Succeed()) 72 util.SetAdmissionCheckActive(ctx, k8sClient, ac, metav1.ConditionTrue) 73 74 clusterQueue = testing.MakeClusterQueue("cluster-queue.queue-controller"). 75 ResourceGroup( 76 *testing.MakeFlavorQuotas(flavorModelC).Resource(resourceGPU, "5", "5").Obj(), 77 *testing.MakeFlavorQuotas(flavorModelD).Resource(resourceGPU, "5", "5").Obj(), 78 ). 79 Cohort("cohort"). 80 AdmissionChecks(ac.Name). 81 Obj() 82 queue = testing.MakeLocalQueue("queue", ns.Name).ClusterQueue(clusterQueue.Name).Obj() 83 gomega.Expect(k8sClient.Create(ctx, queue)).To(gomega.Succeed()) 84 }) 85 86 ginkgo.AfterEach(func() { 87 gomega.Expect(util.DeleteLocalQueue(ctx, k8sClient, queue)).To(gomega.Succeed()) 88 util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, clusterQueue, true) 89 for _, rf := range resourceFlavors { 90 util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, &rf, true) 91 } 92 util.ExpectAdmissionCheckToBeDeleted(ctx, k8sClient, ac, true) 93 }) 94 95 ginkgo.It("Should update conditions when clusterQueues that its localQueue references are updated", func() { 96 gomega.Eventually(func() []metav1.Condition { 97 var updatedQueue kueue.LocalQueue 98 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 99 return updatedQueue.Status.Conditions 100 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo([]metav1.Condition{ 101 { 102 Type: kueue.LocalQueueActive, 103 Status: metav1.ConditionFalse, 104 Reason: "ClusterQueueDoesNotExist", 105 Message: "Can't submit new workloads to clusterQueue", 106 }, 107 }, ignoreConditionTimestamps)) 108 109 ginkgo.By("Creating a clusterQueue") 110 gomega.Expect(k8sClient.Create(ctx, clusterQueue)).To(gomega.Succeed()) 111 gomega.Eventually(func() []metav1.Condition { 112 var updatedQueue kueue.LocalQueue 113 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 114 return updatedQueue.Status.Conditions 115 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo([]metav1.Condition{ 116 { 117 Type: kueue.LocalQueueActive, 118 Status: metav1.ConditionFalse, 119 Reason: "ClusterQueueIsInactive", 120 Message: "Can't submit new workloads to clusterQueue", 121 }, 122 }, ignoreConditionTimestamps)) 123 124 ginkgo.By("Creating resourceFlavors") 125 for _, rf := range resourceFlavors { 126 gomega.Expect(k8sClient.Create(ctx, &rf)).To(gomega.Succeed()) 127 } 128 gomega.Eventually(func() []metav1.Condition { 129 var updatedCQ kueue.ClusterQueue 130 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(clusterQueue), &updatedCQ)).To(gomega.Succeed()) 131 return updatedCQ.Status.Conditions 132 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo([]metav1.Condition{ 133 { 134 Type: kueue.ClusterQueueActive, 135 Status: metav1.ConditionTrue, 136 Reason: "Ready", 137 Message: "Can admit new workloads", 138 }, 139 }, ignoreConditionTimestamps)) 140 gomega.Eventually(func() []metav1.Condition { 141 var updatedQueue kueue.LocalQueue 142 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 143 return updatedQueue.Status.Conditions 144 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo([]metav1.Condition{ 145 { 146 Type: kueue.LocalQueueActive, 147 Status: metav1.ConditionTrue, 148 Reason: "Ready", 149 Message: "Can submit new workloads to clusterQueue", 150 }, 151 }, ignoreConditionTimestamps)) 152 153 ginkgo.By("Deleting a clusterQueue") 154 gomega.Expect(k8sClient.Delete(ctx, clusterQueue)).To(gomega.Succeed()) 155 gomega.Eventually(func() []metav1.Condition { 156 var updatedQueue kueue.LocalQueue 157 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 158 return updatedQueue.Status.Conditions 159 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo([]metav1.Condition{ 160 { 161 Type: kueue.LocalQueueActive, 162 Status: metav1.ConditionFalse, 163 Reason: "ClusterQueueDoesNotExist", 164 Message: "Can't submit new workloads to clusterQueue", 165 }, 166 }, ignoreConditionTimestamps)) 167 }) 168 169 ginkgo.It("Should update status when workloads are created", func() { 170 ginkgo.By("Creating resourceFlavors") 171 for _, rf := range resourceFlavors { 172 gomega.Expect(k8sClient.Create(ctx, &rf)).To(gomega.Succeed()) 173 } 174 ginkgo.By("Creating a clusterQueue") 175 gomega.Expect(k8sClient.Create(ctx, clusterQueue)).To(gomega.Succeed()) 176 177 workloads := []*kueue.Workload{ 178 testing.MakeWorkload("one", ns.Name). 179 Queue(queue.Name). 180 Request(resourceGPU, "2"). 181 Obj(), 182 testing.MakeWorkload("two", ns.Name). 183 Queue(queue.Name). 184 Request(resourceGPU, "3"). 185 Obj(), 186 testing.MakeWorkload("three", ns.Name). 187 Queue(queue.Name). 188 Request(resourceGPU, "1"). 189 Obj(), 190 } 191 admissions := []*kueue.Admission{ 192 testing.MakeAdmission(clusterQueue.Name). 193 Assignment(resourceGPU, flavorModelC, "2").Obj(), 194 testing.MakeAdmission(clusterQueue.Name). 195 Assignment(resourceGPU, flavorModelC, "3").Obj(), 196 testing.MakeAdmission(clusterQueue.Name). 197 Assignment(resourceGPU, flavorModelD, "1").Obj(), 198 } 199 200 ginkgo.By("Creating workloads") 201 for _, w := range workloads { 202 gomega.Expect(k8sClient.Create(ctx, w)).To(gomega.Succeed()) 203 } 204 205 emptyUsage := []kueue.LocalQueueFlavorUsage{ 206 { 207 Name: flavorModelC, 208 Resources: []kueue.LocalQueueResourceUsage{ 209 { 210 Name: resourceGPU, 211 Total: resource.MustParse("0"), 212 }, 213 }, 214 }, 215 { 216 Name: flavorModelD, 217 Resources: []kueue.LocalQueueResourceUsage{ 218 { 219 Name: resourceGPU, 220 Total: resource.MustParse("0"), 221 }, 222 }, 223 }, 224 } 225 226 gomega.Eventually(func() kueue.LocalQueueStatus { 227 var updatedQueue kueue.LocalQueue 228 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 229 return updatedQueue.Status 230 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(kueue.LocalQueueStatus{ 231 ReservingWorkloads: 0, 232 AdmittedWorkloads: 0, 233 PendingWorkloads: 3, 234 Conditions: []metav1.Condition{ 235 { 236 Type: kueue.LocalQueueActive, 237 Status: metav1.ConditionTrue, 238 Reason: "Ready", 239 Message: "Can submit new workloads to clusterQueue", 240 }, 241 }, 242 FlavorsReservation: emptyUsage, 243 FlavorUsage: emptyUsage, 244 }, ignoreConditionTimestamps)) 245 246 ginkgo.By("Setting the workloads quota reservation") 247 for i, w := range workloads { 248 gomega.Eventually(func() error { 249 var newWL kueue.Workload 250 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(w), &newWL)).To(gomega.Succeed()) 251 return util.SetQuotaReservation(ctx, k8sClient, &newWL, admissions[i]) 252 }, util.Timeout, util.Interval).Should(gomega.Succeed()) 253 } 254 255 fullUsage := []kueue.LocalQueueFlavorUsage{ 256 { 257 Name: flavorModelC, 258 Resources: []kueue.LocalQueueResourceUsage{ 259 { 260 Name: resourceGPU, 261 Total: resource.MustParse("5"), 262 }, 263 }, 264 }, 265 { 266 Name: flavorModelD, 267 Resources: []kueue.LocalQueueResourceUsage{ 268 { 269 Name: resourceGPU, 270 Total: resource.MustParse("1"), 271 }, 272 }, 273 }, 274 } 275 276 gomega.Eventually(func() kueue.LocalQueueStatus { 277 var updatedQueue kueue.LocalQueue 278 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 279 return updatedQueue.Status 280 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(kueue.LocalQueueStatus{ 281 ReservingWorkloads: 3, 282 AdmittedWorkloads: 0, 283 PendingWorkloads: 0, 284 Conditions: []metav1.Condition{ 285 { 286 Type: kueue.LocalQueueActive, 287 Status: metav1.ConditionTrue, 288 Reason: "Ready", 289 Message: "Can submit new workloads to clusterQueue", 290 }, 291 }, 292 FlavorsReservation: fullUsage, 293 FlavorUsage: emptyUsage, 294 }, ignoreConditionTimestamps)) 295 296 ginkgo.By("Setting the workloads admission checks") 297 for _, w := range workloads { 298 util.SetWorkloadsAdmissionCheck(ctx, k8sClient, w, ac.Name, kueue.CheckStateReady, true) 299 } 300 301 gomega.Eventually(func() kueue.LocalQueueStatus { 302 var updatedQueue kueue.LocalQueue 303 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 304 return updatedQueue.Status 305 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(kueue.LocalQueueStatus{ 306 ReservingWorkloads: 3, 307 AdmittedWorkloads: 3, 308 PendingWorkloads: 0, 309 Conditions: []metav1.Condition{ 310 { 311 Type: kueue.LocalQueueActive, 312 Status: metav1.ConditionTrue, 313 Reason: "Ready", 314 Message: "Can submit new workloads to clusterQueue", 315 }, 316 }, 317 FlavorsReservation: fullUsage, 318 FlavorUsage: fullUsage, 319 }, ignoreConditionTimestamps)) 320 321 ginkgo.By("Finishing workloads") 322 util.FinishWorkloads(ctx, k8sClient, workloads...) 323 gomega.Eventually(func() kueue.LocalQueueStatus { 324 var updatedQueue kueue.LocalQueue 325 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(queue), &updatedQueue)).To(gomega.Succeed()) 326 return updatedQueue.Status 327 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(kueue.LocalQueueStatus{ 328 Conditions: []metav1.Condition{ 329 { 330 Type: kueue.LocalQueueActive, 331 Status: metav1.ConditionTrue, 332 Reason: "Ready", 333 Message: "Can submit new workloads to clusterQueue", 334 }, 335 }, 336 FlavorsReservation: emptyUsage, 337 FlavorUsage: emptyUsage, 338 }, ignoreConditionTimestamps)) 339 }) 340 })