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