Merge pull request #123537 from kaisoz/commonize-job-util-functions
Add the util pkg to commonize job util functions
This commit is contained in:
		| @@ -38,7 +38,7 @@ import ( | |||||||
| 	batchv1informers "k8s.io/client-go/informers/batch/v1" | 	batchv1informers "k8s.io/client-go/informers/batch/v1" | ||||||
| 	clientset "k8s.io/client-go/kubernetes" | 	clientset "k8s.io/client-go/kubernetes" | ||||||
| 	"k8s.io/client-go/kubernetes/scheme" | 	"k8s.io/client-go/kubernetes/scheme" | ||||||
| 	covev1client "k8s.io/client-go/kubernetes/typed/core/v1" | 	corev1client "k8s.io/client-go/kubernetes/typed/core/v1" | ||||||
| 	batchv1listers "k8s.io/client-go/listers/batch/v1" | 	batchv1listers "k8s.io/client-go/listers/batch/v1" | ||||||
| 	"k8s.io/client-go/tools/cache" | 	"k8s.io/client-go/tools/cache" | ||||||
| 	"k8s.io/client-go/tools/record" | 	"k8s.io/client-go/tools/record" | ||||||
| @@ -47,6 +47,7 @@ import ( | |||||||
| 	"k8s.io/klog/v2" | 	"k8s.io/klog/v2" | ||||||
| 	"k8s.io/kubernetes/pkg/controller" | 	"k8s.io/kubernetes/pkg/controller" | ||||||
| 	"k8s.io/kubernetes/pkg/controller/cronjob/metrics" | 	"k8s.io/kubernetes/pkg/controller/cronjob/metrics" | ||||||
|  | 	jobutil "k8s.io/kubernetes/pkg/controller/job/util" | ||||||
| 	"k8s.io/utils/pointer" | 	"k8s.io/utils/pointer" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -135,7 +136,7 @@ func (jm *ControllerV2) Run(ctx context.Context, workers int) { | |||||||
|  |  | ||||||
| 	// Start event processing pipeline. | 	// Start event processing pipeline. | ||||||
| 	jm.broadcaster.StartStructuredLogging(3) | 	jm.broadcaster.StartStructuredLogging(3) | ||||||
| 	jm.broadcaster.StartRecordingToSink(&covev1client.EventSinkImpl{Interface: jm.kubeClient.CoreV1().Events("")}) | 	jm.broadcaster.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: jm.kubeClient.CoreV1().Events("")}) | ||||||
| 	defer jm.broadcaster.Shutdown() | 	defer jm.broadcaster.Shutdown() | ||||||
|  |  | ||||||
| 	defer jm.queue.ShutDown() | 	defer jm.queue.ShutDown() | ||||||
| @@ -429,7 +430,7 @@ func (jm *ControllerV2) syncCronJob( | |||||||
| 	for _, j := range jobs { | 	for _, j := range jobs { | ||||||
| 		childrenJobs[j.ObjectMeta.UID] = true | 		childrenJobs[j.ObjectMeta.UID] = true | ||||||
| 		found := inActiveList(cronJob, j.ObjectMeta.UID) | 		found := inActiveList(cronJob, j.ObjectMeta.UID) | ||||||
| 		if !found && !IsJobFinished(j) { | 		if !found && !jobutil.IsJobFinished(j) { | ||||||
| 			cjCopy, err := jm.cronJobControl.GetCronJob(ctx, cronJob.Namespace, cronJob.Name) | 			cjCopy, err := jm.cronJobControl.GetCronJob(ctx, cronJob.Namespace, cronJob.Name) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, updateStatus, err | 				return nil, updateStatus, err | ||||||
| @@ -443,12 +444,12 @@ func (jm *ControllerV2) syncCronJob( | |||||||
| 			// This could happen if we crashed right after creating the Job and before updating the status, | 			// This could happen if we crashed right after creating the Job and before updating the status, | ||||||
| 			// or if our jobs list is newer than our cj status after a relist, or if someone intentionally created | 			// or if our jobs list is newer than our cj status after a relist, or if someone intentionally created | ||||||
| 			// a job that they wanted us to adopt. | 			// a job that they wanted us to adopt. | ||||||
| 		} else if found && IsJobFinished(j) { | 		} else if found && jobutil.IsJobFinished(j) { | ||||||
| 			_, status := getFinishedStatus(j) | 			_, condition := jobutil.FinishedCondition(j) | ||||||
| 			deleteFromActiveList(cronJob, j.ObjectMeta.UID) | 			deleteFromActiveList(cronJob, j.ObjectMeta.UID) | ||||||
| 			jm.recorder.Eventf(cronJob, corev1.EventTypeNormal, "SawCompletedJob", "Saw completed job: %s, status: %v", j.Name, status) | 			jm.recorder.Eventf(cronJob, corev1.EventTypeNormal, "SawCompletedJob", "Saw completed job: %s, condition: %v", j.Name, condition) | ||||||
| 			updateStatus = true | 			updateStatus = true | ||||||
| 		} else if IsJobSucceeded(j) { | 		} else if jobutil.IsJobSucceeded(j) { | ||||||
| 			// a job does not have to be in active list, as long as it has completed successfully, we will process the timestamp | 			// a job does not have to be in active list, as long as it has completed successfully, we will process the timestamp | ||||||
| 			if cronJob.Status.LastSuccessfulTime == nil { | 			if cronJob.Status.LastSuccessfulTime == nil { | ||||||
| 				cronJob.Status.LastSuccessfulTime = j.Status.CompletionTime | 				cronJob.Status.LastSuccessfulTime = j.Status.CompletionTime | ||||||
|   | |||||||
| @@ -277,31 +277,6 @@ func getTimeHashInMinutes(scheduledTime time.Time) int64 { | |||||||
| 	return scheduledTime.Unix() / 60 | 	return scheduledTime.Unix() / 60 | ||||||
| } | } | ||||||
|  |  | ||||||
| func getFinishedStatus(j *batchv1.Job) (bool, batchv1.JobConditionType) { |  | ||||||
| 	for _, c := range j.Status.Conditions { |  | ||||||
| 		if (c.Type == batchv1.JobComplete || c.Type == batchv1.JobFailed) && c.Status == corev1.ConditionTrue { |  | ||||||
| 			return true, c.Type |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false, "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsJobFinished returns whether or not a job has completed successfully or failed. |  | ||||||
| func IsJobFinished(j *batchv1.Job) bool { |  | ||||||
| 	isFinished, _ := getFinishedStatus(j) |  | ||||||
| 	return isFinished |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsJobSucceeded returns whether a job has completed successfully. |  | ||||||
| func IsJobSucceeded(j *batchv1.Job) bool { |  | ||||||
| 	for _, c := range j.Status.Conditions { |  | ||||||
| 		if c.Type == batchv1.JobComplete && c.Status == corev1.ConditionTrue { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // byJobStartTime sorts a list of jobs by start timestamp, using their names as a tie breaker. | // byJobStartTime sorts a list of jobs by start timestamp, using their names as a tie breaker. | ||||||
| type byJobStartTime []*batchv1.Job | type byJobStartTime []*batchv1.Job | ||||||
|  |  | ||||||
|   | |||||||
| @@ -706,59 +706,6 @@ func TestNextScheduleTimeDuration(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestIsJobSucceeded(t *testing.T) { |  | ||||||
| 	tests := map[string]struct { |  | ||||||
| 		job        batchv1.Job |  | ||||||
| 		wantResult bool |  | ||||||
| 	}{ |  | ||||||
| 		"job doesn't have any conditions": { |  | ||||||
| 			wantResult: false, |  | ||||||
| 		}, |  | ||||||
| 		"job has Complete=True condition": { |  | ||||||
| 			job: batchv1.Job{ |  | ||||||
| 				Status: batchv1.JobStatus{ |  | ||||||
| 					Conditions: []batchv1.JobCondition{ |  | ||||||
| 						{ |  | ||||||
| 							Type:   batchv1.JobSuspended, |  | ||||||
| 							Status: v1.ConditionFalse, |  | ||||||
| 						}, |  | ||||||
| 						{ |  | ||||||
| 							Type:   batchv1.JobComplete, |  | ||||||
| 							Status: v1.ConditionTrue, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			wantResult: true, |  | ||||||
| 		}, |  | ||||||
| 		"job has Complete=False condition": { |  | ||||||
| 			job: batchv1.Job{ |  | ||||||
| 				Status: batchv1.JobStatus{ |  | ||||||
| 					Conditions: []batchv1.JobCondition{ |  | ||||||
| 						{ |  | ||||||
| 							Type:   batchv1.JobFailed, |  | ||||||
| 							Status: v1.ConditionTrue, |  | ||||||
| 						}, |  | ||||||
| 						{ |  | ||||||
| 							Type:   batchv1.JobComplete, |  | ||||||
| 							Status: v1.ConditionFalse, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			wantResult: false, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	for name, tc := range tests { |  | ||||||
| 		t.Run(name, func(t *testing.T) { |  | ||||||
| 			gotResult := IsJobSucceeded(&tc.job) |  | ||||||
| 			if tc.wantResult != gotResult { |  | ||||||
| 				t.Errorf("unexpected result, want=%v, got=%v", tc.wantResult, gotResult) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func topOfTheHour() *time.Time { | func topOfTheHour() *time.Time { | ||||||
| 	T1, err := time.Parse(time.RFC3339, "2016-05-19T10:00:00Z") | 	T1, err := time.Parse(time.RFC3339, "2016-05-19T10:00:00Z") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -50,6 +50,7 @@ import ( | |||||||
| 	podutil "k8s.io/kubernetes/pkg/api/v1/pod" | 	podutil "k8s.io/kubernetes/pkg/api/v1/pod" | ||||||
| 	"k8s.io/kubernetes/pkg/controller" | 	"k8s.io/kubernetes/pkg/controller" | ||||||
| 	"k8s.io/kubernetes/pkg/controller/job/metrics" | 	"k8s.io/kubernetes/pkg/controller/job/metrics" | ||||||
|  | 	"k8s.io/kubernetes/pkg/controller/job/util" | ||||||
| 	"k8s.io/kubernetes/pkg/features" | 	"k8s.io/kubernetes/pkg/features" | ||||||
| 	"k8s.io/utils/clock" | 	"k8s.io/utils/clock" | ||||||
| 	"k8s.io/utils/ptr" | 	"k8s.io/utils/ptr" | ||||||
| @@ -426,7 +427,7 @@ func (jm *Controller) deletePod(logger klog.Logger, obj interface{}, final bool) | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	job := jm.resolveControllerRef(pod.Namespace, controllerRef) | 	job := jm.resolveControllerRef(pod.Namespace, controllerRef) | ||||||
| 	if job == nil || IsJobFinished(job) { | 	if job == nil || util.IsJobFinished(job) { | ||||||
| 		// syncJob will not remove this finalizer. | 		// syncJob will not remove this finalizer. | ||||||
| 		if hasFinalizer { | 		if hasFinalizer { | ||||||
| 			jm.enqueueOrphanPod(pod) | 			jm.enqueueOrphanPod(pod) | ||||||
| @@ -480,7 +481,7 @@ func (jm *Controller) updateJob(logger klog.Logger, old, cur interface{}) { | |||||||
|  |  | ||||||
| 	// The job shouldn't be marked as finished until all pod finalizers are removed. | 	// The job shouldn't be marked as finished until all pod finalizers are removed. | ||||||
| 	// This is a backup operation in this case. | 	// This is a backup operation in this case. | ||||||
| 	if IsJobFinished(curJob) { | 	if util.IsJobFinished(curJob) { | ||||||
| 		jm.cleanupPodFinalizers(curJob) | 		jm.cleanupPodFinalizers(curJob) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -655,7 +656,7 @@ func (jm *Controller) syncOrphanPod(ctx context.Context, key string) error { | |||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if job != nil && !IsJobFinished(job) { | 		if job != nil && !util.IsJobFinished(job) { | ||||||
| 			// The pod was adopted. Do not remove finalizer. | 			// The pod was adopted. Do not remove finalizer. | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| @@ -766,7 +767,7 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (rErr error) { | |||||||
| 	job := *sharedJob.DeepCopy() | 	job := *sharedJob.DeepCopy() | ||||||
|  |  | ||||||
| 	// if job was finished previously, we don't want to redo the termination | 	// if job was finished previously, we don't want to redo the termination | ||||||
| 	if IsJobFinished(&job) { | 	if util.IsJobFinished(&job) { | ||||||
| 		err := jm.podBackoffStore.removeBackoffRecord(key) | 		err := jm.podBackoffStore.removeBackoffRecord(key) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			// re-syncing here as the record has to be removed for finished/deleted jobs | 			// re-syncing here as the record has to be removed for finished/deleted jobs | ||||||
|   | |||||||
| @@ -14,18 +14,35 @@ See the License for the specific language governing permissions and | |||||||
| limitations under the License. | limitations under the License. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| package job | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	batch "k8s.io/api/batch/v1" | 	batch "k8s.io/api/batch/v1" | ||||||
| 	"k8s.io/api/core/v1" | 	v1 "k8s.io/api/core/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // FinishedCondition returns true if a job is finished as well as the condition type indicating that. | ||||||
|  | // Returns false and no condition type otherwise | ||||||
|  | func FinishedCondition(j *batch.Job) (bool, batch.JobConditionType) { | ||||||
|  | 	for _, c := range j.Status.Conditions { | ||||||
|  | 		if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue { | ||||||
|  | 			return true, c.Type | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false, "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // IsJobFinished checks whether the given Job has finished execution. | // IsJobFinished checks whether the given Job has finished execution. | ||||||
| // It does not discriminate between successful and failed terminations. | // It does not discriminate between successful and failed terminations. | ||||||
| func IsJobFinished(j *batch.Job) bool { | func IsJobFinished(j *batch.Job) bool { | ||||||
|  | 	isFinished, _ := FinishedCondition(j) | ||||||
|  | 	return isFinished | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsJobSucceeded returns whether a job has completed successfully. | ||||||
|  | func IsJobSucceeded(j *batch.Job) bool { | ||||||
| 	for _, c := range j.Status.Conditions { | 	for _, c := range j.Status.Conditions { | ||||||
| 		if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue { | 		if c.Type == batch.JobComplete && c.Status == v1.ConditionTrue { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
							
								
								
									
										209
									
								
								pkg/controller/job/util/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								pkg/controller/job/util/utils_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2016 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 util | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	batch "k8s.io/api/batch/v1" | ||||||
|  | 	v1 "k8s.io/api/core/v1" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestFinishedCondition(t *testing.T) { | ||||||
|  | 	tests := map[string]struct { | ||||||
|  | 		conditions        []batch.JobCondition | ||||||
|  | 		wantJobFinished   bool | ||||||
|  | 		wantConditionType batch.JobConditionType | ||||||
|  | 	}{ | ||||||
|  | 		"Job doesn't have any conditions": { | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 		"Job is completed and condition is true": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobComplete, | ||||||
|  | 					Status: v1.ConditionTrue, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   true, | ||||||
|  | 			wantConditionType: batch.JobComplete, | ||||||
|  | 		}, | ||||||
|  | 		"Job is completed and condition is false": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobComplete, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 		"Job is completed and condition is unknown": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobComplete, | ||||||
|  | 					Status: v1.ConditionUnknown, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 		"Job has multiple conditions, one of them being complete and condition true": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobSuspended, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobComplete, | ||||||
|  | 					Status: v1.ConditionTrue, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobFailed, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   true, | ||||||
|  | 			wantConditionType: batch.JobComplete, | ||||||
|  | 		}, | ||||||
|  | 		"Job is failed and condition is true": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobFailed, | ||||||
|  | 					Status: v1.ConditionTrue, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   true, | ||||||
|  | 			wantConditionType: batch.JobFailed, | ||||||
|  | 		}, | ||||||
|  | 		"Job is failed and condition is false": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobFailed, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 		"Job is failed and condition is unknown": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobFailed, | ||||||
|  | 					Status: v1.ConditionUnknown, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 		"Job has multiple conditions, none of them has condition true": { | ||||||
|  | 			conditions: []batch.JobCondition{ | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobSuspended, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobComplete, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Type:   batch.JobFailed, | ||||||
|  | 					Status: v1.ConditionFalse, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantJobFinished:   false, | ||||||
|  | 			wantConditionType: "", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, test := range tests { | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			job := &batch.Job{ | ||||||
|  | 				Status: batch.JobStatus{ | ||||||
|  | 					Conditions: test.conditions, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			isJobFinished, conditionType := FinishedCondition(job) | ||||||
|  | 			if isJobFinished != test.wantJobFinished { | ||||||
|  | 				if test.wantJobFinished { | ||||||
|  | 					t.Error("Expected the job to be finished") | ||||||
|  | 				} else { | ||||||
|  | 					t.Error("Expected the job to be unfinished") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if conditionType != test.wantConditionType { | ||||||
|  | 				t.Errorf("Unexpected job condition type. got: '%v', want: '%v'", conditionType, test.wantConditionType) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIsJobSucceeded(t *testing.T) { | ||||||
|  | 	tests := map[string]struct { | ||||||
|  | 		job        batch.Job | ||||||
|  | 		wantResult bool | ||||||
|  | 	}{ | ||||||
|  | 		"job doesn't have any conditions": { | ||||||
|  | 			wantResult: false, | ||||||
|  | 		}, | ||||||
|  | 		"job has Complete=True condition": { | ||||||
|  | 			job: batch.Job{ | ||||||
|  | 				Status: batch.JobStatus{ | ||||||
|  | 					Conditions: []batch.JobCondition{ | ||||||
|  | 						{ | ||||||
|  | 							Type:   batch.JobSuspended, | ||||||
|  | 							Status: v1.ConditionFalse, | ||||||
|  | 						}, | ||||||
|  | 						{ | ||||||
|  | 							Type:   batch.JobComplete, | ||||||
|  | 							Status: v1.ConditionTrue, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantResult: true, | ||||||
|  | 		}, | ||||||
|  | 		"job has Complete=False condition": { | ||||||
|  | 			job: batch.Job{ | ||||||
|  | 				Status: batch.JobStatus{ | ||||||
|  | 					Conditions: []batch.JobCondition{ | ||||||
|  | 						{ | ||||||
|  | 							Type:   batch.JobFailed, | ||||||
|  | 							Status: v1.ConditionTrue, | ||||||
|  | 						}, | ||||||
|  | 						{ | ||||||
|  | 							Type:   batch.JobComplete, | ||||||
|  | 							Status: v1.ConditionFalse, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantResult: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for name, tc := range tests { | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			gotResult := IsJobSucceeded(&tc.job) | ||||||
|  | 			if tc.wantResult != gotResult { | ||||||
|  | 				t.Errorf("unexpected result, want=%v, got=%v", tc.wantResult, gotResult) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2016 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 job |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	batch "k8s.io/api/batch/v1" |  | ||||||
| 	"k8s.io/api/core/v1" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestIsJobFinished(t *testing.T) { |  | ||||||
| 	testCases := map[string]struct { |  | ||||||
| 		conditionType        batch.JobConditionType |  | ||||||
| 		conditionStatus      v1.ConditionStatus |  | ||||||
| 		expectJobNotFinished bool |  | ||||||
| 	}{ |  | ||||||
| 		"Job is completed and condition is true": { |  | ||||||
| 			batch.JobComplete, |  | ||||||
| 			v1.ConditionTrue, |  | ||||||
| 			false, |  | ||||||
| 		}, |  | ||||||
| 		"Job is completed and condition is false": { |  | ||||||
| 			batch.JobComplete, |  | ||||||
| 			v1.ConditionFalse, |  | ||||||
| 			true, |  | ||||||
| 		}, |  | ||||||
| 		"Job is completed and condition is unknown": { |  | ||||||
| 			batch.JobComplete, |  | ||||||
| 			v1.ConditionUnknown, |  | ||||||
| 			true, |  | ||||||
| 		}, |  | ||||||
| 		"Job is failed and condition is true": { |  | ||||||
| 			batch.JobFailed, |  | ||||||
| 			v1.ConditionTrue, |  | ||||||
| 			false, |  | ||||||
| 		}, |  | ||||||
| 		"Job is failed and condition is false": { |  | ||||||
| 			batch.JobFailed, |  | ||||||
| 			v1.ConditionFalse, |  | ||||||
| 			true, |  | ||||||
| 		}, |  | ||||||
| 		"Job is failed and condition is unknown": { |  | ||||||
| 			batch.JobFailed, |  | ||||||
| 			v1.ConditionUnknown, |  | ||||||
| 			true, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for name, tc := range testCases { |  | ||||||
| 		job := &batch.Job{ |  | ||||||
| 			Status: batch.JobStatus{ |  | ||||||
| 				Conditions: []batch.JobCondition{{ |  | ||||||
| 					Type:   tc.conditionType, |  | ||||||
| 					Status: tc.conditionStatus, |  | ||||||
| 				}}, |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if tc.expectJobNotFinished == IsJobFinished(job) { |  | ||||||
| 			if tc.expectJobNotFinished { |  | ||||||
| 				t.Errorf("test name: %s, job was not expected to be finished", name) |  | ||||||
| 			} else { |  | ||||||
| 				t.Errorf("test name: %s, job was expected to be finished", name) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -37,7 +37,7 @@ import ( | |||||||
| 	"k8s.io/klog/v2" | 	"k8s.io/klog/v2" | ||||||
| 	"k8s.io/kubectl/pkg/scheme" | 	"k8s.io/kubectl/pkg/scheme" | ||||||
| 	"k8s.io/kubernetes/pkg/controller" | 	"k8s.io/kubernetes/pkg/controller" | ||||||
| 	jobutil "k8s.io/kubernetes/pkg/controller/job" | 	jobutil "k8s.io/kubernetes/pkg/controller/job/util" | ||||||
| 	"k8s.io/kubernetes/pkg/controller/ttlafterfinished/metrics" | 	"k8s.io/kubernetes/pkg/controller/ttlafterfinished/metrics" | ||||||
| 	"k8s.io/utils/clock" | 	"k8s.io/utils/clock" | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ import ( | |||||||
| 	"k8s.io/client-go/kubernetes/scheme" | 	"k8s.io/client-go/kubernetes/scheme" | ||||||
| 	"k8s.io/client-go/util/retry" | 	"k8s.io/client-go/util/retry" | ||||||
| 	batchinternal "k8s.io/kubernetes/pkg/apis/batch" | 	batchinternal "k8s.io/kubernetes/pkg/apis/batch" | ||||||
| 	"k8s.io/kubernetes/pkg/controller/job" | 	jobutil "k8s.io/kubernetes/pkg/controller/job/util" | ||||||
| 	"k8s.io/kubernetes/test/e2e/framework" | 	"k8s.io/kubernetes/test/e2e/framework" | ||||||
| 	e2ejob "k8s.io/kubernetes/test/e2e/framework/job" | 	e2ejob "k8s.io/kubernetes/test/e2e/framework/job" | ||||||
| 	e2eresource "k8s.io/kubernetes/test/e2e/framework/resource" | 	e2eresource "k8s.io/kubernetes/test/e2e/framework/resource" | ||||||
| @@ -715,7 +715,7 @@ func waitForAnyFinishedJob(ctx context.Context, c clientset.Interface, ns string | |||||||
| 			return false, err | 			return false, err | ||||||
| 		} | 		} | ||||||
| 		for i := range jobs.Items { | 		for i := range jobs.Items { | ||||||
| 			if job.IsJobFinished(&jobs.Items[i]) { | 			if jobutil.IsJobFinished(&jobs.Items[i]) { | ||||||
| 				return true, nil | 				return true, nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -761,7 +761,7 @@ func filterNotDeletedJobs(jobs *batchv1.JobList) []*batchv1.Job { | |||||||
| func filterActiveJobs(jobs *batchv1.JobList) (active []*batchv1.Job, finished []*batchv1.Job) { | func filterActiveJobs(jobs *batchv1.JobList) (active []*batchv1.Job, finished []*batchv1.Job) { | ||||||
| 	for i := range jobs.Items { | 	for i := range jobs.Items { | ||||||
| 		j := jobs.Items[i] | 		j := jobs.Items[i] | ||||||
| 		if !job.IsJobFinished(&j) { | 		if !jobutil.IsJobFinished(&j) { | ||||||
| 			active = append(active, &j) | 			active = append(active, &j) | ||||||
| 		} else { | 		} else { | ||||||
| 			finished = append(finished, &j) | 			finished = append(finished, &j) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot