sigs.k8s.io/kueue@v0.6.2/test/integration/controller/jobs/job/job_webhook_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 job 18 19 import ( 20 "github.com/onsi/ginkgo/v2" 21 "github.com/onsi/gomega" 22 batchv1 "k8s.io/api/batch/v1" 23 corev1 "k8s.io/api/core/v1" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/types" 27 "k8s.io/client-go/discovery" 28 "k8s.io/utils/ptr" 29 30 "sigs.k8s.io/kueue/pkg/controller/constants" 31 "sigs.k8s.io/kueue/pkg/controller/jobframework" 32 "sigs.k8s.io/kueue/pkg/controller/jobs/job" 33 "sigs.k8s.io/kueue/pkg/util/kubeversion" 34 testingjob "sigs.k8s.io/kueue/pkg/util/testingjobs/job" 35 "sigs.k8s.io/kueue/test/integration/framework" 36 "sigs.k8s.io/kueue/test/util" 37 ) 38 39 var _ = ginkgo.Describe("Job Webhook", func() { 40 var ns *corev1.Namespace 41 42 ginkgo.When("With manageJobsWithoutQueueName enabled", ginkgo.Ordered, ginkgo.ContinueOnFailure, func() { 43 44 ginkgo.BeforeAll(func() { 45 fwk = &framework.Framework{ 46 CRDPath: crdPath, 47 WebhookPath: webhookPath, 48 } 49 cfg = fwk.Init() 50 51 discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) 52 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 53 serverVersionFetcher = kubeversion.NewServerVersionFetcher(discoveryClient) 54 err = serverVersionFetcher.FetchServerVersion() 55 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 56 57 ctx, k8sClient = fwk.RunManager(cfg, managerSetup( 58 jobframework.WithManageJobsWithoutQueueName(true), 59 jobframework.WithKubeServerVersion(serverVersionFetcher), 60 )) 61 }) 62 ginkgo.BeforeEach(func() { 63 ns = &corev1.Namespace{ 64 ObjectMeta: metav1.ObjectMeta{ 65 GenerateName: "job-", 66 }, 67 } 68 gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) 69 }) 70 71 ginkgo.AfterEach(func() { 72 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed()) 73 }) 74 ginkgo.AfterAll(func() { 75 fwk.Teardown() 76 }) 77 78 ginkgo.It("checking the workload is not created when JobMinParallelismAnnotation is invalid", func() { 79 job := testingjob.MakeJob("job-with-queue-name", ns.Name).Queue("foo").SetAnnotation(job.JobMinParallelismAnnotation, "a").Obj() 80 err := k8sClient.Create(ctx, job) 81 gomega.Expect(err).Should(gomega.HaveOccurred()) 82 gomega.Expect(apierrors.IsForbidden(err)).Should(gomega.BeTrue(), "error: %v", err) 83 }) 84 85 ginkgo.It("Should suspend a Job even no queue name specified", func() { 86 job := testingjob.MakeJob("job-without-queue-name", ns.Name).Suspend(false).Obj() 87 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 88 89 lookupKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace} 90 createdJob := &batchv1.Job{} 91 gomega.Eventually(func() bool { 92 if err := k8sClient.Get(ctx, lookupKey, createdJob); err != nil { 93 return false 94 } 95 return createdJob.Spec.Suspend != nil && *createdJob.Spec.Suspend 96 }, util.Timeout, util.Interval).Should(gomega.BeTrue()) 97 }) 98 99 ginkgo.It("Should not update unsuspend Job successfully when adding queue name", func() { 100 job := testingjob.MakeJob("job-without-queue-name", ns.Name).Suspend(false).Obj() 101 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 102 103 lookupKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace} 104 createdJob := &batchv1.Job{} 105 gomega.Expect(k8sClient.Get(ctx, lookupKey, createdJob)).Should(gomega.Succeed()) 106 107 createdJob.Annotations = map[string]string{constants.QueueAnnotation: "queue"} 108 createdJob.Spec.Suspend = ptr.To(false) 109 gomega.Expect(k8sClient.Update(ctx, createdJob)).ShouldNot(gomega.Succeed()) 110 }) 111 112 ginkgo.It("Should not succeed Job when kubernetes less than 1.27 and sync completions annotation is enabled for indexed jobs", func() { 113 if v := serverVersionFetcher.GetServerVersion(); v.AtLeast(kubeversion.KubeVersion1_27) { 114 ginkgo.Skip("Kubernetes version is less then 1.27. Skip test...") 115 } 116 j := testingjob.MakeJob("job-without-queue-name", ns.Name). 117 Parallelism(5). 118 Completions(5). 119 SetAnnotation(job.JobCompletionsEqualParallelismAnnotation, "true"). 120 Indexed(true). 121 Obj() 122 gomega.Expect(apierrors.IsForbidden(k8sClient.Create(ctx, j))).Should(gomega.BeTrue()) 123 }) 124 }) 125 126 ginkgo.When("with manageJobsWithoutQueueName disabled", ginkgo.Ordered, ginkgo.ContinueOnFailure, func() { 127 ginkgo.BeforeAll(func() { 128 fwk = &framework.Framework{ 129 CRDPath: crdPath, 130 WebhookPath: webhookPath, 131 } 132 cfg = fwk.Init() 133 ctx, k8sClient = fwk.RunManager(cfg, managerSetup(jobframework.WithManageJobsWithoutQueueName(false))) 134 }) 135 ginkgo.BeforeEach(func() { 136 ns = &corev1.Namespace{ 137 ObjectMeta: metav1.ObjectMeta{ 138 GenerateName: "job-", 139 }, 140 } 141 gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) 142 }) 143 ginkgo.AfterEach(func() { 144 gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed()) 145 }) 146 ginkgo.AfterAll(func() { 147 fwk.Teardown() 148 }) 149 150 ginkgo.It("should suspend a Job when created in unsuspend state", func() { 151 job := testingjob.MakeJob("job-with-queue-name", ns.Name).Suspend(false).Queue("default").Obj() 152 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 153 154 lookupKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace} 155 createdJob := &batchv1.Job{} 156 gomega.Eventually(func() bool { 157 if err := k8sClient.Get(ctx, lookupKey, createdJob); err != nil { 158 return false 159 } 160 return createdJob.Spec.Suspend != nil && *createdJob.Spec.Suspend 161 }, util.Timeout, util.Interval).Should(gomega.BeTrue()) 162 }) 163 164 ginkgo.It("should not suspend a Job when no queue name specified", func() { 165 job := testingjob.MakeJob("job-without-queue-name", ns.Name).Suspend(false).Obj() 166 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 167 168 lookupKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace} 169 createdJob := &batchv1.Job{} 170 gomega.Eventually(func() bool { 171 if err := k8sClient.Get(ctx, lookupKey, createdJob); err != nil { 172 return false 173 } 174 return createdJob.Spec.Suspend != nil && !(*createdJob.Spec.Suspend) 175 }, util.Timeout, util.Interval).Should(gomega.BeTrue()) 176 }) 177 178 ginkgo.It("should not update unsuspend Job successfully when changing queue name", func() { 179 job := testingjob.MakeJob("job-with-queue-name", ns.Name).Queue("queue").Obj() 180 gomega.Expect(k8sClient.Create(ctx, job)).Should(gomega.Succeed()) 181 182 lookupKey := types.NamespacedName{Name: job.Name, Namespace: job.Namespace} 183 createdJob := &batchv1.Job{} 184 gomega.Expect(k8sClient.Get(ctx, lookupKey, createdJob)).Should(gomega.Succeed()) 185 186 createdJob.Labels[constants.QueueLabel] = "queue2" 187 createdJob.Spec.Suspend = ptr.To(false) 188 gomega.Expect(k8sClient.Update(ctx, createdJob)).ShouldNot(gomega.Succeed()) 189 }) 190 }) 191 })