k8s.io/kubernetes@v1.29.3/test/e2e/apps/ttl_after_finished.go (about)

     1  /*
     2  Copyright 2018 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 apps
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	batchv1 "k8s.io/api/batch/v1"
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/wait"
    28  	clientset "k8s.io/client-go/kubernetes"
    29  	"k8s.io/kubernetes/pkg/util/slice"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2ejob "k8s.io/kubernetes/test/e2e/framework/job"
    32  	admissionapi "k8s.io/pod-security-admission/api"
    33  
    34  	"github.com/onsi/ginkgo/v2"
    35  	"github.com/onsi/gomega"
    36  )
    37  
    38  const (
    39  	dummyFinalizer = "k8s.io/dummy-finalizer"
    40  
    41  	// JobTimeout is how long to wait for a job to finish.
    42  	JobTimeout = 15 * time.Minute
    43  )
    44  
    45  var _ = SIGDescribe("TTLAfterFinished", func() {
    46  	f := framework.NewDefaultFramework("ttlafterfinished")
    47  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    48  
    49  	ginkgo.It("job should be deleted once it finishes after TTL seconds", func(ctx context.Context) {
    50  		testFinishedJob(ctx, f)
    51  	})
    52  })
    53  
    54  func cleanupJob(ctx context.Context, f *framework.Framework, job *batchv1.Job) {
    55  	ns := f.Namespace.Name
    56  	c := f.ClientSet
    57  
    58  	framework.Logf("Remove the Job's dummy finalizer; the Job should be deleted cascadingly")
    59  	removeFinalizerFunc := func(j *batchv1.Job) {
    60  		j.ObjectMeta.Finalizers = slice.RemoveString(j.ObjectMeta.Finalizers, dummyFinalizer, nil)
    61  	}
    62  	_, err := updateJobWithRetries(ctx, c, ns, job.Name, removeFinalizerFunc)
    63  	framework.ExpectNoError(err)
    64  	e2ejob.WaitForJobGone(ctx, c, ns, job.Name, wait.ForeverTestTimeout)
    65  
    66  	err = e2ejob.WaitForAllJobPodsGone(ctx, c, ns, job.Name)
    67  	framework.ExpectNoError(err)
    68  }
    69  
    70  func testFinishedJob(ctx context.Context, f *framework.Framework) {
    71  	ns := f.Namespace.Name
    72  	c := f.ClientSet
    73  
    74  	parallelism := int32(1)
    75  	completions := int32(1)
    76  	backoffLimit := int32(2)
    77  	ttl := int32(10)
    78  
    79  	job := e2ejob.NewTestJob("randomlySucceedOrFail", "rand-non-local", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
    80  	job.Spec.TTLSecondsAfterFinished = &ttl
    81  	job.ObjectMeta.Finalizers = []string{dummyFinalizer}
    82  	ginkgo.DeferCleanup(cleanupJob, f, job)
    83  
    84  	framework.Logf("Create a Job %s/%s with TTL", ns, job.Name)
    85  	job, err := e2ejob.CreateJob(ctx, c, ns, job)
    86  	framework.ExpectNoError(err)
    87  
    88  	framework.Logf("Wait for the Job to finish")
    89  	err = e2ejob.WaitForJobFinish(ctx, c, ns, job.Name)
    90  	framework.ExpectNoError(err)
    91  
    92  	framework.Logf("Wait for TTL after finished controller to delete the Job")
    93  	err = waitForJobDeleting(ctx, c, ns, job.Name)
    94  	framework.ExpectNoError(err)
    95  
    96  	framework.Logf("Check Job's deletionTimestamp and compare with the time when the Job finished")
    97  	job, err = e2ejob.GetJob(ctx, c, ns, job.Name)
    98  	framework.ExpectNoError(err)
    99  	jobFinishTime := finishTime(job)
   100  	finishTimeUTC := jobFinishTime.UTC()
   101  	if jobFinishTime.IsZero() {
   102  		framework.Fail("Expected job finish time not to be zero.")
   103  	}
   104  
   105  	deleteAtUTC := job.ObjectMeta.DeletionTimestamp.UTC()
   106  	gomega.Expect(deleteAtUTC).NotTo(gomega.BeNil())
   107  
   108  	expireAtUTC := finishTimeUTC.Add(time.Duration(ttl) * time.Second)
   109  	if deleteAtUTC.Before(expireAtUTC) {
   110  		framework.Fail("Expected job's deletion time to be after expiration time.")
   111  	}
   112  }
   113  
   114  // finishTime returns finish time of the specified job.
   115  func finishTime(finishedJob *batchv1.Job) metav1.Time {
   116  	var finishTime metav1.Time
   117  	for _, c := range finishedJob.Status.Conditions {
   118  		if (c.Type == batchv1.JobComplete || c.Type == batchv1.JobFailed) && c.Status == v1.ConditionTrue {
   119  			return c.LastTransitionTime
   120  		}
   121  	}
   122  	return finishTime
   123  }
   124  
   125  // updateJobWithRetries updates job with retries.
   126  func updateJobWithRetries(ctx context.Context, c clientset.Interface, namespace, name string, applyUpdate func(*batchv1.Job)) (job *batchv1.Job, err error) {
   127  	jobs := c.BatchV1().Jobs(namespace)
   128  	var updateErr error
   129  	pollErr := wait.PollUntilContextTimeout(ctx, framework.Poll, JobTimeout, true, func(ctx context.Context) (bool, error) {
   130  		if job, err = jobs.Get(ctx, name, metav1.GetOptions{}); err != nil {
   131  			return false, err
   132  		}
   133  		// Apply the update, then attempt to push it to the apiserver.
   134  		applyUpdate(job)
   135  		if job, err = jobs.Update(ctx, job, metav1.UpdateOptions{}); err == nil {
   136  			framework.Logf("Updating job %s", name)
   137  			return true, nil
   138  		}
   139  		updateErr = err
   140  		return false, nil
   141  	})
   142  	if wait.Interrupted(pollErr) {
   143  		pollErr = fmt.Errorf("couldn't apply the provided updated to job %q: %v", name, updateErr)
   144  	}
   145  	return job, pollErr
   146  }
   147  
   148  // waitForJobDeleting uses c to wait for the Job jobName in namespace ns to have
   149  // a non-nil deletionTimestamp (i.e. being deleted).
   150  func waitForJobDeleting(ctx context.Context, c clientset.Interface, ns, jobName string) error {
   151  	return wait.PollUntilContextTimeout(ctx, framework.Poll, JobTimeout, true, func(ctx context.Context) (bool, error) {
   152  		curr, err := c.BatchV1().Jobs(ns).Get(ctx, jobName, metav1.GetOptions{})
   153  		if err != nil {
   154  			return false, err
   155  		}
   156  		return curr.ObjectMeta.DeletionTimestamp != nil, nil
   157  	})
   158  }