Job: Add the CompletionsReached reason to the SuccessCriteriaMet condition

Signed-off-by: Yuki Iwai <yuki.iwai.tz@gmail.com>
This commit is contained in:
Yuki Iwai
2024-07-14 08:25:31 +09:00
parent a8d354bf39
commit 594490fd77
5 changed files with 143 additions and 6 deletions

View File

@@ -988,7 +988,12 @@ func (jm *Controller) newSuccessCondition() *batch.JobCondition {
if delayTerminalCondition() { if delayTerminalCondition() {
cType = batch.JobSuccessCriteriaMet cType = batch.JobSuccessCriteriaMet
} }
return newCondition(cType, v1.ConditionTrue, "", "", jm.clock.Now()) var reason, message string
if feature.DefaultFeatureGate.Enabled(features.JobSuccessPolicy) {
reason = batch.JobReasonCompletionsReached
message = "Reached expected number of succeeded pods"
}
return newCondition(cType, v1.ConditionTrue, reason, message, jm.clock.Now())
} }
func delayTerminalCondition() bool { func delayTerminalCondition() bool {

View File

@@ -4991,6 +4991,45 @@ func TestSyncJobWithJobSuccessPolicy(t *testing.T) {
}, },
}, },
}, },
"job without successPolicy; jobSuccessPolicy is enabled; job got SuccessCriteriaMet and Completion with CompletionsReached reason conditions": {
enableJobSuccessPolicy: true,
enableJobManagedBy: true,
job: batch.Job{
TypeMeta: validTypeMeta,
ObjectMeta: validObjectMeta,
Spec: batch.JobSpec{
Selector: validSelector,
Template: validTemplate,
CompletionMode: ptr.To(batch.IndexedCompletion),
Completions: ptr.To[int32](1),
Parallelism: ptr.To[int32](1),
BackoffLimit: ptr.To[int32](math.MaxInt32),
},
},
pods: []v1.Pod{
*buildPod().uid("a1").index("0").phase(v1.PodSucceeded).trackingFinalizer().Pod,
},
wantStatus: batch.JobStatus{
Failed: 0,
Succeeded: 1,
CompletedIndexes: "0",
UncountedTerminatedPods: &batch.UncountedTerminatedPods{},
Conditions: []batch.JobCondition{
{
Type: batch.JobSuccessCriteriaMet,
Status: v1.ConditionTrue,
Reason: batch.JobReasonCompletionsReached,
Message: "Reached expected number of succeeded pods",
},
{
Type: batch.JobComplete,
Status: v1.ConditionTrue,
Reason: batch.JobReasonCompletionsReached,
Message: "Reached expected number of succeeded pods",
},
},
},
},
"when the JobSuccessPolicy is disabled, the Job never got SuccessCriteriaMet condition even if the Job has the successPolicy field": { "when the JobSuccessPolicy is disabled, the Job never got SuccessCriteriaMet condition even if the Job has the successPolicy field": {
job: batch.Job{ job: batch.Job{
TypeMeta: validTypeMeta, TypeMeta: validTypeMeta,

View File

@@ -651,6 +651,11 @@ const (
// https://kep.k8s.io/3998 // https://kep.k8s.io/3998
// This is currently an alpha field. // This is currently an alpha field.
JobReasonSuccessPolicy string = "SuccessPolicy" JobReasonSuccessPolicy string = "SuccessPolicy"
// JobReasonCompletionsReached reason indicates a SuccessCriteriaMet condition is added due to
// a number of succeeded Job pods met completions.
// - https://kep.k8s.io/3998
// This is currently a beta field.
JobReasonCompletionsReached string = "CompletionsReached"
) )
// JobCondition describes current state of a job. // JobCondition describes current state of a job.

View File

@@ -710,7 +710,7 @@ done`}
framework.ExpectNoError(err, "failed to create job in namespace: %s", f.Namespace.Name) framework.ExpectNoError(err, "failed to create job in namespace: %s", f.Namespace.Name)
ginkgo.By("Awaiting for the job to have the interim success condition") ginkgo.By("Awaiting for the job to have the interim success condition")
err = e2ejob.WaitForJobCondition(ctx, f.ClientSet, f.Namespace.Name, job.Name, batchv1.JobSuccessCriteriaMet, "") err = e2ejob.WaitForJobCondition(ctx, f.ClientSet, f.Namespace.Name, job.Name, batchv1.JobSuccessCriteriaMet, batchv1.JobReasonCompletionsReached)
framework.ExpectNoError(err, "failed to ensure job has the interim success condition: %s", f.Namespace.Name) framework.ExpectNoError(err, "failed to ensure job has the interim success condition: %s", f.Namespace.Name)
ginkgo.By("Ensuring job reaches completions") ginkgo.By("Ensuring job reaches completions")

View File

@@ -1185,6 +1185,7 @@ func TestDelayTerminalPhaseCondition(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
enableJobManagedBy bool enableJobManagedBy bool
enableJobPodReplacementPolicy bool enableJobPodReplacementPolicy bool
enableJobSuccessPolicy bool
job batchv1.Job job batchv1.Job
action func(context.Context, clientset.Interface, *batchv1.Job) action func(context.Context, clientset.Interface, *batchv1.Job)
@@ -1393,6 +1394,92 @@ func TestDelayTerminalPhaseCondition(t *testing.T) {
}, },
}, },
}, },
"job scale down to meet completions; JobManagedBy and JobSuccessPolicy are enabled": {
enableJobManagedBy: true,
enableJobSuccessPolicy: true,
job: batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: ptr.To[int32](2),
Completions: ptr.To[int32](2),
CompletionMode: ptr.To(batchv1.IndexedCompletion),
Template: podTemplateSpec,
},
},
action: succeedOnePodAndScaleDown,
wantInterimStatus: &batchv1.JobStatus{
Succeeded: 1,
Ready: ptr.To[int32](0),
CompletedIndexes: "0",
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobSuccessCriteriaMet,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
},
},
wantTerminalStatus: batchv1.JobStatus{
Succeeded: 1,
Ready: ptr.To[int32](0),
CompletedIndexes: "0",
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobSuccessCriteriaMet,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
{
Type: batchv1.JobComplete,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
},
},
},
"job scale down to meet completions; JobPodReplacementPolicy and JobSuccessPolicy are enabled": {
enableJobPodReplacementPolicy: true,
enableJobSuccessPolicy: true,
job: batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: ptr.To[int32](2),
Completions: ptr.To[int32](2),
CompletionMode: ptr.To(batchv1.IndexedCompletion),
Template: podTemplateSpec,
},
},
action: succeedOnePodAndScaleDown,
wantInterimStatus: &batchv1.JobStatus{
Succeeded: 1,
Ready: ptr.To[int32](0),
Terminating: ptr.To[int32](1),
CompletedIndexes: "0",
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobSuccessCriteriaMet,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
},
},
wantTerminalStatus: batchv1.JobStatus{
Succeeded: 1,
Ready: ptr.To[int32](0),
Terminating: ptr.To[int32](0),
CompletedIndexes: "0",
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobSuccessCriteriaMet,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
{
Type: batchv1.JobComplete,
Status: v1.ConditionTrue,
Reason: batchv1.JobReasonCompletionsReached,
},
},
},
},
} }
for name, test := range testCases { for name, test := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@@ -1400,6 +1487,7 @@ func TestDelayTerminalPhaseCondition(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobPodReplacementPolicy, test.enableJobPodReplacementPolicy) featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobPodReplacementPolicy, test.enableJobPodReplacementPolicy)
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobManagedBy, test.enableJobManagedBy) featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobManagedBy, test.enableJobManagedBy)
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ElasticIndexedJob, true) featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ElasticIndexedJob, true)
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobSuccessPolicy, test.enableJobSuccessPolicy)
closeFn, restConfig, clientSet, ns := setup(t, "delay-terminal-condition") closeFn, restConfig, clientSet, ns := setup(t, "delay-terminal-condition")
t.Cleanup(closeFn) t.Cleanup(closeFn)