Change:
If the user update , duplicate condition: ! Only one Failed condition Create Job and wait it marked as Failed Update the Failed condition to False No response <details</details<details</details<details</details<details</details<details</details<details</details
It's not a bug. The is a list, so it can contain many conditions with different probe timestamps. Also, we should not update the fields.
But should we avoid the duplicate Type's conditionIn my opinionthis will confuse the user. According to kubelet, it will rewrite the 'Ready' condition in pod, and take is as managed only by kubelet. Should we did this for job Failed condition? Refer:
/sig apps
It is a bug. We should only have one condition for each type. /triage accepted
/assign
/assign
/assign
I have reproduced the issue locally and intend work on the fix
/priority backlog Since the user is manually modifying the status, this is not an inherent bug in the controller. Still worth avoiding duplicates.

Discussion:
"time" batch "k8s.io/api/batch/v1" <del> "k8s.io/api/core/v1" </del> <ins> v1 "k8s.io/api/core/v1" </ins> apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels"

if isIndexedJob(job) { completionMode = string(*job.Spec.CompletionMode) } <del> job.Status.Conditions = append(job.Status.Conditions, *finishedCond) </del> <ins> job.Status.Conditions, _ = ensureJobConditionStatus(job.Status.Conditions, finishedCond.Type, finishedCond.Status, finishedCond.Reason, finishedCond.Message) </ins> if finishedCond.Type == batch.JobComplete { if job.Spec.Completions != nil && job.Status.Succeeded > *job.Spec.Completions { jm.recorder.Event(job, v1.EventTypeWarning, "TooManySucceededPods", "Too many succeeded pods running after completion count reached")

// update the status condition to false. The function returns a bool to let the // caller know if the list was changed (either appended or updated). func ensureJobConditionStatus(list []batch.JobCondition, cType batch.JobConditionType, status v1.ConditionStatus, reason, message string) ([]batch.JobCondition, bool) { <del> for i := range list { if list[i].Type == cType { if list[i].Status != status || list[i].Reason != reason || list[i].Message != message { list[i].Status = status list[i].LastTransitionTime = metav1.Now() list[i].Reason = reason list[i].Message = message return list, true } return list, false </del> <ins> if condition := findConditionByType(list, cType); condition != nil { if condition.Status != status || condition.Reason != reason || condition.Message != message { *condition = *newCondition(cType, status, reason, message) return list, true </ins> } <ins> return list, false </ins> } // A condition with that type doesn't exist in the list. if status != v1.ConditionFalse {

return list, false } <ins> func findConditionByType(list []batch.JobCondition, cType batch.JobConditionType) *batch.JobCondition { for i := range list { if list[i].Type == cType { return &list[i] } } return nil } </ins> func recordJobPodFinished(job *batch.Job, oldCounters batch.JobStatus) { completionMode := completionModeStr(job) diff := job.Status.Succeeded - oldCounters.Succeeded

"time" "github.com/google/go-cmp/cmp" <ins> "github.com/google/go-cmp/cmp/cmpopts" </ins> batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality"

if !errors.Is(err, tc.wantErr) { t.Errorf("Got error %v, want %v", err, tc.wantErr) } <del> if diff := cmp.Diff(tc.wantStatusUpdates, statusUpdates); diff != "" { </del> <ins> if diff := cmp.Diff(tc.wantStatusUpdates, statusUpdates, cmpopts.IgnoreFields(batch.JobCondition{}, "LastProbeTime", "LastTransitionTime")); diff != "" { </ins> t.Errorf("Unexpected status updates (-want,+got):n%s", diff) } rmFinalizers := len(fakePodControl.Patches)

} } <ins> func TestSingleJobFailedCondition(t *testing.T) { clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}) manager, sharedInformerFactory := newControllerFromClient(clientset, controller.NoResyncPeriodFunc) fakePodControl := controller.FakePodControl{} manager.podControl = &fakePodControl manager.podStoreSynced = alwaysReady manager.jobStoreSynced = alwaysReady var actual *batch.Job manager.updateStatusHandler = func(ctx context.Context, job *batch.Job) (*batch.Job, error) { actual = job return job, nil } job := newJob(1, 1, 6, batch.NonIndexedCompletion) activeDeadlineSeconds := int64(10) job.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds start := metav1.Unix(metav1.Now().Time.Unix()-15, 0) job.Status.StartTime = &start job.Status.Conditions = append(job.Status.Conditions, *newCondition(batch.JobFailed, v1.ConditionFalse, "DeadlineExceeded", "Job was active longer than specified deadline")) sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) forget, err := manager.syncJob(context.TODO(), testutil.GetKey(job, t)) if err != nil { t.Errorf("Unexpected error when syncing jobs %v", err) } if !forget { t.Errorf("Unexpected forget value. Expected %v, saw %vn", true, forget) } if len(fakePodControl.DeletePodName) != 0 { t.Errorf("Unexpected number of deletes.  Expected %d, saw %dn", 0, len(fakePodControl.DeletePodName)) } if actual == nil { t.Error("Expected job modificationn") } failedConditions := getConditionsByType(actual.Status.Conditions, batch.JobFailed) if len(failedConditions) != 1 { t.Error("Unexpected number of failed conditionsn") } if failedConditions[0].Status != v1.ConditionTrue { t.Errorf("Unexpected status for the failed condition. Expected: %v, saw %vn", v1.ConditionTrue, failedConditions[0].Status) } } </ins> func TestSyncJobComplete(t *testing.T) { clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}) manager, sharedInformerFactory := newControllerFromClient(clientset, controller.NoResyncPeriodFunc)

if len(gotList) != len(tc.expectList) { t.Errorf("got a list of length %d, want %d", len(gotList), len(tc.expectList)) } <del> for i := range gotList { // Make timestamps the same before comparing the two lists. gotList[i].LastProbeTime = tc.expectList[i].LastProbeTime gotList[i].LastTransitionTime = tc.expectList[i].LastTransitionTime } if diff := cmp.Diff(tc.expectList, gotList); diff != "" { </del> <ins> if diff := cmp.Diff(tc.expectList, gotList, cmpopts.IgnoreFields(batch.JobCondition{}, "LastProbeTime", "LastTransitionTime")); diff != "" { </ins> t.Errorf("Unexpected JobCondition list: (-want,+got):n%s", diff) } })

}} } <ins> func getConditionsByType(list []batch.JobCondition, cType batch.JobConditionType) []*batch.JobCondition { var result []*batch.JobCondition for i := range list { if list[i].Type == cType { result = append(result, &list[i]) } } return result } </ins> func (pb podBuilder) name(n string) podBuilder { pb.Name = n return pb