mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2018 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package ttlafterfinished
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	batchv1 "k8s.io/api/batch/v1"
 | |
| 	corev1 "k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/klog/v2"
 | |
| 	"k8s.io/klog/v2/ktesting"
 | |
| 	"k8s.io/utils/pointer"
 | |
| )
 | |
| 
 | |
| func newJob(completionTime, failedTime metav1.Time, ttl *int32) *batchv1.Job {
 | |
| 	j := &batchv1.Job{
 | |
| 		TypeMeta: metav1.TypeMeta{Kind: "Job"},
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name:      "foobar",
 | |
| 			Namespace: metav1.NamespaceDefault,
 | |
| 		},
 | |
| 		Spec: batchv1.JobSpec{
 | |
| 			Selector: &metav1.LabelSelector{
 | |
| 				MatchLabels: map[string]string{"foo": "bar"},
 | |
| 			},
 | |
| 			Template: corev1.PodTemplateSpec{
 | |
| 				ObjectMeta: metav1.ObjectMeta{
 | |
| 					Labels: map[string]string{
 | |
| 						"foo": "bar",
 | |
| 					},
 | |
| 				},
 | |
| 				Spec: corev1.PodSpec{
 | |
| 					Containers: []corev1.Container{
 | |
| 						{Image: "foo/bar"},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if !completionTime.IsZero() {
 | |
| 		c := batchv1.JobCondition{Type: batchv1.JobComplete, Status: corev1.ConditionTrue, LastTransitionTime: completionTime}
 | |
| 		j.Status.Conditions = append(j.Status.Conditions, c)
 | |
| 	}
 | |
| 
 | |
| 	if !failedTime.IsZero() {
 | |
| 		c := batchv1.JobCondition{Type: batchv1.JobFailed, Status: corev1.ConditionTrue, LastTransitionTime: failedTime}
 | |
| 		j.Status.Conditions = append(j.Status.Conditions, c)
 | |
| 	}
 | |
| 
 | |
| 	if ttl != nil {
 | |
| 		j.Spec.TTLSecondsAfterFinished = ttl
 | |
| 	}
 | |
| 
 | |
| 	return j
 | |
| }
 | |
| 
 | |
| func TestTimeLeft(t *testing.T) {
 | |
| 	now := metav1.Now()
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name             string
 | |
| 		completionTime   metav1.Time
 | |
| 		failedTime       metav1.Time
 | |
| 		ttl              *int32
 | |
| 		since            *time.Time
 | |
| 		expectErr        bool
 | |
| 		expectErrStr     string
 | |
| 		expectedTimeLeft *time.Duration
 | |
| 		expectedExpireAt time.Time
 | |
| 	}{
 | |
| 		{
 | |
| 			name:         "Error case: Job unfinished",
 | |
| 			ttl:          pointer.Int32(100),
 | |
| 			since:        &now.Time,
 | |
| 			expectErr:    true,
 | |
| 			expectErrStr: "should not be cleaned up",
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "Error case: Job completed now, no TTL",
 | |
| 			completionTime: now,
 | |
| 			since:          &now.Time,
 | |
| 			expectErr:      true,
 | |
| 			expectErrStr:   "should not be cleaned up",
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job completed now, 0s TTL",
 | |
| 			completionTime:   now,
 | |
| 			ttl:              pointer.Int32(0),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(0 * time.Second),
 | |
| 			expectedExpireAt: now.Time,
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job completed now, 10s TTL",
 | |
| 			completionTime:   now,
 | |
| 			ttl:              pointer.Int32(10),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(10 * time.Second),
 | |
| 			expectedExpireAt: now.Add(10 * time.Second),
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job completed 10s ago, 15s TTL",
 | |
| 			completionTime:   metav1.NewTime(now.Add(-10 * time.Second)),
 | |
| 			ttl:              pointer.Int32(15),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(5 * time.Second),
 | |
| 			expectedExpireAt: now.Add(5 * time.Second),
 | |
| 		},
 | |
| 		{
 | |
| 			name:         "Error case: Job failed now, no TTL",
 | |
| 			failedTime:   now,
 | |
| 			since:        &now.Time,
 | |
| 			expectErr:    true,
 | |
| 			expectErrStr: "should not be cleaned up",
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job failed now, 0s TTL",
 | |
| 			failedTime:       now,
 | |
| 			ttl:              pointer.Int32(0),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(0 * time.Second),
 | |
| 			expectedExpireAt: now.Time,
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job failed now, 10s TTL",
 | |
| 			failedTime:       now,
 | |
| 			ttl:              pointer.Int32(10),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(10 * time.Second),
 | |
| 			expectedExpireAt: now.Add(10 * time.Second),
 | |
| 		},
 | |
| 		{
 | |
| 			name:             "Job failed 10s ago, 15s TTL",
 | |
| 			failedTime:       metav1.NewTime(now.Add(-10 * time.Second)),
 | |
| 			ttl:              pointer.Int32(15),
 | |
| 			since:            &now.Time,
 | |
| 			expectedTimeLeft: pointer.Duration(5 * time.Second),
 | |
| 			expectedExpireAt: now.Add(5 * time.Second),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 
 | |
| 		job := newJob(tc.completionTime, tc.failedTime, tc.ttl)
 | |
| 		_, ctx := ktesting.NewTestContext(t)
 | |
| 		logger := klog.FromContext(ctx)
 | |
| 		gotTimeLeft, gotExpireAt, gotErr := timeLeft(logger, job, tc.since)
 | |
| 		if tc.expectErr != (gotErr != nil) {
 | |
| 			t.Errorf("%s: expected error is %t, got %t, error: %v", tc.name, tc.expectErr, gotErr != nil, gotErr)
 | |
| 		}
 | |
| 		if tc.expectErr && len(tc.expectErrStr) == 0 {
 | |
| 			t.Errorf("%s: invalid test setup; error message must not be empty for error cases", tc.name)
 | |
| 		}
 | |
| 		if tc.expectErr && !strings.Contains(gotErr.Error(), tc.expectErrStr) {
 | |
| 			t.Errorf("%s: expected error message contains %q, got %v", tc.name, tc.expectErrStr, gotErr)
 | |
| 		}
 | |
| 		if !tc.expectErr {
 | |
| 			if *gotTimeLeft != *tc.expectedTimeLeft {
 | |
| 				t.Errorf("%s: expected time left %v, got %v", tc.name, tc.expectedTimeLeft, gotTimeLeft)
 | |
| 			}
 | |
| 			if *gotExpireAt != tc.expectedExpireAt {
 | |
| 				t.Errorf("%s: expected expire at %v, got %v", tc.name, tc.expectedExpireAt, *gotExpireAt)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |