diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 920ab9019a8..19ab6d0cb71 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -3633,7 +3633,7 @@ "description": "JobStatus represents the current state of a Job.", "properties": { "active": { - "description": "The number of actively running pods.", + "description": "The number of pending and running pods.", "format": "int32", "type": "integer" }, @@ -3660,6 +3660,11 @@ "format": "int32", "type": "integer" }, + "ready": { + "description": "The number of pods which have a Ready condition.\n\nThis field is alpha-level. The job controller populates the field when the feature gate JobReadyPods is enabled (disabled by default).", + "format": "int32", + "type": "integer" + }, "startTime": { "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", "description": "Represents time when the job controller started processing a job. When a Job is created in the suspended state, this field is not set until the first time it is resumed. This field is reset every time a Job is resumed from suspension. It is represented in RFC3339 form and is in UTC." diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index ef81a8f2746..529748c2ee3 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -241,10 +241,17 @@ type JobStatus struct { // +optional CompletionTime *metav1.Time - // The number of actively running pods. + // The number of pending and running pods. // +optional Active int32 + // The number of active pods which have a Ready condition. + // + // This field is alpha-level. The job controller populates the field when + // the feature gate JobReadyPods is enabled (disabled by default). + // +optional + Ready *int32 + // The number of pods which reached phase Succeeded. // +optional Succeeded int32 diff --git a/pkg/apis/batch/v1/zz_generated.conversion.go b/pkg/apis/batch/v1/zz_generated.conversion.go index b67a2796dc7..78d8f364ee3 100644 --- a/pkg/apis/batch/v1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1/zz_generated.conversion.go @@ -434,6 +434,7 @@ func autoConvert_v1_JobStatus_To_batch_JobStatus(in *v1.JobStatus, out *batch.Jo out.Failed = in.Failed out.CompletedIndexes = in.CompletedIndexes out.UncountedTerminatedPods = (*batch.UncountedTerminatedPods)(unsafe.Pointer(in.UncountedTerminatedPods)) + out.Ready = (*int32)(unsafe.Pointer(in.Ready)) return nil } @@ -447,6 +448,7 @@ func autoConvert_batch_JobStatus_To_v1_JobStatus(in *batch.JobStatus, out *v1.Jo out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime)) out.CompletionTime = (*metav1.Time)(unsafe.Pointer(in.CompletionTime)) out.Active = in.Active + out.Ready = (*int32)(unsafe.Pointer(in.Ready)) out.Succeeded = in.Succeeded out.Failed = in.Failed out.CompletedIndexes = in.CompletedIndexes diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 39bf325aec6..5a97095d261 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -186,6 +186,9 @@ func validateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.Error allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...) + if status.Ready != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.Ready), fldPath.Child("ready"))...) + } if status.UncountedTerminatedPods != nil { path := fldPath.Child("uncountedTerminatedPods") seen := sets.NewString() diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index 735886af9f4..ec0e4d01bbd 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -686,6 +686,21 @@ func TestValidateJobUpdateStatus(t *testing.T) { }, }, update: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Status: batch.JobStatus{ + Active: 2, + Succeeded: 3, + Failed: 4, + Ready: pointer.Int32(1), + }, + }, + }, + "nil ready": { + old: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "abc", Namespace: metav1.NamespaceDefault, @@ -693,10 +708,22 @@ func TestValidateJobUpdateStatus(t *testing.T) { }, Status: batch.JobStatus{ Active: 1, - Succeeded: 1, + Succeeded: 2, Failed: 3, }, }, + update: batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "abc", + Namespace: metav1.NamespaceDefault, + ResourceVersion: "1", + }, + Status: batch.JobStatus{ + Active: 2, + Succeeded: 3, + Failed: 4, + }, + }, }, "negative counts": { old: batch.Job{ @@ -720,12 +747,15 @@ func TestValidateJobUpdateStatus(t *testing.T) { Status: batch.JobStatus{ Active: -1, Succeeded: -2, - Failed: 3, + Failed: -3, + Ready: pointer.Int32(-1), }, }, wantErrs: field.ErrorList{ {Type: field.ErrorTypeInvalid, Field: "status.active"}, {Type: field.ErrorTypeInvalid, Field: "status.succeeded"}, + {Type: field.ErrorTypeInvalid, Field: "status.failed"}, + {Type: field.ErrorTypeInvalid, Field: "status.ready"}, }, }, "empty and duplicated uncounted pods": { diff --git a/pkg/apis/batch/zz_generated.deepcopy.go b/pkg/apis/batch/zz_generated.deepcopy.go index e11ec29b046..ccc36fd51db 100644 --- a/pkg/apis/batch/zz_generated.deepcopy.go +++ b/pkg/apis/batch/zz_generated.deepcopy.go @@ -314,6 +314,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) { in, out := &in.CompletionTime, &out.CompletionTime *out = (*in).DeepCopy() } + if in.Ready != nil { + in, out := &in.Ready, &out.Ready + *out = new(int32) + **out = **in + } if in.UncountedTerminatedPods != nil { in, out := &in.UncountedTerminatedPods, &out.UncountedTerminatedPods *out = new(UncountedTerminatedPods) diff --git a/pkg/controller/job/job_controller.go b/pkg/controller/job/job_controller.go index fb6baf85274..a61b74b2e7f 100644 --- a/pkg/controller/job/job_controller.go +++ b/pkg/controller/job/job_controller.go @@ -49,12 +49,18 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/component-base/metrics/prometheus/ratelimiter" "k8s.io/klog/v2" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/job/metrics" "k8s.io/kubernetes/pkg/features" "k8s.io/utils/integer" + "k8s.io/utils/pointer" ) +// podUpdateBatchPeriod is the batch period to hold pod updates before syncing +// a Job. It is used if the feature gate JobReadyPods is enabled. +const podUpdateBatchPeriod = 500 * time.Millisecond + // controllerKind contains the schema.GroupVersionKind for this controller type. var controllerKind = batch.SchemeGroupVersion.WithKind("Job") @@ -110,6 +116,8 @@ type Controller struct { orphanQueue workqueue.RateLimitingInterface recorder record.EventRecorder + + podUpdateBatchPeriod time.Duration } // NewController creates a new Job controller that keeps the relevant pods @@ -135,6 +143,9 @@ func NewController(podInformer coreinformers.PodInformer, jobInformer batchinfor orphanQueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(DefaultJobBackOff, MaxJobBackOff), "job_orphan_pod"), recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "job-controller"}), } + if feature.DefaultFeatureGate.Enabled(features.JobReadyPods) { + jm.podUpdateBatchPeriod = podUpdateBatchPeriod + } jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { @@ -249,7 +260,7 @@ func (jm *Controller) addPod(obj interface{}) { return } jm.expectations.CreationObserved(jobKey) - jm.enqueueController(job, true) + jm.enqueueControllerPodUpdate(job, true) return } @@ -258,7 +269,7 @@ func (jm *Controller) addPod(obj interface{}) { // DO NOT observe creation because no controller should be waiting for an // orphan. for _, job := range jm.getPodJobs(pod) { - jm.enqueueController(job, true) + jm.enqueueControllerPodUpdate(job, true) } } @@ -303,7 +314,7 @@ func (jm *Controller) updatePod(old, cur interface{}) { jm.finalizerExpectations.finalizerRemovalObserved(key, string(curPod.UID)) } } - jm.enqueueController(job, immediate) + jm.enqueueControllerPodUpdate(job, immediate) } } @@ -319,7 +330,7 @@ func (jm *Controller) updatePod(old, cur interface{}) { jm.finalizerExpectations.finalizerRemovalObserved(key, string(curPod.UID)) } } - jm.enqueueController(job, immediate) + jm.enqueueControllerPodUpdate(job, immediate) return } @@ -328,7 +339,7 @@ func (jm *Controller) updatePod(old, cur interface{}) { labelChanged := !reflect.DeepEqual(curPod.Labels, oldPod.Labels) if labelChanged || controllerRefChanged { for _, job := range jm.getPodJobs(curPod) { - jm.enqueueController(job, immediate) + jm.enqueueControllerPodUpdate(job, immediate) } } } @@ -379,7 +390,7 @@ func (jm *Controller) deletePod(obj interface{}, final bool) { jm.finalizerExpectations.finalizerRemovalObserved(jobKey, string(pod.UID)) } - jm.enqueueController(job, true) + jm.enqueueControllerPodUpdate(job, true) } func (jm *Controller) updateJob(old, cur interface{}) { @@ -415,13 +426,21 @@ func (jm *Controller) updateJob(old, cur interface{}) { // immediate tells the controller to update the status right away, and should // happen ONLY when there was a successful pod run. func (jm *Controller) enqueueController(obj interface{}, immediate bool) { + jm.enqueueControllerDelayed(obj, immediate, 0) +} + +func (jm *Controller) enqueueControllerPodUpdate(obj interface{}, immediate bool) { + jm.enqueueControllerDelayed(obj, immediate, jm.podUpdateBatchPeriod) +} + +func (jm *Controller) enqueueControllerDelayed(obj interface{}, immediate bool, delay time.Duration) { key, err := controller.KeyFunc(obj) if err != nil { utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err)) return } - backoff := time.Duration(0) + backoff := delay if !immediate { backoff = getBackoff(jm.queue, key) } @@ -670,6 +689,10 @@ func (jm *Controller) syncJob(key string) (forget bool, rErr error) { activePods := controller.FilterActivePods(pods) active := int32(len(activePods)) succeeded, failed := getStatus(&job, pods, uncounted, expectedRmFinalizers) + var ready *int32 + if feature.DefaultFeatureGate.Enabled(features.JobReadyPods) { + ready = pointer.Int32(countReadyPods(activePods)) + } // Job first start. Set StartTime and start the ActiveDeadlineSeconds timer // only if the job is not in the suspended state. if job.Status.StartTime == nil && !jobSuspended(&job) { @@ -787,8 +810,9 @@ func (jm *Controller) syncJob(key string) (forget bool, rErr error) { } if uncounted != nil { - needsStatusUpdate := suspendCondChanged || active != job.Status.Active + needsStatusUpdate := suspendCondChanged || active != job.Status.Active || !equalReady(ready, job.Status.Ready) job.Status.Active = active + job.Status.Ready = ready err = jm.trackJobStatusAndRemoveFinalizers(&job, pods, prevSucceededIndexes, *uncounted, expectedRmFinalizers, finishedCondition, needsStatusUpdate) if err != nil { return false, fmt.Errorf("tracking status: %w", err) @@ -809,10 +833,11 @@ func (jm *Controller) syncJob(key string) (forget bool, rErr error) { } // no need to update the job if the status hasn't changed since last time - if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed || suspendCondChanged || finishedCondition != nil { + if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed || !equalReady(job.Status.Ready, ready) || suspendCondChanged || finishedCondition != nil { job.Status.Active = active job.Status.Succeeded = succeeded job.Status.Failed = failed + job.Status.Ready = ready if isIndexedJob(&job) { job.Status.CompletedIndexes = succeededIndexes.String() } @@ -1609,3 +1634,20 @@ func recordJobPodFinished(job *batch.Job, oldCounters batch.JobStatus) { diff = job.Status.Failed - oldCounters.Failed metrics.JobPodsFinished.WithLabelValues(completionMode, metrics.Failed).Add(float64(diff)) } + +func countReadyPods(pods []*v1.Pod) int32 { + cnt := int32(0) + for _, p := range pods { + if podutil.IsPodReady(p) { + cnt++ + } + } + return cnt +} + +func equalReady(a, b *int32) bool { + if a != nil && b != nil { + return *a == *b + } + return a == b +} diff --git a/pkg/controller/job/job_controller_test.go b/pkg/controller/job/job_controller_test.go index 49be41684e8..52274b1f293 100644 --- a/pkg/controller/job/job_controller_test.go +++ b/pkg/controller/job/job_controller_test.go @@ -125,31 +125,41 @@ func newPod(name string, job *batch.Job) *v1.Pod { } // create count pods with the given phase for the given job -func newPodList(count int32, status v1.PodPhase, job *batch.Job) []v1.Pod { - pods := []v1.Pod{} - for i := int32(0); i < count; i++ { +func newPodList(count int, status v1.PodPhase, job *batch.Job) []*v1.Pod { + var pods []*v1.Pod + for i := 0; i < count; i++ { newPod := newPod(fmt.Sprintf("pod-%v", rand.String(10)), job) newPod.Status = v1.PodStatus{Phase: status} if trackingUncountedPods(job) { newPod.Finalizers = append(newPod.Finalizers, batch.JobTrackingFinalizer) } - pods = append(pods, *newPod) + pods = append(pods, newPod) } return pods } -func setPodsStatuses(podIndexer cache.Indexer, job *batch.Job, pendingPods, activePods, succeededPods, failedPods int32) { +func setPodsStatuses(podIndexer cache.Indexer, job *batch.Job, pendingPods, activePods, succeededPods, failedPods, readyPods int) { for _, pod := range newPodList(pendingPods, v1.PodPending, job) { - podIndexer.Add(&pod) + podIndexer.Add(pod) } - for _, pod := range newPodList(activePods, v1.PodRunning, job) { - podIndexer.Add(&pod) + running := newPodList(activePods, v1.PodRunning, job) + for i, p := range running { + if i >= readyPods { + break + } + p.Status.Conditions = append(p.Status.Conditions, v1.PodCondition{ + Type: v1.PodReady, + Status: v1.ConditionTrue, + }) + } + for _, pod := range running { + podIndexer.Add(pod) } for _, pod := range newPodList(succeededPods, v1.PodSucceeded, job) { - podIndexer.Add(&pod) + podIndexer.Add(pod) } for _, pod := range newPodList(failedPods, v1.PodFailed, job) { - podIndexer.Add(&pod) + podIndexer.Add(pod) } } @@ -189,10 +199,11 @@ func TestControllerSyncJob(t *testing.T) { // pod setup podControllerError error jobKeyForget bool - pendingPods int32 - activePods int32 - succeededPods int32 - failedPods int32 + pendingPods int + activePods int + readyPods int + succeededPods int + failedPods int podsWithIndexes []indexPhase fakeExpectationAtCreation int32 // negative: ExpectDeletions, positive: ExpectCreations @@ -200,6 +211,7 @@ func TestControllerSyncJob(t *testing.T) { expectedCreations int32 expectedDeletions int32 expectedActive int32 + expectedReady *int32 expectedSucceeded int32 expectedCompletedIdxs string expectedFailed int32 @@ -212,8 +224,9 @@ func TestControllerSyncJob(t *testing.T) { expectedPodPatches int // features - indexedJobEnabled bool - suspendJobEnabled bool + indexedJobEnabled bool + suspendJobEnabled bool + jobReadyPodsEnabled bool }{ "job start": { parallelism: 2, @@ -240,12 +253,24 @@ func TestControllerSyncJob(t *testing.T) { expectedActive: 2, }, "correct # of pods": { - parallelism: 2, + parallelism: 3, completions: 5, backoffLimit: 6, jobKeyForget: true, - activePods: 2, - expectedActive: 2, + activePods: 3, + readyPods: 2, + expectedActive: 3, + }, + "correct # of pods, ready enabled": { + parallelism: 3, + completions: 5, + backoffLimit: 6, + jobKeyForget: true, + activePods: 3, + readyPods: 2, + expectedActive: 3, + expectedReady: pointer.Int32(2), + jobReadyPodsEnabled: true, }, "WQ job: correct # of pods": { parallelism: 2, @@ -709,6 +734,7 @@ func TestControllerSyncJob(t *testing.T) { } defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, tc.indexedJobEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.SuspendJob, tc.suspendJobEnabled)() + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobReadyPods, tc.jobReadyPodsEnabled)() defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, wFinalizers)() // job manager setup @@ -745,7 +771,7 @@ func TestControllerSyncJob(t *testing.T) { } sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - setPodsStatuses(podIndexer, job, tc.pendingPods, tc.activePods, tc.succeededPods, tc.failedPods) + setPodsStatuses(podIndexer, job, tc.pendingPods, tc.activePods, tc.succeededPods, tc.failedPods, tc.readyPods) setPodsStatusesWithIndexes(podIndexer, job, tc.podsWithIndexes) actual := job @@ -822,6 +848,9 @@ func TestControllerSyncJob(t *testing.T) { if actual.Status.Active != tc.expectedActive { t.Errorf("Unexpected number of active pods. Expected %d, saw %d\n", tc.expectedActive, actual.Status.Active) } + if diff := cmp.Diff(tc.expectedReady, actual.Status.Ready); diff != "" { + t.Errorf("Unexpected number of ready pods (-want,+got): %s", diff) + } if actual.Status.Succeeded != tc.expectedSucceeded { t.Errorf("Unexpected number of succeeded pods. Expected %d, saw %d\n", tc.expectedSucceeded, actual.Status.Succeeded) } @@ -1655,9 +1684,9 @@ func TestSyncJobPastDeadline(t *testing.T) { suspend bool // pod setup - activePods int32 - succeededPods int32 - failedPods int32 + activePods int + succeededPods int + failedPods int // expectations expectedForGetKey bool @@ -1759,7 +1788,7 @@ func TestSyncJobPastDeadline(t *testing.T) { job.Status.StartTime = &start sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - setPodsStatuses(podIndexer, job, 0, tc.activePods, tc.succeededPods, tc.failedPods) + setPodsStatuses(podIndexer, job, 0, tc.activePods, tc.succeededPods, tc.failedPods, 0) // run forget, err := manager.syncJob(testutil.GetKey(job, t)) @@ -2447,14 +2476,14 @@ func TestSyncJobExpectations(t *testing.T) { sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) pods := newPodList(2, v1.PodPending, job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - podIndexer.Add(&pods[0]) + podIndexer.Add(pods[0]) manager.expectations = FakeJobExpectations{ controller.NewControllerExpectations(), true, func() { // If we check active pods before checking expectations, the job // will create a new replica because it doesn't see this pod, but // has fulfilled its expectations. - podIndexer.Add(&pods[1]) + podIndexer.Add(pods[1]) }, } manager.syncJob(testutil.GetKey(job, t)) @@ -2551,7 +2580,7 @@ func TestWatchPods(t *testing.T) { pods := newPodList(1, v1.PodRunning, testJob) testPod := pods[0] testPod.Status.Phase = v1.PodFailed - fakeWatch.Add(&testPod) + fakeWatch.Add(testPod) t.Log("Waiting for pod to reach syncHandler") <-received @@ -2604,10 +2633,10 @@ func bumpResourceVersion(obj metav1.Object) { } type pods struct { - pending int32 - active int32 - succeed int32 - failed int32 + pending int + active int + succeed int + failed int } func TestJobBackoffReset(t *testing.T) { @@ -2656,7 +2685,7 @@ func TestJobBackoffReset(t *testing.T) { sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - setPodsStatuses(podIndexer, job, tc.pods[0].pending, tc.pods[0].active, tc.pods[0].succeed, tc.pods[0].failed) + setPodsStatuses(podIndexer, job, tc.pods[0].pending, tc.pods[0].active, tc.pods[0].succeed, tc.pods[0].failed, 0) manager.queue.Add(key) manager.processNextWorkItem() retries := manager.queue.NumRequeues(key) @@ -2666,7 +2695,7 @@ func TestJobBackoffReset(t *testing.T) { job = actual sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Replace([]interface{}{actual}, actual.ResourceVersion) - setPodsStatuses(podIndexer, job, tc.pods[1].pending, tc.pods[1].active, tc.pods[1].succeed, tc.pods[1].failed) + setPodsStatuses(podIndexer, job, tc.pods[1].pending, tc.pods[1].active, tc.pods[1].succeed, tc.pods[1].failed, 0) manager.processNextWorkItem() retries = manager.queue.NumRequeues(key) if retries != 0 { @@ -2835,9 +2864,9 @@ func TestJobBackoffForOnFailure(t *testing.T) { job.Spec.Template.Spec.RestartPolicy = v1.RestartPolicyOnFailure sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() - for i, pod := range newPodList(int32(len(tc.restartCounts)), tc.podPhase, job) { + for i, pod := range newPodList(len(tc.restartCounts), tc.podPhase, job) { pod.Status.ContainerStatuses = []v1.ContainerStatus{{RestartCount: tc.restartCounts[i]}} - podIndexer.Add(&pod) + podIndexer.Add(pod) } // run @@ -2878,8 +2907,8 @@ func TestJobBackoffOnRestartPolicyNever(t *testing.T) { // pod setup activePodsPhase v1.PodPhase - activePods int32 - failedPods int32 + activePods int + failedPods int // expectations isExpectingAnError bool @@ -2938,10 +2967,10 @@ func TestJobBackoffOnRestartPolicyNever(t *testing.T) { sharedInformerFactory.Batch().V1().Jobs().Informer().GetIndexer().Add(job) podIndexer := sharedInformerFactory.Core().V1().Pods().Informer().GetIndexer() for _, pod := range newPodList(tc.failedPods, v1.PodFailed, job) { - podIndexer.Add(&pod) + podIndexer.Add(pod) } for _, pod := range newPodList(tc.activePods, tc.activePodsPhase, job) { - podIndexer.Add(&pod) + podIndexer.Add(pod) } // run @@ -3079,8 +3108,8 @@ func TestFinalizersRemovedExpectations(t *testing.T) { podIndexer := podInformer.GetIndexer() uids := sets.NewString() for i := range pods { - clientset.Tracker().Add(&pods[i]) - podIndexer.Add(&pods[i]) + clientset.Tracker().Add(pods[i]) + podIndexer.Add(pods[i]) uids.Insert(string(pods[i].UID)) } jobKey := testutil.GetKey(job, t) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index df440c7e651..a009669bd47 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -232,6 +232,12 @@ const ( // yet. JobTrackingWithFinalizers featuregate.Feature = "JobTrackingWithFinalizers" + // owner: @alculquicondor + // alpha: v1.23 + // + // Track the number of pods with Ready condition in the Job status. + JobReadyPods featuregate.Feature = "JobReadyPods" + // owner: @dashpole // alpha: v1.13 // beta: v1.15 @@ -829,6 +835,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS TTLAfterFinished: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 IndexedJob: {Default: true, PreRelease: featuregate.Beta}, JobTrackingWithFinalizers: {Default: true, PreRelease: featuregate.Beta}, + JobReadyPods: {Default: false, PreRelease: featuregate.Alpha}, KubeletPodResources: {Default: true, PreRelease: featuregate.Beta}, LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha}, NonPreemptingPriority: {Default: true, PreRelease: featuregate.Beta}, diff --git a/staging/src/k8s.io/api/batch/v1/generated.pb.go b/staging/src/k8s.io/api/batch/v1/generated.pb.go index b8a10283175..a33edf5b7f1 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1/generated.pb.go @@ -375,95 +375,96 @@ func init() { } var fileDescriptor_3b52da57c93de713 = []byte{ - // 1395 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcf, 0x6f, 0x1b, 0xc5, - 0x17, 0xcf, 0xc6, 0x76, 0x6c, 0x8f, 0x93, 0xd4, 0x9d, 0x7e, 0xdb, 0xfa, 0x6b, 0x2a, 0x6f, 0x6a, + // 1413 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0x41, 0x6f, 0x1b, 0xc5, + 0x17, 0xcf, 0x26, 0x71, 0x62, 0x8f, 0x93, 0xd4, 0x9d, 0xfe, 0xdb, 0xfa, 0x6f, 0x2a, 0x6f, 0x6a, 0x0a, 0x0a, 0xa8, 0xac, 0x49, 0x88, 0x10, 0x42, 0x80, 0x94, 0x4d, 0x55, 0x68, 0x70, 0xd4, 0x30, - 0x76, 0x84, 0x04, 0x05, 0xb1, 0xde, 0x1d, 0x3b, 0xdb, 0xec, 0xee, 0x58, 0x3b, 0x63, 0x8b, 0xdc, - 0x90, 0xf8, 0x07, 0xf8, 0x2b, 0x10, 0x27, 0x84, 0x04, 0x67, 0x8e, 0xa8, 0xc7, 0x1e, 0x7b, 0x5a, - 0xd1, 0xe5, 0x0f, 0x80, 0x73, 0xb8, 0xa0, 0x9d, 0x1d, 0xef, 0x0f, 0x7b, 0x37, 0xa4, 0x3d, 0x54, - 0xdc, 0xbc, 0x6f, 0x3e, 0x9f, 0xcf, 0xbc, 0x99, 0xf7, 0xe6, 0xbd, 0x67, 0xf0, 0xde, 0xc9, 0x3b, - 0x54, 0x31, 0x49, 0xe7, 0x64, 0x32, 0xc0, 0xae, 0x83, 0x19, 0xa6, 0x9d, 0x29, 0x76, 0x0c, 0xe2, - 0x76, 0xc4, 0x82, 0x36, 0x36, 0x3b, 0x03, 0x8d, 0xe9, 0xc7, 0x9d, 0xe9, 0x56, 0x67, 0x84, 0x1d, - 0xec, 0x6a, 0x0c, 0x1b, 0xca, 0xd8, 0x25, 0x8c, 0xc0, 0x2b, 0x21, 0x48, 0xd1, 0xc6, 0xa6, 0xc2, - 0x41, 0xca, 0x74, 0xab, 0xf9, 0xc6, 0xc8, 0x64, 0xc7, 0x93, 0x81, 0xa2, 0x13, 0xbb, 0x33, 0x22, - 0x23, 0xd2, 0xe1, 0xd8, 0xc1, 0x64, 0xc8, 0xbf, 0xf8, 0x07, 0xff, 0x15, 0x6a, 0x34, 0xdb, 0x89, - 0x8d, 0x74, 0xe2, 0xe2, 0x8c, 0x7d, 0x9a, 0x3b, 0x31, 0xc6, 0xd6, 0xf4, 0x63, 0xd3, 0xc1, 0xee, - 0x69, 0x67, 0x7c, 0x32, 0x0a, 0x0c, 0xb4, 0x63, 0x63, 0xa6, 0x65, 0xb1, 0x3a, 0x79, 0x2c, 0x77, - 0xe2, 0x30, 0xd3, 0xc6, 0x0b, 0x84, 0xb7, 0xff, 0x8d, 0x40, 0xf5, 0x63, 0x6c, 0x6b, 0xf3, 0xbc, - 0xf6, 0xdf, 0x12, 0x28, 0xef, 0xb9, 0xc4, 0xd9, 0x27, 0x03, 0xf8, 0x15, 0xa8, 0x04, 0xfe, 0x18, - 0x1a, 0xd3, 0x1a, 0xd2, 0x86, 0xb4, 0x59, 0xdb, 0x7e, 0x53, 0x89, 0x6f, 0x29, 0x92, 0x55, 0xc6, - 0x27, 0xa3, 0xc0, 0x40, 0x95, 0x00, 0xad, 0x4c, 0xb7, 0x94, 0xfb, 0x83, 0x87, 0x58, 0x67, 0x07, - 0x98, 0x69, 0x2a, 0x7c, 0xe4, 0xc9, 0x4b, 0xbe, 0x27, 0x83, 0xd8, 0x86, 0x22, 0x55, 0xa8, 0x82, - 0x22, 0x1d, 0x63, 0xbd, 0xb1, 0xcc, 0xd5, 0x37, 0x94, 0x8c, 0x18, 0x28, 0xc2, 0x9b, 0xde, 0x18, - 0xeb, 0xea, 0xaa, 0x50, 0x2b, 0x06, 0x5f, 0x88, 0x73, 0xe1, 0x3e, 0x58, 0xa1, 0x4c, 0x63, 0x13, - 0xda, 0x28, 0x70, 0x95, 0xf6, 0xb9, 0x2a, 0x1c, 0xa9, 0xae, 0x0b, 0x9d, 0x95, 0xf0, 0x1b, 0x09, - 0x85, 0xf6, 0x8f, 0x12, 0xa8, 0x09, 0x64, 0xd7, 0xa4, 0x0c, 0x3e, 0x58, 0xb8, 0x01, 0xe5, 0x62, - 0x37, 0x10, 0xb0, 0xf9, 0xf9, 0xeb, 0x62, 0xa7, 0xca, 0xcc, 0x92, 0x38, 0xfd, 0x2e, 0x28, 0x99, - 0x0c, 0xdb, 0xb4, 0xb1, 0xbc, 0x51, 0xd8, 0xac, 0x6d, 0xdf, 0x38, 0xcf, 0x71, 0x75, 0x4d, 0x08, - 0x95, 0xee, 0x05, 0x14, 0x14, 0x32, 0xdb, 0x3f, 0x14, 0x23, 0x87, 0x83, 0x2b, 0x81, 0xb7, 0x41, - 0x25, 0x08, 0xac, 0x31, 0xb1, 0x30, 0x77, 0xb8, 0x1a, 0x3b, 0xd0, 0x13, 0x76, 0x14, 0x21, 0xe0, - 0x11, 0xb8, 0x4e, 0x99, 0xe6, 0x32, 0xd3, 0x19, 0xdd, 0xc1, 0x9a, 0x61, 0x99, 0x0e, 0xee, 0x61, - 0x9d, 0x38, 0x06, 0xe5, 0x11, 0x29, 0xa8, 0x2f, 0xf9, 0x9e, 0x7c, 0xbd, 0x97, 0x0d, 0x41, 0x79, - 0x5c, 0xf8, 0x00, 0x5c, 0xd6, 0x89, 0xa3, 0x4f, 0x5c, 0x17, 0x3b, 0xfa, 0xe9, 0x21, 0xb1, 0x4c, - 0xfd, 0x94, 0x07, 0xa7, 0xaa, 0x2a, 0xc2, 0x9b, 0xcb, 0x7b, 0xf3, 0x80, 0xb3, 0x2c, 0x23, 0x5a, - 0x14, 0x82, 0xaf, 0x80, 0x32, 0x9d, 0xd0, 0x31, 0x76, 0x8c, 0x46, 0x71, 0x43, 0xda, 0xac, 0xa8, - 0x35, 0xdf, 0x93, 0xcb, 0xbd, 0xd0, 0x84, 0x66, 0x6b, 0xf0, 0x73, 0x50, 0x7b, 0x48, 0x06, 0x7d, - 0x6c, 0x8f, 0x2d, 0x8d, 0xe1, 0x46, 0x89, 0x47, 0xef, 0x56, 0xe6, 0x15, 0xef, 0xc7, 0x38, 0x9e, - 0x65, 0x57, 0x84, 0x93, 0xb5, 0xc4, 0x02, 0x4a, 0xaa, 0xc1, 0x2f, 0x41, 0x93, 0x4e, 0x74, 0x1d, - 0x53, 0x3a, 0x9c, 0x58, 0xfb, 0x64, 0x40, 0x3f, 0x32, 0x29, 0x23, 0xee, 0x69, 0xd7, 0xb4, 0x4d, - 0xd6, 0x58, 0xd9, 0x90, 0x36, 0x4b, 0x6a, 0xcb, 0xf7, 0xe4, 0x66, 0x2f, 0x17, 0x85, 0xce, 0x51, - 0x80, 0x08, 0x5c, 0x1b, 0x6a, 0xa6, 0x85, 0x8d, 0x05, 0xed, 0x32, 0xd7, 0x6e, 0xfa, 0x9e, 0x7c, - 0xed, 0x6e, 0x26, 0x02, 0xe5, 0x30, 0xdb, 0xbf, 0x2e, 0x83, 0xb5, 0xd4, 0x2b, 0x80, 0x1f, 0x83, - 0x15, 0x4d, 0x67, 0xe6, 0x34, 0x48, 0x95, 0x20, 0x01, 0x5f, 0x4e, 0xde, 0x4e, 0x50, 0xbf, 0xe2, - 0xb7, 0x8c, 0xf0, 0x10, 0x07, 0x41, 0xc0, 0xf1, 0xd3, 0xd9, 0xe5, 0x54, 0x24, 0x24, 0xa0, 0x05, - 0xea, 0x96, 0x46, 0xd9, 0x2c, 0xcb, 0xfa, 0xa6, 0x8d, 0x79, 0x7c, 0x6a, 0xdb, 0xaf, 0x5f, 0xec, - 0xc9, 0x04, 0x0c, 0xf5, 0x7f, 0xbe, 0x27, 0xd7, 0xbb, 0x73, 0x3a, 0x68, 0x41, 0x19, 0xba, 0x00, - 0x72, 0x5b, 0x74, 0x85, 0x7c, 0xbf, 0xd2, 0x33, 0xef, 0x77, 0xcd, 0xf7, 0x64, 0xd8, 0x5d, 0x50, - 0x42, 0x19, 0xea, 0xed, 0x3f, 0x25, 0x50, 0x78, 0x31, 0x65, 0xf1, 0x83, 0x54, 0x59, 0xbc, 0x91, - 0x97, 0xb4, 0xb9, 0x25, 0xf1, 0xee, 0x5c, 0x49, 0x6c, 0xe5, 0x2a, 0x9c, 0x5f, 0x0e, 0x7f, 0x2b, - 0x80, 0xd5, 0x7d, 0x32, 0xd8, 0x23, 0x8e, 0x61, 0x32, 0x93, 0x38, 0x70, 0x07, 0x14, 0xd9, 0xe9, - 0x78, 0x56, 0x5a, 0x36, 0x66, 0x5b, 0xf7, 0x4f, 0xc7, 0xf8, 0xcc, 0x93, 0xeb, 0x49, 0x6c, 0x60, - 0x43, 0x1c, 0x0d, 0xbb, 0x91, 0x3b, 0xcb, 0x9c, 0xb7, 0x93, 0xde, 0xee, 0xcc, 0x93, 0x33, 0x1a, - 0xa7, 0x12, 0x29, 0xa5, 0x9d, 0x82, 0x23, 0xb0, 0x16, 0x04, 0xe7, 0xd0, 0x25, 0x83, 0x30, 0xcb, - 0x0a, 0xcf, 0x1c, 0xf5, 0xab, 0xc2, 0x81, 0xb5, 0x6e, 0x52, 0x08, 0xa5, 0x75, 0xe1, 0x34, 0xcc, - 0xb1, 0xbe, 0xab, 0x39, 0x34, 0x3c, 0xd2, 0xf3, 0xe5, 0x74, 0x53, 0xec, 0xc6, 0xf3, 0x2c, 0xad, - 0x86, 0x32, 0x76, 0x80, 0xaf, 0x82, 0x15, 0x17, 0x6b, 0x94, 0x38, 0x3c, 0x9f, 0xab, 0x71, 0x74, - 0x10, 0xb7, 0x22, 0xb1, 0x0a, 0x5f, 0x03, 0x65, 0x1b, 0x53, 0xaa, 0x8d, 0x30, 0xaf, 0x38, 0x55, - 0xf5, 0x92, 0x00, 0x96, 0x0f, 0x42, 0x33, 0x9a, 0xad, 0xb7, 0xbf, 0x97, 0x40, 0xf9, 0xc5, 0xf4, - 0xb4, 0xf7, 0xd3, 0x3d, 0xad, 0x91, 0x97, 0x79, 0x39, 0xfd, 0xec, 0xa7, 0x12, 0x77, 0x94, 0xf7, - 0xb2, 0x2d, 0x50, 0x1b, 0x6b, 0xae, 0x66, 0x59, 0xd8, 0x32, 0xa9, 0xcd, 0x7d, 0x2d, 0xa9, 0x97, - 0x82, 0xba, 0x7c, 0x18, 0x9b, 0x51, 0x12, 0x13, 0x50, 0x74, 0x62, 0x8f, 0x2d, 0x1c, 0x5c, 0x66, - 0x98, 0x6e, 0x82, 0xb2, 0x17, 0x9b, 0x51, 0x12, 0x03, 0xef, 0x83, 0xab, 0x61, 0x05, 0x9b, 0xef, - 0x80, 0x05, 0xde, 0x01, 0xff, 0xef, 0x7b, 0xf2, 0xd5, 0xdd, 0x2c, 0x00, 0xca, 0xe6, 0xc1, 0x1d, - 0xb0, 0x3a, 0xd0, 0xf4, 0x13, 0x32, 0x1c, 0x26, 0x2b, 0x76, 0xdd, 0xf7, 0xe4, 0x55, 0x35, 0x61, - 0x47, 0x29, 0x14, 0xfc, 0x02, 0x54, 0x28, 0xb6, 0xb0, 0xce, 0x88, 0x2b, 0x52, 0xec, 0xad, 0x0b, - 0x46, 0x45, 0x1b, 0x60, 0xab, 0x27, 0xa8, 0xea, 0x2a, 0xef, 0xf4, 0xe2, 0x0b, 0x45, 0x92, 0xf0, - 0x5d, 0xb0, 0x6e, 0x6b, 0xce, 0x44, 0x8b, 0x90, 0x3c, 0xb7, 0x2a, 0x2a, 0xf4, 0x3d, 0x79, 0xfd, - 0x20, 0xb5, 0x82, 0xe6, 0x90, 0xf0, 0x13, 0x50, 0x61, 0xb3, 0x36, 0xba, 0xc2, 0x5d, 0xcb, 0x6c, - 0x14, 0x87, 0xc4, 0x48, 0x75, 0xd1, 0x28, 0x4b, 0xa2, 0x16, 0x1a, 0xc9, 0x04, 0x83, 0x07, 0x63, - 0x96, 0xb8, 0xb1, 0xdd, 0x21, 0xc3, 0xee, 0x5d, 0xd3, 0x31, 0xe9, 0x31, 0x36, 0x1a, 0x15, 0x7e, - 0x5d, 0x7c, 0xf0, 0xe8, 0xf7, 0xbb, 0x59, 0x10, 0x94, 0xc7, 0x85, 0x5d, 0xb0, 0x1e, 0x87, 0xf6, - 0x80, 0x18, 0xb8, 0x51, 0xe5, 0x0f, 0xe3, 0x56, 0x70, 0xca, 0xbd, 0xd4, 0xca, 0xd9, 0x82, 0x05, - 0xcd, 0x71, 0x93, 0x83, 0x06, 0xc8, 0x1f, 0x34, 0xda, 0x7f, 0x15, 0x41, 0x35, 0xee, 0xa9, 0x47, - 0x00, 0xe8, 0xb3, 0xc2, 0x45, 0x45, 0x5f, 0xbd, 0x99, 0xf7, 0x08, 0xa2, 0x12, 0x17, 0xf7, 0x83, - 0xc8, 0x44, 0x51, 0x42, 0x08, 0x7e, 0x0a, 0xaa, 0x7c, 0xda, 0xe2, 0x25, 0x68, 0xf9, 0x99, 0x4b, - 0xd0, 0x9a, 0xef, 0xc9, 0xd5, 0xde, 0x4c, 0x00, 0xc5, 0x5a, 0x70, 0x98, 0xbc, 0xb2, 0xe7, 0x2c, - 0xa7, 0x30, 0x7d, 0xbd, 0x7c, 0x8b, 0x39, 0xd5, 0xa0, 0xa8, 0x89, 0x59, 0xa3, 0xc8, 0x03, 0x9c, - 0x37, 0x46, 0x74, 0x40, 0x95, 0xcf, 0x45, 0xd8, 0xc0, 0x06, 0xcf, 0xd1, 0x92, 0x7a, 0x59, 0x40, - 0xab, 0xbd, 0xd9, 0x02, 0x8a, 0x31, 0x81, 0x70, 0x38, 0xf0, 0x88, 0xb1, 0x2b, 0x12, 0x0e, 0xc7, - 0x23, 0x24, 0x56, 0xe1, 0x1d, 0x50, 0x17, 0x2e, 0x61, 0xe3, 0x9e, 0x63, 0xe0, 0xaf, 0x31, 0xe5, - 0x4f, 0xb3, 0xaa, 0x36, 0x04, 0xa3, 0xbe, 0x37, 0xb7, 0x8e, 0x16, 0x18, 0xf0, 0x5b, 0x09, 0x5c, - 0x9f, 0x38, 0x3a, 0x99, 0x38, 0x0c, 0x1b, 0x7d, 0xec, 0xda, 0xa6, 0x13, 0xfc, 0x79, 0x3a, 0x24, - 0x06, 0xe5, 0x99, 0x5b, 0xdb, 0xbe, 0x9d, 0x19, 0xec, 0xa3, 0x6c, 0x4e, 0x98, 0xe7, 0x39, 0x8b, - 0x28, 0x6f, 0xa7, 0xf6, 0xcf, 0x12, 0xb8, 0x34, 0x37, 0xb4, 0xfe, 0xf7, 0xa7, 0x92, 0xf6, 0x2f, - 0x12, 0xc8, 0x3b, 0x2a, 0x3c, 0x4c, 0x86, 0x3d, 0x78, 0x35, 0x55, 0x75, 0x3b, 0x15, 0xf2, 0x33, - 0x4f, 0xbe, 0x99, 0xf7, 0x97, 0x36, 0x18, 0x32, 0xa8, 0x72, 0x74, 0xef, 0x4e, 0x32, 0x2f, 0x3e, - 0x8c, 0xf2, 0x62, 0x99, 0xcb, 0x75, 0xe2, 0x9c, 0xb8, 0x98, 0x96, 0xa0, 0xab, 0x9b, 0x8f, 0x9e, - 0xb6, 0x96, 0x1e, 0x3f, 0x6d, 0x2d, 0x3d, 0x79, 0xda, 0x5a, 0xfa, 0xc6, 0x6f, 0x49, 0x8f, 0xfc, - 0x96, 0xf4, 0xd8, 0x6f, 0x49, 0x4f, 0xfc, 0x96, 0xf4, 0xbb, 0xdf, 0x92, 0xbe, 0xfb, 0xa3, 0xb5, - 0xf4, 0xd9, 0xf2, 0x74, 0xeb, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa8, 0xfc, 0x0b, 0x48, 0x81, - 0x10, 0x00, 0x00, + 0x76, 0x84, 0x04, 0x05, 0xb1, 0xde, 0x1d, 0x3b, 0xdb, 0xec, 0xee, 0x58, 0x3b, 0x63, 0x0b, 0xdf, + 0x90, 0xf8, 0x02, 0xf0, 0x25, 0x10, 0x27, 0x84, 0x04, 0x67, 0x8e, 0xa8, 0xc7, 0x1e, 0x7b, 0x5a, + 0xd1, 0xe5, 0x03, 0x70, 0x0f, 0x17, 0x34, 0xb3, 0xe3, 0xdd, 0xb5, 0xbd, 0x1b, 0xd2, 0x1e, 0x2a, + 0x6e, 0xd9, 0x37, 0xbf, 0xf7, 0x9b, 0xe7, 0xf7, 0x7e, 0xf3, 0xde, 0x0b, 0x78, 0xef, 0xf4, 0x1d, + 0xaa, 0xd9, 0xa4, 0x79, 0x3a, 0xec, 0x62, 0xdf, 0xc3, 0x0c, 0xd3, 0xe6, 0x08, 0x7b, 0x16, 0xf1, + 0x9b, 0xf2, 0xc0, 0x18, 0xd8, 0xcd, 0xae, 0xc1, 0xcc, 0x93, 0xe6, 0x68, 0xbb, 0xd9, 0xc7, 0x1e, + 0xf6, 0x0d, 0x86, 0x2d, 0x6d, 0xe0, 0x13, 0x46, 0xe0, 0x95, 0x08, 0xa4, 0x19, 0x03, 0x5b, 0x13, + 0x20, 0x6d, 0xb4, 0x5d, 0x7b, 0xa3, 0x6f, 0xb3, 0x93, 0x61, 0x57, 0x33, 0x89, 0xdb, 0xec, 0x93, + 0x3e, 0x69, 0x0a, 0x6c, 0x77, 0xd8, 0x13, 0x5f, 0xe2, 0x43, 0xfc, 0x15, 0x71, 0xd4, 0x1a, 0xa9, + 0x8b, 0x4c, 0xe2, 0xe3, 0x8c, 0x7b, 0x6a, 0xbb, 0x09, 0xc6, 0x35, 0xcc, 0x13, 0xdb, 0xc3, 0xfe, + 0xb8, 0x39, 0x38, 0xed, 0x73, 0x03, 0x6d, 0xba, 0x98, 0x19, 0x59, 0x5e, 0xcd, 0x3c, 0x2f, 0x7f, + 0xe8, 0x31, 0xdb, 0xc5, 0x73, 0x0e, 0x6f, 0xff, 0x9b, 0x03, 0x35, 0x4f, 0xb0, 0x6b, 0xcc, 0xfa, + 0x35, 0xfe, 0x56, 0xc0, 0xea, 0xbe, 0x4f, 0xbc, 0x03, 0xd2, 0x85, 0x5f, 0x81, 0x22, 0x8f, 0xc7, + 0x32, 0x98, 0x51, 0x55, 0x36, 0x95, 0xad, 0xf2, 0xce, 0x9b, 0x5a, 0x92, 0xa5, 0x98, 0x56, 0x1b, + 0x9c, 0xf6, 0xb9, 0x81, 0x6a, 0x1c, 0xad, 0x8d, 0xb6, 0xb5, 0xfb, 0xdd, 0x87, 0xd8, 0x64, 0x87, + 0x98, 0x19, 0x3a, 0x7c, 0x14, 0xa8, 0x0b, 0x61, 0xa0, 0x82, 0xc4, 0x86, 0x62, 0x56, 0xa8, 0x83, + 0x65, 0x3a, 0xc0, 0x66, 0x75, 0x51, 0xb0, 0x6f, 0x6a, 0x19, 0x35, 0xd0, 0x64, 0x34, 0xed, 0x01, + 0x36, 0xf5, 0x35, 0xc9, 0xb6, 0xcc, 0xbf, 0x90, 0xf0, 0x85, 0x07, 0x60, 0x85, 0x32, 0x83, 0x0d, + 0x69, 0x75, 0x49, 0xb0, 0x34, 0xce, 0x65, 0x11, 0x48, 0x7d, 0x43, 0xf2, 0xac, 0x44, 0xdf, 0x48, + 0x32, 0x34, 0x7e, 0x52, 0x40, 0x59, 0x22, 0x5b, 0x36, 0x65, 0xf0, 0xc1, 0x5c, 0x06, 0xb4, 0x8b, + 0x65, 0x80, 0x7b, 0x8b, 0xdf, 0x5f, 0x91, 0x37, 0x15, 0x27, 0x96, 0xd4, 0xaf, 0xdf, 0x03, 0x05, + 0x9b, 0x61, 0x97, 0x56, 0x17, 0x37, 0x97, 0xb6, 0xca, 0x3b, 0x37, 0xce, 0x0b, 0x5c, 0x5f, 0x97, + 0x44, 0x85, 0x7b, 0xdc, 0x05, 0x45, 0x9e, 0x8d, 0x1f, 0x97, 0xe3, 0x80, 0x79, 0x4a, 0xe0, 0x6d, + 0x50, 0xe4, 0x85, 0xb5, 0x86, 0x0e, 0x16, 0x01, 0x97, 0x92, 0x00, 0xda, 0xd2, 0x8e, 0x62, 0x04, + 0x3c, 0x06, 0xd7, 0x29, 0x33, 0x7c, 0x66, 0x7b, 0xfd, 0x3b, 0xd8, 0xb0, 0x1c, 0xdb, 0xc3, 0x6d, + 0x6c, 0x12, 0xcf, 0xa2, 0xa2, 0x22, 0x4b, 0xfa, 0x4b, 0x61, 0xa0, 0x5e, 0x6f, 0x67, 0x43, 0x50, + 0x9e, 0x2f, 0x7c, 0x00, 0x2e, 0x9b, 0xc4, 0x33, 0x87, 0xbe, 0x8f, 0x3d, 0x73, 0x7c, 0x44, 0x1c, + 0xdb, 0x1c, 0x8b, 0xe2, 0x94, 0x74, 0x4d, 0x46, 0x73, 0x79, 0x7f, 0x16, 0x70, 0x96, 0x65, 0x44, + 0xf3, 0x44, 0xf0, 0x15, 0xb0, 0x4a, 0x87, 0x74, 0x80, 0x3d, 0xab, 0xba, 0xbc, 0xa9, 0x6c, 0x15, + 0xf5, 0x72, 0x18, 0xa8, 0xab, 0xed, 0xc8, 0x84, 0x26, 0x67, 0xf0, 0x73, 0x50, 0x7e, 0x48, 0xba, + 0x1d, 0xec, 0x0e, 0x1c, 0x83, 0xe1, 0x6a, 0x41, 0x54, 0xef, 0x56, 0x66, 0x8a, 0x0f, 0x12, 0x9c, + 0x50, 0xd9, 0x15, 0x19, 0x64, 0x39, 0x75, 0x80, 0xd2, 0x6c, 0xf0, 0x4b, 0x50, 0xa3, 0x43, 0xd3, + 0xc4, 0x94, 0xf6, 0x86, 0xce, 0x01, 0xe9, 0xd2, 0x8f, 0x6c, 0xca, 0x88, 0x3f, 0x6e, 0xd9, 0xae, + 0xcd, 0xaa, 0x2b, 0x9b, 0xca, 0x56, 0x41, 0xaf, 0x87, 0x81, 0x5a, 0x6b, 0xe7, 0xa2, 0xd0, 0x39, + 0x0c, 0x10, 0x81, 0x6b, 0x3d, 0xc3, 0x76, 0xb0, 0x35, 0xc7, 0xbd, 0x2a, 0xb8, 0x6b, 0x61, 0xa0, + 0x5e, 0xbb, 0x9b, 0x89, 0x40, 0x39, 0x9e, 0x8d, 0xdf, 0x16, 0xc1, 0xfa, 0xd4, 0x2b, 0x80, 0x1f, + 0x83, 0x15, 0xc3, 0x64, 0xf6, 0x88, 0x4b, 0x85, 0x0b, 0xf0, 0xe5, 0x74, 0x76, 0x78, 0xff, 0x4a, + 0xde, 0x32, 0xc2, 0x3d, 0xcc, 0x8b, 0x80, 0x93, 0xa7, 0xb3, 0x27, 0x5c, 0x91, 0xa4, 0x80, 0x0e, + 0xa8, 0x38, 0x06, 0x65, 0x13, 0x95, 0x75, 0x6c, 0x17, 0x8b, 0xfa, 0x94, 0x77, 0x5e, 0xbf, 0xd8, + 0x93, 0xe1, 0x1e, 0xfa, 0xff, 0xc2, 0x40, 0xad, 0xb4, 0x66, 0x78, 0xd0, 0x1c, 0x33, 0xf4, 0x01, + 0x14, 0xb6, 0x38, 0x85, 0xe2, 0xbe, 0xc2, 0x33, 0xdf, 0x77, 0x2d, 0x0c, 0x54, 0xd8, 0x9a, 0x63, + 0x42, 0x19, 0xec, 0x8d, 0xbf, 0x14, 0xb0, 0xf4, 0x62, 0xda, 0xe2, 0x07, 0x53, 0x6d, 0xf1, 0x46, + 0x9e, 0x68, 0x73, 0x5b, 0xe2, 0xdd, 0x99, 0x96, 0x58, 0xcf, 0x65, 0x38, 0xbf, 0x1d, 0xfe, 0xbe, + 0x04, 0xd6, 0x0e, 0x48, 0x77, 0x9f, 0x78, 0x96, 0xcd, 0x6c, 0xe2, 0xc1, 0x5d, 0xb0, 0xcc, 0xc6, + 0x83, 0x49, 0x6b, 0xd9, 0x9c, 0x5c, 0xdd, 0x19, 0x0f, 0xf0, 0x59, 0xa0, 0x56, 0xd2, 0x58, 0x6e, + 0x43, 0x02, 0x0d, 0x5b, 0x71, 0x38, 0x8b, 0xc2, 0x6f, 0x77, 0xfa, 0xba, 0xb3, 0x40, 0xcd, 0x18, + 0x9c, 0x5a, 0xcc, 0x34, 0x1d, 0x14, 0xec, 0x83, 0x75, 0x5e, 0x9c, 0x23, 0x9f, 0x74, 0x23, 0x95, + 0x2d, 0x3d, 0x73, 0xd5, 0xaf, 0xca, 0x00, 0xd6, 0x5b, 0x69, 0x22, 0x34, 0xcd, 0x0b, 0x47, 0x91, + 0xc6, 0x3a, 0xbe, 0xe1, 0xd1, 0xe8, 0x27, 0x3d, 0x9f, 0xa6, 0x6b, 0xf2, 0x36, 0xa1, 0xb3, 0x69, + 0x36, 0x94, 0x71, 0x03, 0x7c, 0x15, 0xac, 0xf8, 0xd8, 0xa0, 0xc4, 0x13, 0x7a, 0x2e, 0x25, 0xd5, + 0x41, 0xc2, 0x8a, 0xe4, 0x29, 0x7c, 0x0d, 0xac, 0xba, 0x98, 0x52, 0xa3, 0x8f, 0x45, 0xc7, 0x29, + 0xe9, 0x97, 0x24, 0x70, 0xf5, 0x30, 0x32, 0xa3, 0xc9, 0x79, 0xe3, 0x07, 0x05, 0xac, 0xbe, 0x98, + 0x99, 0xf6, 0xfe, 0xf4, 0x4c, 0xab, 0xe6, 0x29, 0x2f, 0x67, 0x9e, 0xfd, 0x5c, 0x10, 0x81, 0x8a, + 0x59, 0xb6, 0x0d, 0xca, 0x03, 0xc3, 0x37, 0x1c, 0x07, 0x3b, 0x36, 0x75, 0x45, 0xac, 0x05, 0xfd, + 0x12, 0xef, 0xcb, 0x47, 0x89, 0x19, 0xa5, 0x31, 0xdc, 0xc5, 0x24, 0xee, 0xc0, 0xc1, 0x3c, 0x99, + 0x91, 0xdc, 0xa4, 0xcb, 0x7e, 0x62, 0x46, 0x69, 0x0c, 0xbc, 0x0f, 0xae, 0x46, 0x1d, 0x6c, 0x76, + 0x02, 0x2e, 0x89, 0x09, 0xf8, 0xff, 0x30, 0x50, 0xaf, 0xee, 0x65, 0x01, 0x50, 0xb6, 0x1f, 0xdc, + 0x05, 0x6b, 0x5d, 0xc3, 0x3c, 0x25, 0xbd, 0x5e, 0xba, 0x63, 0x57, 0xc2, 0x40, 0x5d, 0xd3, 0x53, + 0x76, 0x34, 0x85, 0x82, 0x5f, 0x80, 0x22, 0xc5, 0x0e, 0x36, 0x19, 0xf1, 0xa5, 0xc4, 0xde, 0xba, + 0x60, 0x55, 0x8c, 0x2e, 0x76, 0xda, 0xd2, 0x55, 0x5f, 0x13, 0x93, 0x5e, 0x7e, 0xa1, 0x98, 0x12, + 0xbe, 0x0b, 0x36, 0x5c, 0xc3, 0x1b, 0x1a, 0x31, 0x52, 0x68, 0xab, 0xa8, 0xc3, 0x30, 0x50, 0x37, + 0x0e, 0xa7, 0x4e, 0xd0, 0x0c, 0x12, 0x7e, 0x02, 0x8a, 0x6c, 0x32, 0x46, 0x57, 0x44, 0x68, 0x99, + 0x83, 0xe2, 0x88, 0x58, 0x53, 0x53, 0x34, 0x56, 0x49, 0x3c, 0x42, 0x63, 0x1a, 0xbe, 0x78, 0x30, + 0xe6, 0xc8, 0x8c, 0xed, 0xf5, 0x18, 0xf6, 0xef, 0xda, 0x9e, 0x4d, 0x4f, 0xb0, 0x55, 0x2d, 0x8a, + 0x74, 0x89, 0xc5, 0xa3, 0xd3, 0x69, 0x65, 0x41, 0x50, 0x9e, 0x2f, 0x6c, 0x81, 0x8d, 0xa4, 0xb4, + 0x87, 0xc4, 0xc2, 0xd5, 0x92, 0x78, 0x18, 0xb7, 0xf8, 0xaf, 0xdc, 0x9f, 0x3a, 0x39, 0x9b, 0xb3, + 0xa0, 0x19, 0xdf, 0xf4, 0xa2, 0x01, 0xf2, 0x17, 0x8d, 0xc6, 0xf7, 0x05, 0x50, 0x4a, 0x66, 0xea, + 0x31, 0x00, 0xe6, 0xa4, 0x71, 0x51, 0x39, 0x57, 0x6f, 0xe6, 0x3d, 0x82, 0xb8, 0xc5, 0x25, 0xf3, + 0x20, 0x36, 0x51, 0x94, 0x22, 0x82, 0x9f, 0x82, 0x92, 0xd8, 0xb6, 0x44, 0x0b, 0x5a, 0x7c, 0xe6, + 0x16, 0xb4, 0x1e, 0x06, 0x6a, 0xa9, 0x3d, 0x21, 0x40, 0x09, 0x17, 0xec, 0xa5, 0x53, 0xf6, 0x9c, + 0xed, 0x14, 0x4e, 0xa7, 0x57, 0x5c, 0x31, 0xc3, 0xca, 0x9b, 0x9a, 0xdc, 0x35, 0x96, 0x45, 0x81, + 0xf3, 0xd6, 0x88, 0x26, 0x28, 0x89, 0xbd, 0x08, 0x5b, 0xd8, 0x12, 0x1a, 0x2d, 0xe8, 0x97, 0x25, + 0xb4, 0xd4, 0x9e, 0x1c, 0xa0, 0x04, 0xc3, 0x89, 0xa3, 0x85, 0x47, 0xae, 0x5d, 0x31, 0x71, 0xb4, + 0x1e, 0x21, 0x79, 0x0a, 0xef, 0x80, 0x8a, 0x0c, 0x09, 0x5b, 0xf7, 0x3c, 0x0b, 0x7f, 0x8d, 0xa9, + 0x78, 0x9a, 0x25, 0xbd, 0x2a, 0x3d, 0x2a, 0xfb, 0x33, 0xe7, 0x68, 0xce, 0x03, 0x7e, 0xab, 0x80, + 0xeb, 0x43, 0xcf, 0x24, 0x43, 0x8f, 0x61, 0xab, 0x83, 0x7d, 0xd7, 0xf6, 0xf8, 0x3f, 0x4f, 0x47, + 0xc4, 0xa2, 0x42, 0xb9, 0xe5, 0x9d, 0xdb, 0x99, 0xc5, 0x3e, 0xce, 0xf6, 0x89, 0x74, 0x9e, 0x73, + 0x88, 0xf2, 0x6e, 0x82, 0x2a, 0x28, 0xf8, 0xd8, 0xb0, 0xc6, 0x42, 0xde, 0x05, 0xbd, 0xc4, 0xdb, + 0x28, 0xe2, 0x06, 0x14, 0xd9, 0x1b, 0xbf, 0x28, 0xe0, 0xd2, 0xcc, 0x56, 0xfb, 0xdf, 0x5f, 0x5b, + 0x1a, 0xbf, 0x2a, 0x20, 0x2f, 0x17, 0xf0, 0x28, 0xad, 0x0b, 0xfe, 0xac, 0x4a, 0xfa, 0xce, 0x94, + 0x26, 0xce, 0x02, 0xf5, 0x66, 0xde, 0xff, 0xbc, 0x7c, 0x0b, 0xa1, 0xda, 0xf1, 0xbd, 0x3b, 0x69, + 0xe1, 0x7c, 0x18, 0x0b, 0x67, 0x51, 0xd0, 0x35, 0x13, 0xd1, 0x5c, 0x8c, 0x4b, 0xba, 0xeb, 0x5b, + 0x8f, 0x9e, 0xd6, 0x17, 0x1e, 0x3f, 0xad, 0x2f, 0x3c, 0x79, 0x5a, 0x5f, 0xf8, 0x26, 0xac, 0x2b, + 0x8f, 0xc2, 0xba, 0xf2, 0x38, 0xac, 0x2b, 0x4f, 0xc2, 0xba, 0xf2, 0x47, 0x58, 0x57, 0xbe, 0xfb, + 0xb3, 0xbe, 0xf0, 0xd9, 0xe2, 0x68, 0xfb, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x54, 0x31, 0x16, + 0xcf, 0xa2, 0x10, 0x00, 0x00, } func (m *CronJob) Marshal() (dAtA []byte, err error) { @@ -975,6 +976,11 @@ func (m *JobStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Ready != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Ready)) + i-- + dAtA[i] = 0x48 + } if m.UncountedTerminatedPods != nil { { size, err := m.UncountedTerminatedPods.MarshalToSizedBuffer(dAtA[:i]) @@ -1341,6 +1347,9 @@ func (m *JobStatus) Size() (n int) { l = m.UncountedTerminatedPods.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Ready != nil { + n += 1 + sovGenerated(uint64(*m.Ready)) + } return n } @@ -1525,6 +1534,7 @@ func (this *JobStatus) String() string { `Failed:` + fmt.Sprintf("%v", this.Failed) + `,`, `CompletedIndexes:` + fmt.Sprintf("%v", this.CompletedIndexes) + `,`, `UncountedTerminatedPods:` + strings.Replace(this.UncountedTerminatedPods.String(), "UncountedTerminatedPods", "UncountedTerminatedPods", 1) + `,`, + `Ready:` + valueToStringGenerated(this.Ready) + `,`, `}`, }, "") return s @@ -3273,6 +3283,26 @@ func (m *JobStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ready", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Ready = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/batch/v1/generated.proto b/staging/src/k8s.io/api/batch/v1/generated.proto index ffa0f90da42..161886029b2 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1/generated.proto @@ -298,7 +298,7 @@ message JobStatus { // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.Time completionTime = 3; - // The number of actively running pods. + // The number of pending and running pods. // +optional optional int32 active = 4; @@ -338,6 +338,13 @@ message JobStatus { // remains null. // +optional optional UncountedTerminatedPods uncountedTerminatedPods = 8; + + // The number of pods which have a Ready condition. + // + // This field is alpha-level. The job controller populates the field when + // the feature gate JobReadyPods is enabled (disabled by default). + // +optional + optional int32 ready = 9; } // JobTemplateSpec describes the data a Job should have when created from a template diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index beeaa8325a0..1cf1c7eb9a5 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -225,7 +225,7 @@ type JobStatus struct { // +optional CompletionTime *metav1.Time `json:"completionTime,omitempty" protobuf:"bytes,3,opt,name=completionTime"` - // The number of actively running pods. + // The number of pending and running pods. // +optional Active int32 `json:"active,omitempty" protobuf:"varint,4,opt,name=active"` @@ -265,6 +265,13 @@ type JobStatus struct { // remains null. // +optional UncountedTerminatedPods *UncountedTerminatedPods `json:"uncountedTerminatedPods,omitempty" protobuf:"bytes,8,opt,name=uncountedTerminatedPods"` + + // The number of pods which have a Ready condition. + // + // This field is alpha-level. The job controller populates the field when + // the feature gate JobReadyPods is enabled (disabled by default). + // +optional + Ready *int32 `json:"ready,omitempty" protobuf:"varint,9,opt,name=ready"` } // UncountedTerminatedPods holds UIDs of Pods that have terminated but haven't diff --git a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go index e27225457b0..269021a9c04 100644 --- a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go @@ -132,11 +132,12 @@ var map_JobStatus = map[string]string{ "conditions": "The latest available observations of an object's current state. When a Job fails, one of the conditions will have type \"Failed\" and status true. When a Job is suspended, one of the conditions will have type \"Suspended\" and status true; when the Job is resumed, the status of this condition will become false. When a Job is completed, one of the conditions will have type \"Complete\" and status true. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", "startTime": "Represents time when the job controller started processing a job. When a Job is created in the suspended state, this field is not set until the first time it is resumed. This field is reset every time a Job is resumed from suspension. It is represented in RFC3339 form and is in UTC.", "completionTime": "Represents time when the job was completed. It is not guaranteed to be set in happens-before order across separate operations. It is represented in RFC3339 form and is in UTC. The completion time is only set when the job finishes successfully.", - "active": "The number of actively running pods.", + "active": "The number of pending and running pods.", "succeeded": "The number of pods which reached phase Succeeded.", "failed": "The number of pods which reached phase Failed.", "completedIndexes": "CompletedIndexes holds the completed indexes when .spec.completionMode = \"Indexed\" in a text format. The indexes are represented as decimal integers separated by commas. The numbers are listed in increasing order. Three or more consecutive numbers are compressed and represented by the first and last element of the series, separated by a hyphen. For example, if the completed indexes are 1, 3, 4, 5 and 7, they are represented as \"1,3-5,7\".", "uncountedTerminatedPods": "UncountedTerminatedPods holds the UIDs of Pods that have terminated but the job controller hasn't yet accounted for in the status counters.\n\nThe job controller creates pods with a finalizer. When a pod terminates (succeeded or failed), the controller does three steps to account for it in the job status: (1) Add the pod UID to the arrays in this field. (2) Remove the pod finalizer. (3) Remove the pod UID from the arrays while increasing the corresponding\n counter.\n\nThis field is beta-level. The job controller only makes use of this field when the feature gate JobTrackingWithFinalizers is enabled (enabled by default). Old jobs might not be tracked using this field, in which case the field remains null.", + "ready": "The number of pods which have a Ready condition.\n\nThis field is alpha-level. The job controller populates the field when the feature gate JobReadyPods is enabled (disabled by default).", } func (JobStatus) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go index e6915a456f9..a9806a50246 100644 --- a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go @@ -319,6 +319,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) { *out = new(UncountedTerminatedPods) (*in).DeepCopyInto(*out) } + if in.Ready != nil { + in, out := &in.Ready, &out.Ready + *out = new(int32) + **out = **in + } return } diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json index 530bf57dc86..df74f0ace43 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.json @@ -1573,6 +1573,7 @@ "failed": [ "W" ] - } + }, + "ready": -1917014749 } } \ No newline at end of file diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb index 0738639c596..cd5da71dc1c 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb and b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml index 7689fb98830..52a975eda96 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.yaml @@ -1071,6 +1071,7 @@ status: status: 翻颌徚J殦殐ƕ蟶ŃēÖ釐駆Ŕƿe魛ĩ type: ɓ为\Ŧƺ猑\#ȼ縤ɰTaI楅© failed: 77405208 + ready: -1917014749 succeeded: -377965530 uncountedTerminatedPods: failed: diff --git a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/jobstatus.go b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/jobstatus.go index ba7e27e0850..a36d5d0ae11 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/jobstatus.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/jobstatus.go @@ -33,6 +33,7 @@ type JobStatusApplyConfiguration struct { Failed *int32 `json:"failed,omitempty"` CompletedIndexes *string `json:"completedIndexes,omitempty"` UncountedTerminatedPods *UncountedTerminatedPodsApplyConfiguration `json:"uncountedTerminatedPods,omitempty"` + Ready *int32 `json:"ready,omitempty"` } // JobStatusApplyConfiguration constructs an declarative configuration of the JobStatus type for use with @@ -109,3 +110,11 @@ func (b *JobStatusApplyConfiguration) WithUncountedTerminatedPods(value *Uncount b.UncountedTerminatedPods = value return b } + +// WithReady sets the Ready field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ready field is set to the value of the last call. +func (b *JobStatusApplyConfiguration) WithReady(value int32) *JobStatusApplyConfiguration { + b.Ready = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go index 2f57aae36f2..67e776fed81 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go @@ -2671,6 +2671,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: failed type: scalar: numeric + - name: ready + type: + scalar: numeric - name: startTime type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go index d7901926cf6..efdc64d42bc 100644 --- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go @@ -2216,7 +2216,11 @@ func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) { if job.Spec.ActiveDeadlineSeconds != nil { w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *job.Spec.ActiveDeadlineSeconds) } - w.Write(LEVEL_0, "Pods Statuses:\t%d Running / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Succeeded, job.Status.Failed) + if job.Status.Ready == nil { + w.Write(LEVEL_0, "Pods Statuses:\t%d Active / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Succeeded, job.Status.Failed) + } else { + w.Write(LEVEL_0, "Pods Statuses:\t%d Active (%d Ready) / %d Succeeded / %d Failed\n", job.Status.Active, *job.Status.Ready, job.Status.Succeeded, job.Status.Failed) + } if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode == batchv1.IndexedCompletion { w.Write(LEVEL_0, "Completed Indexes:\t%s\n", capIndexesListOrNone(job.Status.CompletedIndexes, 50)) } diff --git a/test/integration/job/job_test.go b/test/integration/job/job_test.go index dbf34fdd0ec..b614c8c28ba 100644 --- a/test/integration/job/job_test.go +++ b/test/integration/job/job_test.go @@ -42,6 +42,7 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/util/retry" featuregatetesting "k8s.io/component-base/featuregate/testing" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" jobcontroller "k8s.io/kubernetes/pkg/controller/job" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/test/integration/framework" @@ -108,9 +109,26 @@ func TestNonParallelJob(t *testing.T) { } func TestParallelJob(t *testing.T) { - for _, wFinalizers := range []bool{false, true} { - t.Run(fmt.Sprintf("finalizers=%t", wFinalizers), func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, wFinalizers)() + cases := map[string]struct { + trackWithFinalizers bool + enableReadyPods bool + }{ + "none": {}, + "with finalizers": { + trackWithFinalizers: true, + }, + "ready pods": { + enableReadyPods: true, + }, + "all": { + trackWithFinalizers: true, + enableReadyPods: true, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, tc.trackWithFinalizers)() + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobReadyPods, tc.enableReadyPods)() closeFn, restConfig, clientSet, ns := setup(t, "parallel") defer closeFn() @@ -125,43 +143,71 @@ func TestParallelJob(t *testing.T) { if err != nil { t.Fatalf("Failed to create Job: %v", err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ - Active: 5, - }, wFinalizers) + want := podsByStatus{Active: 5} + if tc.enableReadyPods { + want.Ready = pointer.Int32Ptr(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) + + // Tracks ready pods, if enabled. + if err := setJobPodsReady(ctx, clientSet, jobObj, 2); err != nil { + t.Fatalf("Failed Marking Pods as ready: %v", err) + } + if tc.enableReadyPods { + *want.Ready = 2 + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) + // Failed Pods are replaced. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodFailed, 2); err != nil { t.Fatalf("Failed setting phase %s on Job Pods: %v", v1.PodFailed, err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Active: 5, Failed: 2, - }, wFinalizers) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) // Once one Pod succeeds, no more Pods are created, even if some fail. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodSucceeded, 1); err != nil { t.Fatalf("Failed setting phase %s on Job Pod: %v", v1.PodSucceeded, err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Failed: 2, Succeeded: 1, Active: 4, - }, wFinalizers) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodFailed, 2); err != nil { t.Fatalf("Failed setting phase %s on Job Pods: %v", v1.PodFailed, err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Failed: 4, Succeeded: 1, Active: 2, - }, wFinalizers) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) // No more Pods are created after remaining Pods succeed. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodSucceeded, 2); err != nil { t.Fatalf("Failed setting phase %s on Job Pods: %v", v1.PodSucceeded, err) } validateJobSucceeded(ctx, t, clientSet, jobObj) - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Failed: 4, Succeeded: 3, - }, false) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, false) validateFinishedPodsNoFinalizer(ctx, t, clientSet, jobObj) }) } @@ -228,9 +274,26 @@ func TestParallelJobWithCompletions(t *testing.T) { // number of pods. t.Cleanup(setDuringTest(&jobcontroller.MaxUncountedPods, 10)) t.Cleanup(setDuringTest(&jobcontroller.MaxPodCreateDeletePerSync, 10)) - for _, wFinalizers := range []bool{false, true} { - t.Run(fmt.Sprintf("finalizers=%t", wFinalizers), func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, wFinalizers)() + cases := map[string]struct { + trackWithFinalizers bool + enableReadyPods bool + }{ + "none": {}, + "with finalizers": { + trackWithFinalizers: true, + }, + "ready pods": { + enableReadyPods: true, + }, + "all": { + trackWithFinalizers: true, + enableReadyPods: true, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, tc.trackWithFinalizers)() + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobReadyPods, tc.enableReadyPods)() closeFn, restConfig, clientSet, ns := setup(t, "completions") defer closeFn() ctx, cancel := startJobController(restConfig, clientSet) @@ -245,38 +308,62 @@ func TestParallelJobWithCompletions(t *testing.T) { if err != nil { t.Fatalf("Failed to create Job: %v", err) } - if got := hasJobTrackingAnnotation(jobObj); got != wFinalizers { - t.Errorf("apiserver created job with tracking annotation: %t, want %t", got, wFinalizers) + if got := hasJobTrackingAnnotation(jobObj); got != tc.trackWithFinalizers { + t.Errorf("apiserver created job with tracking annotation: %t, want %t", got, tc.trackWithFinalizers) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ - Active: 54, - }, wFinalizers) + want := podsByStatus{Active: 54} + if tc.enableReadyPods { + want.Ready = pointer.Int32Ptr(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) + + // Tracks ready pods, if enabled. + if err := setJobPodsReady(ctx, clientSet, jobObj, 52); err != nil { + t.Fatalf("Failed Marking Pods as ready: %v", err) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(52) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) + // Failed Pods are replaced. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodFailed, 2); err != nil { t.Fatalf("Failed setting phase %s on Job Pods: %v", v1.PodFailed, err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Active: 54, Failed: 2, - }, wFinalizers) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(50) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) // Pods are created until the number of succeeded Pods equals completions. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodSucceeded, 53); err != nil { t.Fatalf("Failed setting phase %s on Job Pod: %v", v1.PodSucceeded, err) } - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Failed: 2, Succeeded: 53, Active: 3, - }, wFinalizers) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, tc.trackWithFinalizers) // No more Pods are created after the Job completes. if err := setJobPodsPhase(ctx, clientSet, jobObj, v1.PodSucceeded, 3); err != nil { t.Fatalf("Failed setting phase %s on Job Pods: %v", v1.PodSucceeded, err) } validateJobSucceeded(ctx, t, clientSet, jobObj) - validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{ + want = podsByStatus{ Failed: 2, Succeeded: 56, - }, false) + } + if tc.enableReadyPods { + want.Ready = pointer.Int32(0) + } + validateJobPodsStatus(ctx, t, clientSet, jobObj, want, false) validateFinishedPodsNoFinalizer(ctx, t, clientSet, jobObj) }) } @@ -709,6 +796,7 @@ func TestNodeSelectorUpdate(t *testing.T) { type podsByStatus struct { Active int + Ready *int32 Failed int Succeeded int } @@ -723,6 +811,7 @@ func validateJobPodsStatus(ctx context.Context, t *testing.T, clientSet clientse } actualCounts = podsByStatus{ Active: int(updatedJob.Status.Active), + Ready: updatedJob.Status.Ready, Succeeded: int(updatedJob.Status.Succeeded), Failed: int(updatedJob.Status.Failed), } @@ -860,6 +949,28 @@ func validateJobSucceeded(ctx context.Context, t *testing.T, clientSet clientset } func setJobPodsPhase(ctx context.Context, clientSet clientset.Interface, jobObj *batchv1.Job, phase v1.PodPhase, cnt int) error { + op := func(p *v1.Pod) bool { + p.Status.Phase = phase + return true + } + return updateJobPodsStatus(ctx, clientSet, jobObj, op, cnt) +} + +func setJobPodsReady(ctx context.Context, clientSet clientset.Interface, jobObj *batchv1.Job, cnt int) error { + op := func(p *v1.Pod) bool { + if podutil.IsPodReady(p) { + return false + } + p.Status.Conditions = append(p.Status.Conditions, v1.PodCondition{ + Type: v1.PodReady, + Status: v1.ConditionTrue, + }) + return true + } + return updateJobPodsStatus(ctx, clientSet, jobObj, op, cnt) +} + +func updateJobPodsStatus(ctx context.Context, clientSet clientset.Interface, jobObj *batchv1.Job, op func(*v1.Pod) bool, cnt int) error { pods, err := clientSet.CoreV1().Pods(jobObj.Namespace).List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("listing Job Pods: %w", err) @@ -870,7 +981,9 @@ func setJobPodsPhase(ctx context.Context, clientSet clientset.Interface, jobObj break } if p := pod.Status.Phase; isPodOwnedByJob(&pod, jobObj) && p != v1.PodFailed && p != v1.PodSucceeded { - pod.Status.Phase = phase + if !op(&pod) { + continue + } updates = append(updates, pod) } }