Merge pull request #104915 from alculquicondor/job-ready

Track ready pods in Job status
This commit is contained in:
Kubernetes Prow Robot 2021-10-28 09:20:26 -07:00 committed by GitHub
commit 6edcb60d9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 487 additions and 176 deletions

View File

@ -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."

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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": {

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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},

View File

@ -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:])

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -1573,6 +1573,7 @@
"failed": [
"W"
]
}
},
"ready": -1917014749
}
}

View File

@ -1071,6 +1071,7 @@ status:
status: 翻颌徚J殦殐ƕ蟶ŃēÖ釐駆Ŕƿe魛ĩ
type: ɓ为\Ŧƺ猑\#ȼ縤ɰTaI楅©
failed: 77405208
ready: -1917014749
succeeded: -377965530
uncountedTerminatedPods:
failed:

View File

@ -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
}

View File

@ -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

View File

@ -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))
}

View File

@ -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)
}
}