sigs.k8s.io/kueue@v0.6.2/test/e2e/singlecluster/visibility_test.go (about) 1 /* 2 Copyright 2024 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 e2e 18 19 import ( 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/onsi/ginkgo/v2" 23 "github.com/onsi/gomega" 24 batchv1 "k8s.io/api/batch/v1" 25 corev1 "k8s.io/api/core/v1" 26 rbacv1 "k8s.io/api/rbac/v1" 27 schedulingv1 "k8s.io/api/scheduling/v1" 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 33 visibility "sigs.k8s.io/kueue/apis/visibility/v1alpha1" 34 "sigs.k8s.io/kueue/pkg/util/testing" 35 testingjob "sigs.k8s.io/kueue/pkg/util/testingjobs/job" 36 "sigs.k8s.io/kueue/test/util" 37 ) 38 39 var _ = ginkgo.Describe("Kueue visibility server", func() { 40 const defaultFlavor = "default-flavor" 41 42 // We do not check workload's Name, CreationTimestamp, and its OwnerReference's UID as they are generated at the server-side. 43 var pendingWorkloadsCmpOpts = []cmp.Option{ 44 cmpopts.IgnoreFields(metav1.ObjectMeta{}, "Name"), 45 cmpopts.IgnoreFields(metav1.ObjectMeta{}, "CreationTimestamp"), 46 cmpopts.IgnoreFields(metav1.OwnerReference{}, "UID"), 47 } 48 49 var ( 50 defaultRF *kueue.ResourceFlavor 51 localQueueA *kueue.LocalQueue 52 localQueueB *kueue.LocalQueue 53 clusterQueue *kueue.ClusterQueue 54 nsA *corev1.Namespace 55 nsB *corev1.Namespace 56 blockingJob *batchv1.Job 57 sampleJob2 *batchv1.Job 58 highPriorityClass *schedulingv1.PriorityClass 59 midPriorityClass *schedulingv1.PriorityClass 60 lowPriorityClass *schedulingv1.PriorityClass 61 ) 62 63 ginkgo.BeforeEach(func() { 64 nsA = &corev1.Namespace{ 65 ObjectMeta: metav1.ObjectMeta{ 66 GenerateName: "e2e-", 67 }, 68 } 69 gomega.Expect(k8sClient.Create(ctx, nsA)).To(gomega.Succeed()) 70 nsB = &corev1.Namespace{ 71 ObjectMeta: metav1.ObjectMeta{ 72 GenerateName: "e2e-", 73 }, 74 } 75 gomega.Expect(k8sClient.Create(ctx, nsB)).To(gomega.Succeed()) 76 }) 77 ginkgo.AfterEach(func() { 78 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, nsA)).To(gomega.Succeed()) 79 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, nsB)).To(gomega.Succeed()) 80 }) 81 82 ginkgo.When("There are pending workloads due to capacity maxed by the admitted job", func() { 83 ginkgo.BeforeEach(func() { 84 defaultRF = testing.MakeResourceFlavor(defaultFlavor).Obj() 85 gomega.Expect(k8sClient.Create(ctx, defaultRF)).Should(gomega.Succeed()) 86 87 clusterQueue = testing.MakeClusterQueue("cluster-queue"). 88 ResourceGroup( 89 *testing.MakeFlavorQuotas(defaultFlavor). 90 Resource(corev1.ResourceCPU, "1"). 91 Obj(), 92 ). 93 Obj() 94 gomega.Expect(k8sClient.Create(ctx, clusterQueue)).Should(gomega.Succeed()) 95 96 localQueueA = testing.MakeLocalQueue("a", nsA.Name).ClusterQueue(clusterQueue.Name).Obj() 97 gomega.Expect(k8sClient.Create(ctx, localQueueA)).Should(gomega.Succeed()) 98 99 localQueueB = testing.MakeLocalQueue("b", nsA.Name).ClusterQueue(clusterQueue.Name).Obj() 100 gomega.Expect(k8sClient.Create(ctx, localQueueB)).Should(gomega.Succeed()) 101 102 highPriorityClass = testing.MakePriorityClass("high").PriorityValue(100).Obj() 103 gomega.Expect(k8sClient.Create(ctx, highPriorityClass)) 104 105 midPriorityClass = testing.MakePriorityClass("mid").PriorityValue(75).Obj() 106 gomega.Expect(k8sClient.Create(ctx, midPriorityClass)) 107 108 lowPriorityClass = testing.MakePriorityClass("low").PriorityValue(50).Obj() 109 gomega.Expect(k8sClient.Create(ctx, lowPriorityClass)) 110 111 ginkgo.By("Schedule a job that when admitted workload blocks the queue", func() { 112 blockingJob = testingjob.MakeJob("test-job-1", nsA.Name). 113 Queue(localQueueA.Name). 114 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1m"}). 115 Request(corev1.ResourceCPU, "1"). 116 TerminationGracePeriod(1). 117 BackoffLimit(0). 118 PriorityClass(highPriorityClass.Name). 119 Obj() 120 gomega.Expect(k8sClient.Create(ctx, blockingJob)).Should(gomega.Succeed()) 121 }) 122 ginkgo.By("Ensure the workload is admitted, by awaiting until the job is unsuspended", func() { 123 expectJobUnsuspended(client.ObjectKeyFromObject(blockingJob)) 124 }) 125 }) 126 ginkgo.AfterEach(func() { 127 gomega.Expect(k8sClient.Delete(ctx, lowPriorityClass)).To(gomega.Succeed()) 128 gomega.Expect(k8sClient.Delete(ctx, midPriorityClass)).To(gomega.Succeed()) 129 gomega.Expect(k8sClient.Delete(ctx, highPriorityClass)).To(gomega.Succeed()) 130 gomega.Expect(util.DeleteLocalQueue(ctx, k8sClient, localQueueB)).Should(gomega.Succeed()) 131 gomega.Expect(util.DeleteLocalQueue(ctx, k8sClient, localQueueA)).Should(gomega.Succeed()) 132 gomega.Expect(util.DeleteAllJobsInNamespace(ctx, k8sClient, nsA)).Should(gomega.Succeed()) 133 util.ExpectClusterQueueToBeDeleted(ctx, k8sClient, clusterQueue, true) 134 util.ExpectResourceFlavorToBeDeleted(ctx, k8sClient, defaultRF, true) 135 }) 136 137 ginkgo.It("Should allow fetching information about pending workloads in ClusterQueue", func() { 138 ginkgo.By("Verify there are zero pending workloads", func() { 139 info, err := visibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, clusterQueue.Name, metav1.GetOptions{}) 140 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 141 gomega.Expect(len(info.Items)).Should(gomega.Equal(0)) 142 }) 143 144 ginkgo.By("Schedule a job which is pending due to lower priority", func() { 145 sampleJob2 = testingjob.MakeJob("test-job-2", nsA.Name). 146 Queue(localQueueA.Name). 147 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1ms"}). 148 Request(corev1.ResourceCPU, "1"). 149 PriorityClass(lowPriorityClass.Name). 150 Obj() 151 gomega.Expect(k8sClient.Create(ctx, sampleJob2)).Should(gomega.Succeed()) 152 }) 153 154 ginkgo.By("Verify there is one pending workload", func() { 155 gomega.Eventually(func() int { 156 info, err := visibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, clusterQueue.Name, metav1.GetOptions{}) 157 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 158 return len(info.Items) 159 }, util.Timeout, util.Interval).Should(gomega.Equal(1)) 160 }) 161 162 ginkgo.By("Await for pods to be running", func() { 163 gomega.Eventually(func() int { 164 createdJob := &batchv1.Job{} 165 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(blockingJob), createdJob)).Should(gomega.Succeed()) 166 return int(*createdJob.Status.Ready) 167 }, util.Timeout, util.Interval).Should(gomega.Equal(1)) 168 }) 169 170 ginkgo.By("Terminate execution of the first workload to release the quota", func() { 171 gomega.Expect(util.DeleteAllPodsInNamespace(ctx, k8sClient, nsA)).Should(gomega.Succeed()) 172 }) 173 174 ginkgo.By("Verify there are zero pending workloads, after the second workload is admitted", func() { 175 gomega.Eventually(func() int { 176 info, err := visibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, clusterQueue.Name, metav1.GetOptions{}) 177 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 178 return len(info.Items) 179 }, util.Timeout, util.Interval).Should(gomega.Equal(0)) 180 }) 181 }) 182 183 ginkgo.It("Should allow fetching information about position of pending workloads in ClusterQueue", func() { 184 ginkgo.By("Schedule three different jobs with different priorities and two different LocalQueues", func() { 185 jobCases := []struct { 186 JobName string 187 JobPrioClassName string 188 LocalQueueName string 189 }{ 190 { 191 JobName: "lq-a-high-prio", 192 JobPrioClassName: highPriorityClass.Name, 193 LocalQueueName: localQueueA.Name, 194 }, 195 { 196 JobName: "lq-b-mid-prio", 197 JobPrioClassName: midPriorityClass.Name, 198 LocalQueueName: localQueueB.Name, 199 }, 200 { 201 JobName: "lq-b-low-prio", 202 JobPrioClassName: lowPriorityClass.Name, 203 LocalQueueName: localQueueB.Name, 204 }, 205 } 206 for _, jobCase := range jobCases { 207 job := testingjob.MakeJob(jobCase.JobName, nsA.Name). 208 Queue(jobCase.LocalQueueName). 209 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1ms"}). 210 Request(corev1.ResourceCPU, "1"). 211 PriorityClass(jobCase.JobPrioClassName). 212 Obj() 213 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 214 } 215 }) 216 217 ginkgo.By("Verify their positions and priorities", func() { 218 wantPendingWorkloads := []visibility.PendingWorkload{ 219 { 220 ObjectMeta: metav1.ObjectMeta{ 221 Namespace: nsA.Name, 222 OwnerReferences: defaultOwnerReferenceForJob("lq-a-high-prio"), 223 }, 224 Priority: highPriorityClass.Value, 225 PositionInLocalQueue: 0, 226 PositionInClusterQueue: 0, 227 LocalQueueName: localQueueA.Name, 228 }, 229 { 230 ObjectMeta: metav1.ObjectMeta{ 231 Namespace: nsA.Name, 232 OwnerReferences: defaultOwnerReferenceForJob("lq-b-mid-prio"), 233 }, 234 Priority: midPriorityClass.Value, 235 PositionInLocalQueue: 0, 236 PositionInClusterQueue: 1, 237 LocalQueueName: localQueueB.Name, 238 }, 239 { 240 ObjectMeta: metav1.ObjectMeta{ 241 Namespace: nsA.Name, 242 OwnerReferences: defaultOwnerReferenceForJob("lq-b-low-prio"), 243 }, 244 Priority: lowPriorityClass.Value, 245 PositionInLocalQueue: 1, 246 PositionInClusterQueue: 2, 247 LocalQueueName: localQueueB.Name, 248 }, 249 } 250 gomega.Eventually(func() []visibility.PendingWorkload { 251 info, err := visibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, clusterQueue.Name, metav1.GetOptions{}) 252 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 253 return info.Items 254 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(wantPendingWorkloads, pendingWorkloadsCmpOpts...)) 255 }) 256 }) 257 258 ginkgo.It("Should allow fetching information about pending workloads in LocalQueue", func() { 259 ginkgo.By("Verify there are zero pending workloads", func() { 260 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueA.Name, metav1.GetOptions{}) 261 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 262 gomega.Expect(len(info.Items)).Should(gomega.Equal(0)) 263 }) 264 265 ginkgo.By("Schedule a job which is pending due to lower priority", func() { 266 sampleJob2 = testingjob.MakeJob("test-job-2", nsA.Name). 267 Queue(localQueueA.Name). 268 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1ms"}). 269 Request(corev1.ResourceCPU, "1"). 270 PriorityClass(lowPriorityClass.Name). 271 Obj() 272 gomega.Expect(k8sClient.Create(ctx, sampleJob2)).Should(gomega.Succeed()) 273 }) 274 275 ginkgo.By("Verify there is one pending workload", func() { 276 gomega.Eventually(func() int { 277 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueA.Name, metav1.GetOptions{}) 278 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 279 return len(info.Items) 280 }, util.Timeout, util.Interval).Should(gomega.Equal(1)) 281 }) 282 283 ginkgo.By("Await for pods to be running", func() { 284 gomega.Eventually(func() int { 285 createdJob := &batchv1.Job{} 286 gomega.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(blockingJob), createdJob)).Should(gomega.Succeed()) 287 return int(*createdJob.Status.Ready) 288 }, util.Timeout, util.Interval).Should(gomega.Equal(1)) 289 }) 290 291 ginkgo.By("Terminate execution of the first workload to release the quota", func() { 292 gomega.Expect(util.DeleteAllPodsInNamespace(ctx, k8sClient, nsA)).Should(gomega.Succeed()) 293 }) 294 295 ginkgo.By("Verify there are zero pending workloads, after the second workload is admitted", func() { 296 gomega.Eventually(func() int { 297 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueA.Name, metav1.GetOptions{}) 298 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 299 return len(info.Items) 300 }, util.Timeout, util.Interval).Should(gomega.Equal(0)) 301 }) 302 }) 303 304 ginkgo.It("Should allow fetching information about position of pending workloads from different LocalQueues", func() { 305 ginkgo.By("Schedule three different jobs with different priorities and two different LocalQueues", func() { 306 jobCases := []struct { 307 JobName string 308 JobPrioClassName string 309 LocalQueueName string 310 }{ 311 { 312 JobName: "lq-a-high-prio", 313 JobPrioClassName: highPriorityClass.Name, 314 LocalQueueName: localQueueA.Name, 315 }, 316 { 317 JobName: "lq-b-mid-prio", 318 JobPrioClassName: midPriorityClass.Name, 319 LocalQueueName: localQueueB.Name, 320 }, 321 { 322 JobName: "lq-b-low-prio", 323 JobPrioClassName: lowPriorityClass.Name, 324 LocalQueueName: localQueueB.Name, 325 }, 326 } 327 for _, jobCase := range jobCases { 328 job := testingjob.MakeJob(jobCase.JobName, nsA.Name). 329 Queue(jobCase.LocalQueueName). 330 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1ms"}). 331 Request(corev1.ResourceCPU, "1"). 332 PriorityClass(jobCase.JobPrioClassName). 333 Obj() 334 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 335 } 336 }) 337 338 ginkgo.By("Verify their positions and priorities in LocalQueueA", func() { 339 wantPendingWorkloads := []visibility.PendingWorkload{ 340 { 341 ObjectMeta: metav1.ObjectMeta{ 342 Namespace: nsA.Name, 343 OwnerReferences: defaultOwnerReferenceForJob("lq-a-high-prio"), 344 }, 345 Priority: highPriorityClass.Value, 346 PositionInLocalQueue: 0, 347 PositionInClusterQueue: 0, 348 LocalQueueName: localQueueA.Name, 349 }, 350 } 351 gomega.Eventually(func() []visibility.PendingWorkload { 352 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueA.Name, metav1.GetOptions{}) 353 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 354 return info.Items 355 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(wantPendingWorkloads, pendingWorkloadsCmpOpts...)) 356 }) 357 358 ginkgo.By("Verify their positions and priorities in LocalQueueB", func() { 359 wantPendingWorkloads := []visibility.PendingWorkload{ 360 { 361 ObjectMeta: metav1.ObjectMeta{ 362 Namespace: nsA.Name, 363 OwnerReferences: defaultOwnerReferenceForJob("lq-b-mid-prio"), 364 }, 365 Priority: midPriorityClass.Value, 366 PositionInLocalQueue: 0, 367 PositionInClusterQueue: 1, 368 LocalQueueName: localQueueB.Name, 369 }, 370 { 371 ObjectMeta: metav1.ObjectMeta{ 372 Namespace: nsA.Name, 373 OwnerReferences: defaultOwnerReferenceForJob("lq-b-low-prio"), 374 }, 375 Priority: lowPriorityClass.Value, 376 PositionInLocalQueue: 1, 377 PositionInClusterQueue: 2, 378 LocalQueueName: localQueueB.Name, 379 }, 380 } 381 gomega.Eventually(func() []visibility.PendingWorkload { 382 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueB.Name, metav1.GetOptions{}) 383 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 384 return info.Items 385 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(wantPendingWorkloads, pendingWorkloadsCmpOpts...)) 386 }) 387 }) 388 ginkgo.It("Should allow fetching information about position of pending workloads from different LocalQueues from different Namespaces", func() { 389 390 ginkgo.By("Create a LocalQueue in a different Namespace", func() { 391 localQueueB = testing.MakeLocalQueue("b", nsB.Name).ClusterQueue(clusterQueue.Name).Obj() 392 gomega.Expect(k8sClient.Create(ctx, localQueueB)).Should(gomega.Succeed()) 393 }) 394 395 ginkgo.By("Schedule three different jobs with different priorities and different LocalQueues in different Namespaces", func() { 396 jobCases := []struct { 397 JobName string 398 JobPrioClassName string 399 LocalQueueName string 400 nsName string 401 }{ 402 { 403 JobName: "lq-a-high-prio", 404 JobPrioClassName: highPriorityClass.Name, 405 LocalQueueName: localQueueA.Name, 406 nsName: nsA.Name, 407 }, 408 { 409 JobName: "lq-b-mid-prio", 410 JobPrioClassName: midPriorityClass.Name, 411 LocalQueueName: localQueueB.Name, 412 nsName: nsB.Name, 413 }, 414 { 415 JobName: "lq-b-low-prio", 416 JobPrioClassName: lowPriorityClass.Name, 417 LocalQueueName: localQueueB.Name, 418 nsName: nsB.Name, 419 }, 420 } 421 for _, jobCase := range jobCases { 422 job := testingjob.MakeJob(jobCase.JobName, jobCase.nsName). 423 Queue(jobCase.LocalQueueName). 424 Image("gcr.io/k8s-staging-perf-tests/sleep:v0.1.0", []string{"1ms"}). 425 Request(corev1.ResourceCPU, "1"). 426 PriorityClass(jobCase.JobPrioClassName). 427 Obj() 428 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 429 } 430 }) 431 432 ginkgo.By("Verify their positions and priorities in LocalQueueA", func() { 433 wantPendingWorkloads := []visibility.PendingWorkload{ 434 { 435 ObjectMeta: metav1.ObjectMeta{ 436 Namespace: nsA.Name, 437 OwnerReferences: defaultOwnerReferenceForJob("lq-a-high-prio"), 438 }, 439 Priority: highPriorityClass.Value, 440 PositionInLocalQueue: 0, 441 PositionInClusterQueue: 0, 442 LocalQueueName: localQueueA.Name, 443 }, 444 } 445 gomega.Eventually(func() []visibility.PendingWorkload { 446 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueA.Name, metav1.GetOptions{}) 447 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 448 return info.Items 449 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(wantPendingWorkloads, pendingWorkloadsCmpOpts...)) 450 }) 451 452 ginkgo.By("Verify their positions and priorities in LocalQueueB", func() { 453 wantPendingWorkloads := []visibility.PendingWorkload{ 454 { 455 ObjectMeta: metav1.ObjectMeta{ 456 Namespace: nsB.Name, 457 OwnerReferences: defaultOwnerReferenceForJob("lq-b-mid-prio"), 458 }, 459 Priority: midPriorityClass.Value, 460 PositionInLocalQueue: 0, 461 PositionInClusterQueue: 1, 462 LocalQueueName: localQueueB.Name, 463 }, 464 { 465 ObjectMeta: metav1.ObjectMeta{ 466 Namespace: nsB.Name, 467 OwnerReferences: defaultOwnerReferenceForJob("lq-b-low-prio"), 468 }, 469 Priority: lowPriorityClass.Value, 470 PositionInLocalQueue: 1, 471 PositionInClusterQueue: 2, 472 LocalQueueName: localQueueB.Name, 473 }, 474 } 475 gomega.Eventually(func() []visibility.PendingWorkload { 476 info, err := visibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, localQueueB.Name, metav1.GetOptions{}) 477 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 478 return info.Items 479 }, util.Timeout, util.Interval).Should(gomega.BeComparableTo(wantPendingWorkloads, pendingWorkloadsCmpOpts...)) 480 }) 481 }) 482 }) 483 484 ginkgo.When("A subject is bound to kueue-batch-admin-role", func() { 485 var clusterRoleBinding *rbacv1.ClusterRoleBinding 486 487 ginkgo.BeforeEach(func() { 488 clusterRoleBinding = &rbacv1.ClusterRoleBinding{ 489 ObjectMeta: metav1.ObjectMeta{Name: "read-pending-workloads"}, 490 RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "kueue-batch-admin-role"}, 491 Subjects: []rbacv1.Subject{ 492 {Name: "default", APIGroup: "", Namespace: "kueue-system", Kind: rbacv1.ServiceAccountKind}, 493 }, 494 } 495 gomega.Expect(k8sClient.Create(ctx, clusterRoleBinding)).Should(gomega.Succeed()) 496 }) 497 498 ginkgo.AfterEach(func() { 499 gomega.Expect(k8sClient.Delete(ctx, clusterRoleBinding)).To(gomega.Succeed()) 500 }) 501 502 ginkgo.It("Should return an appropriate error", func() { 503 ginkgo.By("Returning a ResourceNotFound error for a nonexistent ClusterQueue", func() { 504 _, err := impersonatedVisibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 505 statusErr, ok := err.(*errors.StatusError) 506 gomega.Expect(ok).To(gomega.Equal(true)) 507 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonNotFound)) 508 }) 509 ginkgo.By("Returning a ResourceNotFound error for a nonexistent LocalQueue", func() { 510 _, err := impersonatedVisibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 511 statusErr, ok := err.(*errors.StatusError) 512 gomega.Expect(ok).To(gomega.Equal(true)) 513 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonNotFound)) 514 }) 515 }) 516 }) 517 518 ginkgo.When("A subject is bound to kueue-batch-user-role, but not to kueue-batch-admin-role", func() { 519 var roleBinding *rbacv1.RoleBinding 520 521 ginkgo.BeforeEach(func() { 522 roleBinding = &rbacv1.RoleBinding{ 523 ObjectMeta: metav1.ObjectMeta{Name: "read-pending-workloads", Namespace: nsA.Name}, 524 RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "kueue-batch-user-role"}, 525 Subjects: []rbacv1.Subject{ 526 {Name: "default", APIGroup: "", Namespace: "kueue-system", Kind: rbacv1.ServiceAccountKind}, 527 }, 528 } 529 gomega.Expect(k8sClient.Create(ctx, roleBinding)).Should(gomega.Succeed()) 530 }) 531 532 ginkgo.AfterEach(func() { 533 gomega.Expect(k8sClient.Delete(ctx, roleBinding)).To(gomega.Succeed()) 534 }) 535 536 ginkgo.It("Should return an appropriate error", func() { 537 ginkgo.By("Returning a Forbidden error due to insufficient permissions for the ClusterQueue request", func() { 538 _, err := impersonatedVisibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 539 statusErr, ok := err.(*errors.StatusError) 540 gomega.Expect(ok).To(gomega.Equal(true)) 541 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonForbidden)) 542 }) 543 ginkgo.By("Returning a ResourceNotFound error for a nonexistent LocalQueue", func() { 544 _, err := impersonatedVisibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 545 statusErr, ok := err.(*errors.StatusError) 546 gomega.Expect(ok).To(gomega.Equal(true)) 547 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonNotFound)) 548 }) 549 ginkgo.By("Returning a Forbidden error due to insufficient permissions for the LocalQueue request in different namespace", func() { 550 _, err := impersonatedVisibilityClient.LocalQueues("default").GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 551 statusErr, ok := err.(*errors.StatusError) 552 gomega.Expect(ok).To(gomega.Equal(true)) 553 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonForbidden)) 554 }) 555 }) 556 }) 557 558 ginkgo.When("A subject is not bound to kueue-batch-user-role, nor to kueue-batch-admin-role", func() { 559 ginkgo.It("Should return an appropriate error", func() { 560 ginkgo.By("Returning a Forbidden error due to insufficient permissions for the ClusterQueue request", func() { 561 _, err := impersonatedVisibilityClient.ClusterQueues().GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 562 statusErr, ok := err.(*errors.StatusError) 563 gomega.Expect(ok).To(gomega.Equal(true)) 564 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonForbidden)) 565 }) 566 ginkgo.By("Returning a Forbidden error due to insufficient permissions for the LocalQueue request", func() { 567 _, err := impersonatedVisibilityClient.LocalQueues(nsA.Name).GetPendingWorkloadsSummary(ctx, "non-existent", metav1.GetOptions{}) 568 statusErr, ok := err.(*errors.StatusError) 569 gomega.Expect(ok).To(gomega.Equal(true)) 570 gomega.Expect(statusErr.ErrStatus.Reason).To(gomega.Equal(metav1.StatusReasonForbidden)) 571 }) 572 }) 573 }) 574 })