...

Source file src/k8s.io/kubernetes/pkg/controller/ttlafterfinished/ttlafterfinished_controller_test.go

Documentation: k8s.io/kubernetes/pkg/controller/ttlafterfinished

     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 ttlafterfinished
    18  
    19  import (
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	batchv1 "k8s.io/api/batch/v1"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/klog/v2"
    28  	"k8s.io/klog/v2/ktesting"
    29  	"k8s.io/utils/pointer"
    30  )
    31  
    32  func newJob(completionTime, failedTime metav1.Time, ttl *int32) *batchv1.Job {
    33  	j := &batchv1.Job{
    34  		TypeMeta: metav1.TypeMeta{Kind: "Job"},
    35  		ObjectMeta: metav1.ObjectMeta{
    36  			Name:      "foobar",
    37  			Namespace: metav1.NamespaceDefault,
    38  		},
    39  		Spec: batchv1.JobSpec{
    40  			Selector: &metav1.LabelSelector{
    41  				MatchLabels: map[string]string{"foo": "bar"},
    42  			},
    43  			Template: corev1.PodTemplateSpec{
    44  				ObjectMeta: metav1.ObjectMeta{
    45  					Labels: map[string]string{
    46  						"foo": "bar",
    47  					},
    48  				},
    49  				Spec: corev1.PodSpec{
    50  					Containers: []corev1.Container{
    51  						{Image: "foo/bar"},
    52  					},
    53  				},
    54  			},
    55  		},
    56  	}
    57  
    58  	if !completionTime.IsZero() {
    59  		c := batchv1.JobCondition{Type: batchv1.JobComplete, Status: corev1.ConditionTrue, LastTransitionTime: completionTime}
    60  		j.Status.Conditions = append(j.Status.Conditions, c)
    61  	}
    62  
    63  	if !failedTime.IsZero() {
    64  		c := batchv1.JobCondition{Type: batchv1.JobFailed, Status: corev1.ConditionTrue, LastTransitionTime: failedTime}
    65  		j.Status.Conditions = append(j.Status.Conditions, c)
    66  	}
    67  
    68  	if ttl != nil {
    69  		j.Spec.TTLSecondsAfterFinished = ttl
    70  	}
    71  
    72  	return j
    73  }
    74  
    75  func TestTimeLeft(t *testing.T) {
    76  	now := metav1.Now()
    77  
    78  	testCases := []struct {
    79  		name             string
    80  		completionTime   metav1.Time
    81  		failedTime       metav1.Time
    82  		ttl              *int32
    83  		since            *time.Time
    84  		expectErr        bool
    85  		expectErrStr     string
    86  		expectedTimeLeft *time.Duration
    87  		expectedExpireAt time.Time
    88  	}{
    89  		{
    90  			name:         "Error case: Job unfinished",
    91  			ttl:          pointer.Int32(100),
    92  			since:        &now.Time,
    93  			expectErr:    true,
    94  			expectErrStr: "should not be cleaned up",
    95  		},
    96  		{
    97  			name:           "Error case: Job completed now, no TTL",
    98  			completionTime: now,
    99  			since:          &now.Time,
   100  			expectErr:      true,
   101  			expectErrStr:   "should not be cleaned up",
   102  		},
   103  		{
   104  			name:             "Job completed now, 0s TTL",
   105  			completionTime:   now,
   106  			ttl:              pointer.Int32(0),
   107  			since:            &now.Time,
   108  			expectedTimeLeft: pointer.Duration(0 * time.Second),
   109  			expectedExpireAt: now.Time,
   110  		},
   111  		{
   112  			name:             "Job completed now, 10s TTL",
   113  			completionTime:   now,
   114  			ttl:              pointer.Int32(10),
   115  			since:            &now.Time,
   116  			expectedTimeLeft: pointer.Duration(10 * time.Second),
   117  			expectedExpireAt: now.Add(10 * time.Second),
   118  		},
   119  		{
   120  			name:             "Job completed 10s ago, 15s TTL",
   121  			completionTime:   metav1.NewTime(now.Add(-10 * time.Second)),
   122  			ttl:              pointer.Int32(15),
   123  			since:            &now.Time,
   124  			expectedTimeLeft: pointer.Duration(5 * time.Second),
   125  			expectedExpireAt: now.Add(5 * time.Second),
   126  		},
   127  		{
   128  			name:         "Error case: Job failed now, no TTL",
   129  			failedTime:   now,
   130  			since:        &now.Time,
   131  			expectErr:    true,
   132  			expectErrStr: "should not be cleaned up",
   133  		},
   134  		{
   135  			name:             "Job failed now, 0s TTL",
   136  			failedTime:       now,
   137  			ttl:              pointer.Int32(0),
   138  			since:            &now.Time,
   139  			expectedTimeLeft: pointer.Duration(0 * time.Second),
   140  			expectedExpireAt: now.Time,
   141  		},
   142  		{
   143  			name:             "Job failed now, 10s TTL",
   144  			failedTime:       now,
   145  			ttl:              pointer.Int32(10),
   146  			since:            &now.Time,
   147  			expectedTimeLeft: pointer.Duration(10 * time.Second),
   148  			expectedExpireAt: now.Add(10 * time.Second),
   149  		},
   150  		{
   151  			name:             "Job failed 10s ago, 15s TTL",
   152  			failedTime:       metav1.NewTime(now.Add(-10 * time.Second)),
   153  			ttl:              pointer.Int32(15),
   154  			since:            &now.Time,
   155  			expectedTimeLeft: pointer.Duration(5 * time.Second),
   156  			expectedExpireAt: now.Add(5 * time.Second),
   157  		},
   158  	}
   159  
   160  	for _, tc := range testCases {
   161  
   162  		job := newJob(tc.completionTime, tc.failedTime, tc.ttl)
   163  		_, ctx := ktesting.NewTestContext(t)
   164  		logger := klog.FromContext(ctx)
   165  		gotTimeLeft, gotExpireAt, gotErr := timeLeft(logger, job, tc.since)
   166  		if tc.expectErr != (gotErr != nil) {
   167  			t.Errorf("%s: expected error is %t, got %t, error: %v", tc.name, tc.expectErr, gotErr != nil, gotErr)
   168  		}
   169  		if tc.expectErr && len(tc.expectErrStr) == 0 {
   170  			t.Errorf("%s: invalid test setup; error message must not be empty for error cases", tc.name)
   171  		}
   172  		if tc.expectErr && !strings.Contains(gotErr.Error(), tc.expectErrStr) {
   173  			t.Errorf("%s: expected error message contains %q, got %v", tc.name, tc.expectErrStr, gotErr)
   174  		}
   175  		if !tc.expectErr {
   176  			if *gotTimeLeft != *tc.expectedTimeLeft {
   177  				t.Errorf("%s: expected time left %v, got %v", tc.name, tc.expectedTimeLeft, gotTimeLeft)
   178  			}
   179  			if *gotExpireAt != tc.expectedExpireAt {
   180  				t.Errorf("%s: expected expire at %v, got %v", tc.name, tc.expectedExpireAt, *gotExpireAt)
   181  			}
   182  		}
   183  	}
   184  }
   185  

View as plain text