Add Job.spec.completionMode and Job.status.completedIndexes

And IndexedJob feature gate, disabled by default.
Update JobDescriber
This commit is contained in:
Aldo Culquicondor 2020-12-30 11:42:01 -05:00
parent 6404eda8de
commit a1a5868a5a
36 changed files with 642 additions and 157 deletions

View File

@ -4247,6 +4247,10 @@
"format": "int32",
"type": "integer"
},
"completionMode": {
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.alpha.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5.\n\nThis field is alpha-level and is only honored by servers that enable the IndexedJob feature gate. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
"type": "string"
},
"completions": {
"description": "Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
"format": "int32",
@ -4288,6 +4292,10 @@
"format": "int32",
"type": "integer"
},
"completedIndexes": {
"description": "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\".",
"type": "string"
},
"completionTime": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
"description": "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."

View File

@ -18,7 +18,6 @@ package fuzzer
import (
fuzz "github.com/google/gofuzz"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/pkg/apis/batch"
)
@ -53,6 +52,11 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
} else {
j.ManualSelector = nil
}
if c.Rand.Int31()%2 == 0 {
j.CompletionMode = batch.NonIndexedCompletion
} else {
j.CompletionMode = batch.IndexedCompletion
}
},
func(sj *batch.CronJobSpec, c fuzz.Continue) {
c.FuzzNoCustom(sj)

View File

@ -85,6 +85,22 @@ type JobTemplateSpec struct {
Spec JobSpec
}
// CompletionMode specifies how Pod completions of a Job are tracked.
type CompletionMode string
const (
// NonIndexedCompletion is a Job completion mode. In this mode, the Job is
// considered complete when there have been .spec.completions
// successfully completed Pods. Pod completions are homologous to each other.
NonIndexedCompletion CompletionMode = "NonIndexed"
// IndexedCompletion is a Job completion mode. In this mode, the Pods of a
// Job get an associated completion index from 0 to (.spec.completions - 1).
// The Job is considered complete when a Pod completes for each completion
// index.
IndexedCompletion CompletionMode = "Indexed"
)
// JobSpec describes how the job execution will look like.
type JobSpec struct {
@ -149,6 +165,28 @@ type JobSpec struct {
// TTLAfterFinished feature.
// +optional
TTLSecondsAfterFinished *int32
// CompletionMode specifies how Pod completions are tracked. It can be
// `NonIndexed` (default) or `Indexed`.
//
// `NonIndexed` means that the Job is considered complete when there have
// been .spec.completions successfully completed Pods. Each Pod completion is
// homologous to each other.
//
// `Indexed` means that the Pods of a
// Job get an associated completion index from 0 to (.spec.completions - 1),
// available in the annotation batch.alpha.kubernetes.io/job-completion-index.
// The Job is considered complete when there is one successfully completed Pod
// for each index.
// When value is `Indexed`, .spec.completions must be specified and
// `.spec.parallelism` must be less than or equal to 10^5.
//
// This field is alpha-level and is only honored by servers that enable the
// IndexedJob feature gate. More completion modes can be added in the future.
// If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job.
// +optional
CompletionMode CompletionMode
}
// JobStatus represents the current state of a Job.
@ -183,6 +221,16 @@ type JobStatus struct {
// The number of pods which reached phase Failed.
// +optional
Failed int32
// 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".
// +optional
CompletedIndexes string
}
// JobConditionType is a valid value for JobCondition.Type

View File

@ -46,4 +46,7 @@ func SetDefaults_Job(obj *batchv1.Job) {
if labels != nil && len(obj.Labels) == 0 {
obj.Labels = labels
}
if len(obj.Spec.CompletionMode) == 0 {
obj.Spec.CompletionMode = batchv1.NonIndexedCompletion
}
}

View File

@ -21,15 +21,15 @@ import (
"testing"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
. "k8s.io/kubernetes/pkg/apis/batch/v1"
_ "k8s.io/kubernetes/pkg/apis/core/install"
utilpointer "k8s.io/utils/pointer"
. "k8s.io/kubernetes/pkg/apis/batch/v1"
)
func TestSetDefaultJob(t *testing.T) {
@ -49,9 +49,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
expectLabels: true,
@ -69,9 +70,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
},
@ -86,8 +88,9 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(0),
BackoffLimit: utilpointer.Int32Ptr(6),
Parallelism: utilpointer.Int32Ptr(0),
BackoffLimit: utilpointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
expectLabels: true,
@ -103,8 +106,9 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(2),
BackoffLimit: utilpointer.Int32Ptr(6),
Parallelism: utilpointer.Int32Ptr(2),
BackoffLimit: utilpointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
expectLabels: true,
@ -120,9 +124,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(2),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: utilpointer.Int32Ptr(2),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
expectLabels: true,
@ -138,9 +143,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(5),
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(5),
CompletionMode: batchv1.NonIndexedCompletion,
},
},
expectLabels: true,
@ -148,9 +154,10 @@ func TestSetDefaultJob(t *testing.T) {
"All set -> no change": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@ -158,9 +165,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@ -171,9 +179,10 @@ func TestSetDefaultJob(t *testing.T) {
"All set, flipped -> no change": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion,
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@ -181,9 +190,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion,
},
},
expectLabels: true,
@ -191,37 +201,41 @@ func TestSetDefaultJob(t *testing.T) {
}
for name, test := range tests {
original := test.original
expected := test.expected
obj2 := roundTrip(t, runtime.Object(original))
actual, ok := obj2.(*batchv1.Job)
if !ok {
t.Errorf("%s: unexpected object: %v", name, actual)
t.FailNow()
}
t.Run(name, func(t *testing.T) {
validateDefaultInt32(t, name, "Completions", actual.Spec.Completions, expected.Spec.Completions)
validateDefaultInt32(t, name, "Parallelism", actual.Spec.Parallelism, expected.Spec.Parallelism)
validateDefaultInt32(t, name, "BackoffLimit", actual.Spec.BackoffLimit, expected.Spec.BackoffLimit)
if test.expectLabels != reflect.DeepEqual(actual.Labels, actual.Spec.Template.Labels) {
if test.expectLabels {
t.Errorf("%s: expected: %v, got: %v", name, actual.Spec.Template.Labels, actual.Labels)
} else {
t.Errorf("%s: unexpected equality: %v", name, actual.Labels)
original := test.original
expected := test.expected
obj2 := roundTrip(t, runtime.Object(original))
actual, ok := obj2.(*batchv1.Job)
if !ok {
t.Fatalf("Unexpected object: %v", actual)
}
}
validateDefaultInt32(t, "Completions", actual.Spec.Completions, expected.Spec.Completions)
validateDefaultInt32(t, "Parallelism", actual.Spec.Parallelism, expected.Spec.Parallelism)
validateDefaultInt32(t, "BackoffLimit", actual.Spec.BackoffLimit, expected.Spec.BackoffLimit)
if test.expectLabels != reflect.DeepEqual(actual.Labels, actual.Spec.Template.Labels) {
if test.expectLabels {
t.Errorf("Expected labels: %v, got: %v", actual.Spec.Template.Labels, actual.Labels)
} else {
t.Errorf("Unexpected equality: %v", actual.Labels)
}
}
if actual.Spec.CompletionMode != expected.Spec.CompletionMode {
t.Errorf("Got CompletionMode: %v, want: %v", actual.Spec.CompletionMode, expected.Spec.CompletionMode)
}
})
}
}
func validateDefaultInt32(t *testing.T, name string, field string, actual *int32, expected *int32) {
func validateDefaultInt32(t *testing.T, field string, actual *int32, expected *int32) {
if (actual == nil) != (expected == nil) {
t.Errorf("%s: got different *%s than expected: %v %v", name, field, actual, expected)
t.Errorf("Got different *%s than expected: %v %v", field, actual, expected)
}
if actual != nil && expected != nil {
if *actual != *expected {
t.Errorf("%s: got different %s than expected: %d %d", name, field, *actual, *expected)
t.Errorf("Got different %s than expected: %d %d", field, *actual, *expected)
}
}
}

View File

@ -208,6 +208,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec,
return err
}
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = batch.CompletionMode(in.CompletionMode)
return nil
}
@ -222,6 +223,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec,
return err
}
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = v1.CompletionMode(in.CompletionMode)
return nil
}
@ -232,6 +234,7 @@ func autoConvert_v1_JobStatus_To_batch_JobStatus(in *v1.JobStatus, out *batch.Jo
out.Active = in.Active
out.Succeeded = in.Succeeded
out.Failed = in.Failed
out.CompletedIndexes = in.CompletedIndexes
return nil
}
@ -247,6 +250,7 @@ func autoConvert_batch_JobStatus_To_v1_JobStatus(in *batch.JobStatus, out *v1.Jo
out.Active = in.Active
out.Succeeded = in.Succeeded
out.Failed = in.Failed
out.CompletedIndexes = in.CompletedIndexes
return nil
}

View File

@ -31,6 +31,11 @@ import (
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
)
// maxParallelismForIndexJob is the maximum parallelism that an Indexed Job
// is allowed to have. This threshold allows to cap the length of
// .status.completedIndexes.
const maxParallelismForIndexedJob = 100000
// ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
// metadata, and the labels on the pod template are as generated.
//
@ -124,6 +129,20 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidatio
if spec.TTLSecondsAfterFinished != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...)
}
// CompletionMode might be empty when IndexedJob feature gate is disabled.
if spec.CompletionMode != "" {
if spec.CompletionMode != batch.NonIndexedCompletion && spec.CompletionMode != batch.IndexedCompletion {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("completionMode"), spec.CompletionMode, []string{string(batch.NonIndexedCompletion), string(batch.IndexedCompletion)}))
}
if spec.CompletionMode == batch.IndexedCompletion {
if spec.Completions == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("completions"), fmt.Sprintf("when completion mode is %s", batch.IndexedCompletion)))
}
if spec.Parallelism != nil && *spec.Parallelism > maxParallelismForIndexedJob {
allErrs = append(allErrs, field.Invalid(fldPath.Child("parallelism"), *spec.Parallelism, fmt.Sprintf("must be less than or equal to %d when completion mode is %s", maxParallelismForIndexedJob, batch.IndexedCompletion)))
}
}
}
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"), opts)...)
@ -170,6 +189,7 @@ func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path, opt
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.CompletionMode, oldSpec.CompletionMode, fldPath.Child("completionMode"))...)
return allErrs
}

View File

@ -78,7 +78,7 @@ func TestValidateJob(t *testing.T) {
validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
successCases := map[string]batch.Job{
"manual selector": {
"valid manual selector": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
@ -90,7 +90,7 @@ func TestValidateJob(t *testing.T) {
Template: validPodTemplateSpecForManual,
},
},
"generated selector": {
"valid generated selector": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
@ -101,6 +101,32 @@ func TestValidateJob(t *testing.T) {
Template: validPodTemplateSpecForGenerated,
},
},
"valid NonIndexed completion mode": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.NonIndexedCompletion,
},
},
"valid Indexed completion mode": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion,
Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(100000),
},
},
}
for k, v := range successCases {
t.Run(k, func(t *testing.T) {
@ -158,7 +184,7 @@ func TestValidateJob(t *testing.T) {
Template: validPodTemplateSpecForGenerated,
},
},
"spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": {
"spec.template.metadata.labels: Invalid value: map[string]string{\"y\":\"z\"}: `selector` does not match template `labels`": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
@ -179,7 +205,7 @@ func TestValidateJob(t *testing.T) {
},
},
},
"spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
"spec.template.metadata.labels: Invalid value: map[string]string{\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
@ -242,7 +268,7 @@ func TestValidateJob(t *testing.T) {
},
},
},
"spec.ttlSecondsAfterFinished:must be greater than or equal to 0": {
"spec.ttlSecondsAfterFinished: must be greater than or equal to 0": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
@ -254,6 +280,32 @@ func TestValidateJob(t *testing.T) {
Template: validPodTemplateSpecForGenerated,
},
},
"spec.completions: Required value: when completion mode is Indexed": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion,
},
},
"spec.parallelism: must be less than or equal to 100000 when completion mode is Indexed": {
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
Namespace: metav1.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion,
Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(100001),
},
},
}
for k, v := range errorCases {
@ -262,7 +314,7 @@ func TestValidateJob(t *testing.T) {
if len(errs) == 0 {
t.Errorf("expected failure for %s", k)
} else {
s := strings.Split(k, ":")
s := strings.SplitN(k, ":", 2)
err := errs[0]
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
t.Errorf("unexpected error: %v, expected: %s", err, k)
@ -346,6 +398,24 @@ func TestValidateJobUpdate(t *testing.T) {
Field: "spec.template",
},
},
"immutable completion mode": {
old: batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion,
Completions: pointer.Int32Ptr(2),
},
},
update: func(job *batch.Job) {
job.Spec.CompletionMode = batch.NonIndexedCompletion
},
err: &field.Error{
Type: field.ErrorTypeInvalid,
Field: "spec.completionMode",
},
},
}
ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
for k, tc := range cases {

View File

@ -311,6 +311,12 @@ const (
// Allow TTL controller to clean up Pods and Jobs after they finish.
TTLAfterFinished featuregate.Feature = "TTLAfterFinished"
// owner: @alculquicondor
// alpha: v1.21
//
// Allows Job controller to manage Pod completions per completion index.
IndexedJob featuregate.Feature = "IndexedJob"
// owner: @dashpole
// alpha: v1.13
// beta: v1.15
@ -733,6 +739,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
VolumeSnapshotDataSource: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.21
ProcMountType: {Default: false, PreRelease: featuregate.Alpha},
TTLAfterFinished: {Default: true, PreRelease: featuregate.Beta},
IndexedJob: {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

@ -80,6 +80,10 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
job.Spec.TTLSecondsAfterFinished = nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) {
job.Spec.CompletionMode = batch.NonIndexedCompletion
}
pod.DropDisabledTemplateFields(&job.Spec.Template, nil)
}
@ -93,6 +97,10 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
newJob.Spec.TTLSecondsAfterFinished = nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && oldJob.Spec.CompletionMode == batch.NonIndexedCompletion {
newJob.Spec.CompletionMode = batch.NonIndexedCompletion
}
pod.DropDisabledTemplateFields(&newJob.Spec.Template, &oldJob.Spec.Template)
}

View File

@ -43,16 +43,21 @@ func newInt32(i int32) *int32 {
func TestJobStrategy(t *testing.T) {
cases := map[string]struct {
ttlEnabled bool
ttlEnabled bool
indexedJobEnabled bool
}{
"features disabled": {},
"ttl enabled": {
ttlEnabled: true,
},
"indexed job enabled": {
indexedJobEnabled: true,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TTLAfterFinished, tc.ttlEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IndexedJob, tc.indexedJobEnabled)()
testJobStrategy(t)
})
}
@ -60,6 +65,7 @@ func TestJobStrategy(t *testing.T) {
func testJobStrategy(t *testing.T) {
ttlEnabled := utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished)
indexedJobEnabled := utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob)
ctx := genericapirequest.NewDefaultContext()
if !Strategy.NamespaceScoped() {
t.Errorf("Job must be namespace scoped")
@ -87,10 +93,13 @@ func testJobStrategy(t *testing.T) {
Namespace: metav1.NamespaceDefault,
},
Spec: batch.JobSpec{
Selector: validSelector,
Template: validPodTemplateSpec,
TTLSecondsAfterFinished: newInt32(0), // Set TTL
ManualSelector: newBool(true),
Selector: validSelector,
Template: validPodTemplateSpec,
ManualSelector: newBool(true),
Completions: newInt32(2),
// Set gated values.
TTLSecondsAfterFinished: newInt32(0),
CompletionMode: batch.IndexedCompletion,
},
Status: batch.JobStatus{
Active: 11,
@ -106,15 +115,21 @@ func testJobStrategy(t *testing.T) {
t.Errorf("Unexpected error validating %v", errs)
}
if ttlEnabled != (job.Spec.TTLSecondsAfterFinished != nil) {
t.Errorf("Job should allow setting .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished)
t.Errorf("Job should allow setting .spec.ttlSecondsAfterFinished only when %v feature is enabled", features.TTLAfterFinished)
}
if indexedJobEnabled != (job.Spec.CompletionMode != batch.NonIndexedCompletion) {
t.Errorf("Job should allow setting .spec.completionMode=Indexed only when %v feature is enabled", features.IndexedJob)
}
parallelism := int32(10)
updatedJob := &batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"},
Spec: batch.JobSpec{
Parallelism: &parallelism,
TTLSecondsAfterFinished: newInt32(1), // Update TTL
Parallelism: &parallelism,
Completions: newInt32(2),
// Update gated features.
TTLSecondsAfterFinished: newInt32(1),
CompletionMode: batch.IndexedCompletion, // No change because field is immutable.
},
Status: batch.JobStatus{
Active: 11,
@ -135,13 +150,18 @@ func testJobStrategy(t *testing.T) {
t.Errorf("Expected a validation error")
}
// Existing TTLSecondsAfterFinished should be preserved
// Existing gated fields should be preserved
job.Spec.TTLSecondsAfterFinished = newInt32(1)
job.Spec.CompletionMode = batch.IndexedCompletion
updatedJob.Spec.TTLSecondsAfterFinished = newInt32(2)
updatedJob.Spec.CompletionMode = batch.IndexedCompletion
Strategy.PrepareForUpdate(ctx, updatedJob, job)
if job.Spec.TTLSecondsAfterFinished == nil || updatedJob.Spec.TTLSecondsAfterFinished == nil {
t.Errorf("existing TTLSecondsAfterFinished should be preserved")
}
if job.Spec.CompletionMode == "" || updatedJob.Spec.CompletionMode == "" {
t.Errorf("existing completionMode should be preserved")
}
// Make sure we correctly implement the interface.
// Otherwise a typo could silently change the default.

View File

@ -198,66 +198,69 @@ func init() {
}
var fileDescriptor_3b52da57c93de713 = []byte{
// 929 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x6f, 0xe3, 0x44,
0x14, 0xad, 0x9b, 0xa6, 0x4d, 0xa6, 0x1f, 0x5b, 0x06, 0x55, 0x1b, 0x0a, 0xb2, 0x97, 0x20, 0xa1,
0x82, 0x84, 0x4d, 0x4b, 0x85, 0x10, 0x02, 0xa4, 0x75, 0x51, 0x25, 0xaa, 0x54, 0x5b, 0x26, 0x59,
0x21, 0x21, 0x90, 0x18, 0xdb, 0x37, 0x89, 0x89, 0xed, 0xb1, 0x3c, 0x93, 0x48, 0x7d, 0xe3, 0x27,
0xf0, 0x23, 0x10, 0x7f, 0x82, 0x77, 0xd4, 0xc7, 0x7d, 0xdc, 0x27, 0x8b, 0x9a, 0x1f, 0xc0, 0xfb,
0x3e, 0xa1, 0x19, 0x3b, 0xb6, 0xd3, 0x26, 0xa2, 0xcb, 0x5b, 0xe6, 0xcc, 0x39, 0xe7, 0x5e, 0xcf,
0x3d, 0xb9, 0xe8, 0x8b, 0xc9, 0x67, 0xdc, 0xf4, 0x99, 0x35, 0x99, 0x3a, 0x90, 0x44, 0x20, 0x80,
0x5b, 0x33, 0x88, 0x3c, 0x96, 0x58, 0xc5, 0x05, 0x8d, 0x7d, 0xcb, 0xa1, 0xc2, 0x1d, 0x5b, 0xb3,
0x63, 0x6b, 0x04, 0x11, 0x24, 0x54, 0x80, 0x67, 0xc6, 0x09, 0x13, 0x0c, 0xbf, 0x99, 0x93, 0x4c,
0x1a, 0xfb, 0xa6, 0x22, 0x99, 0xb3, 0xe3, 0xc3, 0x8f, 0x46, 0xbe, 0x18, 0x4f, 0x1d, 0xd3, 0x65,
0xa1, 0x35, 0x62, 0x23, 0x66, 0x29, 0xae, 0x33, 0x1d, 0xaa, 0x93, 0x3a, 0xa8, 0x5f, 0xb9, 0xc7,
0x61, 0xb7, 0x56, 0xc8, 0x65, 0x09, 0x2c, 0xa9, 0x73, 0x78, 0x5a, 0x71, 0x42, 0xea, 0x8e, 0xfd,
0x08, 0x92, 0x6b, 0x2b, 0x9e, 0x8c, 0x24, 0xc0, 0xad, 0x10, 0x04, 0x5d, 0xa6, 0xb2, 0x56, 0xa9,
0x92, 0x69, 0x24, 0xfc, 0x10, 0xee, 0x09, 0x3e, 0xfd, 0x2f, 0x01, 0x77, 0xc7, 0x10, 0xd2, 0xbb,
0xba, 0xee, 0x3f, 0x1a, 0x6a, 0x5c, 0x30, 0x07, 0xff, 0x84, 0x5a, 0xb2, 0x17, 0x8f, 0x0a, 0xda,
0xd1, 0x9e, 0x68, 0x47, 0xdb, 0x27, 0x1f, 0x9b, 0xd5, 0x0b, 0x95, 0x96, 0x66, 0x3c, 0x19, 0x49,
0x80, 0x9b, 0x92, 0x6d, 0xce, 0x8e, 0xcd, 0x67, 0xce, 0xcf, 0xe0, 0x8a, 0x4b, 0x10, 0xd4, 0xc6,
0x37, 0xa9, 0xb1, 0x96, 0xa5, 0x06, 0xaa, 0x30, 0x52, 0xba, 0xe2, 0xaf, 0xd0, 0x06, 0x8f, 0xc1,
0xed, 0xac, 0x2b, 0xf7, 0x77, 0xcc, 0x25, 0xef, 0x6f, 0x5e, 0x30, 0xa7, 0x1f, 0x83, 0x6b, 0xef,
0x14, 0x4e, 0x1b, 0xf2, 0x44, 0x94, 0x0e, 0x9f, 0xa3, 0x4d, 0x2e, 0xa8, 0x98, 0xf2, 0x4e, 0x43,
0x39, 0xe8, 0x2b, 0x1d, 0x14, 0xcb, 0xde, 0x2b, 0x3c, 0x36, 0xf3, 0x33, 0x29, 0xd4, 0xdd, 0x3f,
0x1b, 0x68, 0xe7, 0x82, 0x39, 0x67, 0x2c, 0xf2, 0x7c, 0xe1, 0xb3, 0x08, 0x9f, 0xa2, 0x0d, 0x71,
0x1d, 0x83, 0xfa, 0xec, 0xb6, 0xfd, 0x64, 0x5e, 0x7a, 0x70, 0x1d, 0xc3, 0xab, 0xd4, 0xd8, 0xaf,
0x73, 0x25, 0x46, 0x14, 0x1b, 0xf7, 0xca, 0x76, 0xd6, 0x95, 0xee, 0x74, 0xb1, 0xdc, 0xab, 0xd4,
0x58, 0x92, 0x0e, 0xb3, 0x74, 0x5a, 0x6c, 0x0a, 0x8f, 0xd0, 0x6e, 0x40, 0xb9, 0xb8, 0x4a, 0x98,
0x03, 0x03, 0x3f, 0x84, 0xe2, 0x1b, 0x3f, 0x7c, 0xd8, 0x0c, 0xa4, 0xc2, 0x3e, 0x28, 0x1a, 0xd8,
0xed, 0xd5, 0x8d, 0xc8, 0xa2, 0x2f, 0x9e, 0x21, 0x2c, 0x81, 0x41, 0x42, 0x23, 0x9e, 0x7f, 0x92,
0xac, 0xb6, 0xf1, 0xda, 0xd5, 0x0e, 0x8b, 0x6a, 0xb8, 0x77, 0xcf, 0x8d, 0x2c, 0xa9, 0x80, 0xdf,
0x47, 0x9b, 0x09, 0x50, 0xce, 0xa2, 0x4e, 0x53, 0x3d, 0x57, 0x39, 0x1d, 0xa2, 0x50, 0x52, 0xdc,
0xe2, 0x0f, 0xd0, 0x56, 0x08, 0x9c, 0xd3, 0x11, 0x74, 0x36, 0x15, 0xf1, 0x51, 0x41, 0xdc, 0xba,
0xcc, 0x61, 0x32, 0xbf, 0xef, 0xfe, 0xae, 0xa1, 0xad, 0x0b, 0xe6, 0xf4, 0x7c, 0x2e, 0xf0, 0x0f,
0xf7, 0xe2, 0x6b, 0x3e, 0xec, 0x63, 0xa4, 0x5a, 0x85, 0x77, 0xbf, 0xa8, 0xd3, 0x9a, 0x23, 0xb5,
0xe8, 0x7e, 0x89, 0x9a, 0xbe, 0x80, 0x50, 0x8e, 0xba, 0x71, 0xb4, 0x7d, 0xd2, 0x59, 0x95, 0x3c,
0x7b, 0xb7, 0x30, 0x69, 0x7e, 0x23, 0xe9, 0x24, 0x57, 0x75, 0xff, 0xd8, 0x50, 0x8d, 0xca, 0x2c,
0xe3, 0x63, 0xb4, 0x1d, 0xd3, 0x84, 0x06, 0x01, 0x04, 0x3e, 0x0f, 0x55, 0xaf, 0x4d, 0xfb, 0x51,
0x96, 0x1a, 0xdb, 0x57, 0x15, 0x4c, 0xea, 0x1c, 0x29, 0x71, 0x59, 0x18, 0x07, 0x20, 0x1f, 0x33,
0x8f, 0x5b, 0x21, 0x39, 0xab, 0x60, 0x52, 0xe7, 0xe0, 0x67, 0xe8, 0x80, 0xba, 0xc2, 0x9f, 0xc1,
0xd7, 0x40, 0xbd, 0xc0, 0x8f, 0xa0, 0x0f, 0x2e, 0x8b, 0xbc, 0xfc, 0xaf, 0xd3, 0xb0, 0xdf, 0xca,
0x52, 0xe3, 0xe0, 0xe9, 0x32, 0x02, 0x59, 0xae, 0xc3, 0xa7, 0x68, 0xc7, 0xa1, 0xee, 0x84, 0x0d,
0x87, 0x3d, 0x3f, 0xf4, 0x45, 0x67, 0x4b, 0x35, 0xb1, 0x9f, 0xa5, 0xc6, 0x8e, 0x5d, 0xc3, 0xc9,
0x02, 0x0b, 0xff, 0x88, 0x5a, 0x1c, 0x02, 0x70, 0x05, 0x4b, 0x8a, 0x88, 0x7d, 0xf2, 0xc0, 0xa9,
0x50, 0x07, 0x82, 0x7e, 0x21, 0xb5, 0x77, 0xe4, 0x58, 0xe6, 0x27, 0x52, 0x5a, 0xe2, 0xcf, 0xd1,
0x5e, 0x48, 0xa3, 0x29, 0x2d, 0x99, 0x2a, 0x5b, 0x2d, 0x1b, 0x67, 0xa9, 0xb1, 0x77, 0xb9, 0x70,
0x43, 0xee, 0x30, 0xf1, 0xb7, 0xa8, 0x25, 0x20, 0x8c, 0x03, 0x2a, 0xf2, 0xa0, 0x6d, 0x9f, 0xbc,
0x57, 0x9f, 0xaa, 0xfc, 0xbf, 0xca, 0x46, 0xae, 0x98, 0x37, 0x28, 0x68, 0x6a, 0x31, 0x95, 0x29,
0x99, 0xa3, 0xa4, 0xb4, 0xc1, 0xcf, 0xd1, 0x63, 0x21, 0x82, 0xe2, 0xc5, 0x9e, 0x0e, 0x05, 0x24,
0xe7, 0x7e, 0xe4, 0xf3, 0x31, 0x78, 0x9d, 0x96, 0x7a, 0xae, 0xb7, 0xb3, 0xd4, 0x78, 0x3c, 0x18,
0xf4, 0x96, 0x51, 0xc8, 0x2a, 0x6d, 0xf7, 0xb7, 0x06, 0x6a, 0x97, 0x5b, 0x0d, 0x3f, 0x47, 0xc8,
0x9d, 0xef, 0x10, 0xde, 0xd1, 0x54, 0x1e, 0xdf, 0x5d, 0x95, 0xc7, 0x72, 0xdb, 0x54, 0xab, 0xb9,
0x84, 0x38, 0xa9, 0x19, 0xe1, 0xef, 0x50, 0x9b, 0x0b, 0x9a, 0x08, 0xb5, 0x0d, 0xd6, 0x5f, 0x7b,
0x1b, 0xec, 0x66, 0xa9, 0xd1, 0xee, 0xcf, 0x0d, 0x48, 0xe5, 0x85, 0x87, 0x68, 0xaf, 0x0a, 0xe6,
0xff, 0xdc, 0x6c, 0x6a, 0x9e, 0x67, 0x0b, 0x2e, 0xe4, 0x8e, 0xab, 0xdc, 0x2f, 0x79, 0x72, 0x55,
0xd0, 0x9a, 0xd5, 0x7e, 0xc9, 0x63, 0x4e, 0x8a, 0x5b, 0x6c, 0xa1, 0x36, 0x9f, 0xba, 0x2e, 0x80,
0x07, 0x9e, 0x8a, 0x4b, 0xd3, 0x7e, 0xa3, 0xa0, 0xb6, 0xfb, 0xf3, 0x0b, 0x52, 0x71, 0xa4, 0xf1,
0x90, 0xfa, 0x01, 0x78, 0x2a, 0x26, 0x35, 0xe3, 0x73, 0x85, 0x92, 0xe2, 0xd6, 0x3e, 0xba, 0xb9,
0xd5, 0xd7, 0x5e, 0xdc, 0xea, 0x6b, 0x2f, 0x6f, 0xf5, 0xb5, 0x5f, 0x32, 0x5d, 0xbb, 0xc9, 0x74,
0xed, 0x45, 0xa6, 0x6b, 0x2f, 0x33, 0x5d, 0xfb, 0x2b, 0xd3, 0xb5, 0x5f, 0xff, 0xd6, 0xd7, 0xbe,
0x5f, 0x9f, 0x1d, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x73, 0xe7, 0x7a, 0xb8, 0x08, 0x00,
0x00,
// 979 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x5f, 0x6f, 0xe3, 0xc4,
0x17, 0x6d, 0x9a, 0xa6, 0x4d, 0xa6, 0x7f, 0xb6, 0xbf, 0xf9, 0xa9, 0x5a, 0x53, 0x50, 0xbc, 0x04,
0x09, 0x15, 0x24, 0x6c, 0x5a, 0x2a, 0x84, 0x10, 0x20, 0xad, 0xbb, 0xaa, 0xb4, 0x55, 0xaa, 0x2d,
0xd3, 0xac, 0x90, 0x10, 0x48, 0x8c, 0xed, 0x9b, 0xd4, 0xd4, 0xf6, 0x58, 0x9e, 0x49, 0x44, 0xdf,
0xf8, 0x02, 0x48, 0x7c, 0x0a, 0x3e, 0x0a, 0xea, 0xe3, 0x3e, 0xee, 0x93, 0x45, 0xcd, 0x1b, 0x2f,
0xbc, 0xf7, 0x09, 0xcd, 0x78, 0x62, 0x3b, 0x6d, 0x22, 0xba, 0xbc, 0x79, 0xee, 0x3d, 0xe7, 0xdc,
0xeb, 0x3b, 0x67, 0x2e, 0xfa, 0xe2, 0xf2, 0x33, 0x6e, 0x05, 0xcc, 0xbe, 0x1c, 0xbb, 0x90, 0xc6,
0x20, 0x80, 0xdb, 0x13, 0x88, 0x7d, 0x96, 0xda, 0x3a, 0x41, 0x93, 0xc0, 0x76, 0xa9, 0xf0, 0x2e,
0xec, 0xc9, 0xbe, 0x3d, 0x82, 0x18, 0x52, 0x2a, 0xc0, 0xb7, 0x92, 0x94, 0x09, 0x86, 0xff, 0x5f,
0x80, 0x2c, 0x9a, 0x04, 0x96, 0x02, 0x59, 0x93, 0xfd, 0xdd, 0x8f, 0x46, 0x81, 0xb8, 0x18, 0xbb,
0x96, 0xc7, 0x22, 0x7b, 0xc4, 0x46, 0xcc, 0x56, 0x58, 0x77, 0x3c, 0x54, 0x27, 0x75, 0x50, 0x5f,
0x85, 0xc6, 0x6e, 0xaf, 0x56, 0xc8, 0x63, 0x29, 0xcc, 0xa9, 0xb3, 0x7b, 0x58, 0x61, 0x22, 0xea,
0x5d, 0x04, 0x31, 0xa4, 0x57, 0x76, 0x72, 0x39, 0x92, 0x01, 0x6e, 0x47, 0x20, 0xe8, 0x3c, 0x96,
0xbd, 0x88, 0x95, 0x8e, 0x63, 0x11, 0x44, 0x70, 0x8f, 0xf0, 0xe9, 0xbf, 0x11, 0xb8, 0x77, 0x01,
0x11, 0xbd, 0xcb, 0xeb, 0xfd, 0xdd, 0x40, 0xcd, 0x13, 0xe6, 0xe2, 0x1f, 0x50, 0x5b, 0xf6, 0xe2,
0x53, 0x41, 0x8d, 0xc6, 0x93, 0xc6, 0xde, 0xfa, 0xc1, 0xc7, 0x56, 0x35, 0xa1, 0x52, 0xd2, 0x4a,
0x2e, 0x47, 0x32, 0xc0, 0x2d, 0x89, 0xb6, 0x26, 0xfb, 0xd6, 0x0b, 0xf7, 0x47, 0xf0, 0xc4, 0x29,
0x08, 0xea, 0xe0, 0xeb, 0xcc, 0x5c, 0xca, 0x33, 0x13, 0x55, 0x31, 0x52, 0xaa, 0xe2, 0xaf, 0xd0,
0x0a, 0x4f, 0xc0, 0x33, 0x96, 0x95, 0xfa, 0x3b, 0xd6, 0x9c, 0xf9, 0x5b, 0x27, 0xcc, 0x3d, 0x4f,
0xc0, 0x73, 0x36, 0xb4, 0xd2, 0x8a, 0x3c, 0x11, 0xc5, 0xc3, 0xc7, 0x68, 0x95, 0x0b, 0x2a, 0xc6,
0xdc, 0x68, 0x2a, 0x85, 0xee, 0x42, 0x05, 0x85, 0x72, 0xb6, 0xb4, 0xc6, 0x6a, 0x71, 0x26, 0x9a,
0xdd, 0xfb, 0xbd, 0x89, 0x36, 0x4e, 0x98, 0x7b, 0xc4, 0x62, 0x3f, 0x10, 0x01, 0x8b, 0xf1, 0x21,
0x5a, 0x11, 0x57, 0x09, 0xa8, 0xdf, 0xee, 0x38, 0x4f, 0xa6, 0xa5, 0x07, 0x57, 0x09, 0xdc, 0x66,
0xe6, 0x76, 0x1d, 0x2b, 0x63, 0x44, 0xa1, 0x71, 0xbf, 0x6c, 0x67, 0x59, 0xf1, 0x0e, 0x67, 0xcb,
0xdd, 0x66, 0xe6, 0x1c, 0x77, 0x58, 0xa5, 0xd2, 0x6c, 0x53, 0x78, 0x84, 0x36, 0x43, 0xca, 0xc5,
0x59, 0xca, 0x5c, 0x18, 0x04, 0x11, 0xe8, 0x7f, 0xfc, 0xf0, 0x61, 0x77, 0x20, 0x19, 0xce, 0x8e,
0x6e, 0x60, 0xb3, 0x5f, 0x17, 0x22, 0xb3, 0xba, 0x78, 0x82, 0xb0, 0x0c, 0x0c, 0x52, 0x1a, 0xf3,
0xe2, 0x97, 0x64, 0xb5, 0x95, 0x37, 0xae, 0xb6, 0xab, 0xab, 0xe1, 0xfe, 0x3d, 0x35, 0x32, 0xa7,
0x02, 0x7e, 0x1f, 0xad, 0xa6, 0x40, 0x39, 0x8b, 0x8d, 0x96, 0x1a, 0x57, 0x79, 0x3b, 0x44, 0x45,
0x89, 0xce, 0xe2, 0x0f, 0xd0, 0x5a, 0x04, 0x9c, 0xd3, 0x11, 0x18, 0xab, 0x0a, 0xf8, 0x48, 0x03,
0xd7, 0x4e, 0x8b, 0x30, 0x99, 0xe6, 0x7b, 0xbf, 0x35, 0xd0, 0xda, 0x09, 0x73, 0xfb, 0x01, 0x17,
0xf8, 0xbb, 0x7b, 0xf6, 0xb5, 0x1e, 0xf6, 0x33, 0x92, 0xad, 0xcc, 0xbb, 0xad, 0xeb, 0xb4, 0xa7,
0x91, 0x9a, 0x75, 0xbf, 0x44, 0xad, 0x40, 0x40, 0x24, 0xaf, 0xba, 0xb9, 0xb7, 0x7e, 0x60, 0x2c,
0x72, 0x9e, 0xb3, 0xa9, 0x45, 0x5a, 0xcf, 0x25, 0x9c, 0x14, 0xac, 0xde, 0x2f, 0x2d, 0xd5, 0xa8,
0xf4, 0x32, 0xde, 0x47, 0xeb, 0x09, 0x4d, 0x69, 0x18, 0x42, 0x18, 0xf0, 0x48, 0xf5, 0xda, 0x72,
0x1e, 0xe5, 0x99, 0xb9, 0x7e, 0x56, 0x85, 0x49, 0x1d, 0x23, 0x29, 0x1e, 0x8b, 0x92, 0x10, 0xe4,
0x30, 0x0b, 0xbb, 0x69, 0xca, 0x51, 0x15, 0x26, 0x75, 0x0c, 0x7e, 0x81, 0x76, 0xa8, 0x27, 0x82,
0x09, 0x3c, 0x03, 0xea, 0x87, 0x41, 0x0c, 0xe7, 0xe0, 0xb1, 0xd8, 0x2f, 0x9e, 0x4e, 0xd3, 0x79,
0x2b, 0xcf, 0xcc, 0x9d, 0xa7, 0xf3, 0x00, 0x64, 0x3e, 0x0f, 0x1f, 0xa2, 0x0d, 0x97, 0x7a, 0x97,
0x6c, 0x38, 0xec, 0x07, 0x51, 0x20, 0x8c, 0x35, 0xd5, 0xc4, 0x76, 0x9e, 0x99, 0x1b, 0x4e, 0x2d,
0x4e, 0x66, 0x50, 0xf8, 0x7b, 0xd4, 0xe6, 0x10, 0x82, 0x27, 0x58, 0xaa, 0x2d, 0xf6, 0xc9, 0x03,
0x6f, 0x85, 0xba, 0x10, 0x9e, 0x6b, 0xaa, 0xb3, 0x21, 0xaf, 0x65, 0x7a, 0x22, 0xa5, 0x24, 0xfe,
0x1c, 0x6d, 0x45, 0x34, 0x1e, 0xd3, 0x12, 0xa9, 0xbc, 0xd5, 0x76, 0x70, 0x9e, 0x99, 0x5b, 0xa7,
0x33, 0x19, 0x72, 0x07, 0x89, 0xbf, 0x46, 0x6d, 0x01, 0x51, 0x12, 0x52, 0x51, 0x18, 0x6d, 0xfd,
0xe0, 0xbd, 0xfa, 0xad, 0xca, 0xf7, 0x2a, 0x1b, 0x39, 0x63, 0xfe, 0x40, 0xc3, 0xd4, 0x62, 0x2a,
0x5d, 0x32, 0x8d, 0x92, 0x52, 0x06, 0xbf, 0x44, 0x8f, 0x85, 0x08, 0xf5, 0xc4, 0x9e, 0x0e, 0x05,
0xa4, 0xc7, 0x41, 0x1c, 0xf0, 0x0b, 0xf0, 0x8d, 0xb6, 0x1a, 0xd7, 0xdb, 0x79, 0x66, 0x3e, 0x1e,
0x0c, 0xfa, 0xf3, 0x20, 0x64, 0x11, 0x17, 0x9f, 0xa1, 0xad, 0xea, 0x6a, 0x4f, 0x99, 0x0f, 0x46,
0x47, 0x3d, 0x8c, 0x3d, 0xdd, 0xca, 0xd6, 0xd1, 0x4c, 0xf6, 0xf6, 0x5e, 0x84, 0xdc, 0xe1, 0xf7,
0xfe, 0x6a, 0xa2, 0x4e, 0xb9, 0x27, 0xf1, 0x4b, 0x84, 0xbc, 0xe9, 0x56, 0xe2, 0x46, 0x43, 0x39,
0xfc, 0xdd, 0x45, 0x0e, 0x2f, 0xf7, 0x57, 0xb5, 0xec, 0xcb, 0x10, 0x27, 0x35, 0x21, 0xfc, 0x0d,
0xea, 0x70, 0x41, 0x53, 0xa1, 0xf6, 0xcb, 0xf2, 0x1b, 0xef, 0x97, 0xcd, 0x3c, 0x33, 0x3b, 0xe7,
0x53, 0x01, 0x52, 0x69, 0xe1, 0x61, 0x7d, 0x1e, 0xff, 0x71, 0x57, 0xe2, 0xd9, 0xb9, 0xa9, 0x12,
0x77, 0x54, 0xe5, 0xc6, 0x2a, 0xde, 0x82, 0xb2, 0x6e, 0xab, 0xda, 0x58, 0xc5, 0xc3, 0x21, 0x3a,
0x8b, 0x6d, 0xd4, 0xe1, 0x63, 0xcf, 0x03, 0xf0, 0xc1, 0x57, 0x06, 0x6c, 0x39, 0xff, 0xd3, 0xd0,
0xce, 0xf9, 0x34, 0x41, 0x2a, 0x8c, 0x14, 0x1e, 0xd2, 0x20, 0x04, 0x5f, 0x19, 0xaf, 0x26, 0x7c,
0xac, 0xa2, 0x44, 0x67, 0xf1, 0x33, 0xb4, 0xad, 0x5b, 0x02, 0xff, 0x79, 0xec, 0xc3, 0x4f, 0xc0,
0xd5, 0xbb, 0xeb, 0x38, 0x86, 0x66, 0x6c, 0x1f, 0xdd, 0xc9, 0x93, 0x7b, 0x0c, 0x67, 0xef, 0xfa,
0xa6, 0xbb, 0xf4, 0xea, 0xa6, 0xbb, 0xf4, 0xfa, 0xa6, 0xbb, 0xf4, 0x73, 0xde, 0x6d, 0x5c, 0xe7,
0xdd, 0xc6, 0xab, 0xbc, 0xdb, 0x78, 0x9d, 0x77, 0x1b, 0x7f, 0xe4, 0xdd, 0xc6, 0xaf, 0x7f, 0x76,
0x97, 0xbe, 0x5d, 0x9e, 0xec, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x79, 0x3b, 0xba, 0x50,
0x09, 0x00, 0x00,
}
func (m *Job) Marshal() (dAtA []byte, err error) {
@ -443,6 +446,11 @@ func (m *JobSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
i -= len(m.CompletionMode)
copy(dAtA[i:], m.CompletionMode)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.CompletionMode)))
i--
dAtA[i] = 0x4a
if m.TTLSecondsAfterFinished != nil {
i = encodeVarintGenerated(dAtA, i, uint64(*m.TTLSecondsAfterFinished))
i--
@ -523,6 +531,11 @@ func (m *JobStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
i -= len(m.CompletedIndexes)
copy(dAtA[i:], m.CompletedIndexes)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.CompletedIndexes)))
i--
dAtA[i] = 0x3a
i = encodeVarintGenerated(dAtA, i, uint64(m.Failed))
i--
dAtA[i] = 0x30
@ -667,6 +680,8 @@ func (m *JobSpec) Size() (n int) {
if m.TTLSecondsAfterFinished != nil {
n += 1 + sovGenerated(uint64(*m.TTLSecondsAfterFinished))
}
l = len(m.CompletionMode)
n += 1 + l + sovGenerated(uint64(l))
return n
}
@ -693,6 +708,8 @@ func (m *JobStatus) Size() (n int) {
n += 1 + sovGenerated(uint64(m.Active))
n += 1 + sovGenerated(uint64(m.Succeeded))
n += 1 + sovGenerated(uint64(m.Failed))
l = len(m.CompletedIndexes)
n += 1 + l + sovGenerated(uint64(l))
return n
}
@ -758,6 +775,7 @@ func (this *JobSpec) String() string {
`Template:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Template), "PodTemplateSpec", "v11.PodTemplateSpec", 1), `&`, ``, 1) + `,`,
`BackoffLimit:` + valueToStringGenerated(this.BackoffLimit) + `,`,
`TTLSecondsAfterFinished:` + valueToStringGenerated(this.TTLSecondsAfterFinished) + `,`,
`CompletionMode:` + fmt.Sprintf("%v", this.CompletionMode) + `,`,
`}`,
}, "")
return s
@ -778,6 +796,7 @@ func (this *JobStatus) String() string {
`Active:` + fmt.Sprintf("%v", this.Active) + `,`,
`Succeeded:` + fmt.Sprintf("%v", this.Succeeded) + `,`,
`Failed:` + fmt.Sprintf("%v", this.Failed) + `,`,
`CompletedIndexes:` + fmt.Sprintf("%v", this.CompletedIndexes) + `,`,
`}`,
}, "")
return s
@ -1519,6 +1538,38 @@ func (m *JobSpec) Unmarshal(dAtA []byte) error {
}
}
m.TTLSecondsAfterFinished = &v
case 9:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CompletionMode", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.CompletionMode = CompletionMode(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@ -1732,6 +1783,38 @@ func (m *JobStatus) Unmarshal(dAtA []byte) error {
break
}
}
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CompletedIndexes", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.CompletedIndexes = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -146,6 +146,28 @@ message JobSpec {
// TTLAfterFinished feature.
// +optional
optional int32 ttlSecondsAfterFinished = 8;
// CompletionMode specifies how Pod completions are tracked. It can be
// `NonIndexed` (default) or `Indexed`.
//
// `NonIndexed` means that the Job is considered complete when there have
// been .spec.completions successfully completed Pods. Each Pod completion is
// homologous to each other.
//
// `Indexed` means that the Pods of a
// Job get an associated completion index from 0 to (.spec.completions - 1),
// available in the annotation batch.alpha.kubernetes.io/job-completion-index.
// The Job is considered complete when there is one successfully completed Pod
// for each index.
// When value is `Indexed`, .spec.completions must be specified and
// `.spec.parallelism` must be less than or equal to 10^5.
//
// This field is alpha-level and is only honored by servers that enable the
// IndexedJob feature gate. More completion modes can be added in the future.
// If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job.
// +optional
optional string completionMode = 9;
}
// JobStatus represents the current state of a Job.
@ -182,5 +204,15 @@ message JobStatus {
// The number of pods which reached phase Failed.
// +optional
optional int32 failed = 6;
// 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".
// +optional
optional string completedIndexes = 7;
}

View File

@ -21,6 +21,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const JobCompletionIndexAnnotationAlpha = "batch.alpha.kubernetes.io/job-completion-index"
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -57,6 +59,22 @@ type JobList struct {
Items []Job `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// CompletionMode specifies how Pod completions of a Job are tracked.
type CompletionMode string
const (
// NonIndexedCompletion is a Job completion mode. In this mode, the Job is
// considered complete when there have been .spec.completions
// successfully completed Pods. Pod completions are homologous to each other.
NonIndexedCompletion CompletionMode = "NonIndexed"
// IndexedCompletion is a Job completion mode. In this mode, the Pods of a
// Job get an associated completion index from 0 to (.spec.completions - 1).
// The Job is considered complete when a Pod completes for each completion
// index.
IndexedCompletion CompletionMode = "Indexed"
)
// JobSpec describes how the job execution will look like.
type JobSpec struct {
@ -126,6 +144,28 @@ type JobSpec struct {
// TTLAfterFinished feature.
// +optional
TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"varint,8,opt,name=ttlSecondsAfterFinished"`
// CompletionMode specifies how Pod completions are tracked. It can be
// `NonIndexed` (default) or `Indexed`.
//
// `NonIndexed` means that the Job is considered complete when there have
// been .spec.completions successfully completed Pods. Each Pod completion is
// homologous to each other.
//
// `Indexed` means that the Pods of a
// Job get an associated completion index from 0 to (.spec.completions - 1),
// available in the annotation batch.alpha.kubernetes.io/job-completion-index.
// The Job is considered complete when there is one successfully completed Pod
// for each index.
// When value is `Indexed`, .spec.completions must be specified and
// `.spec.parallelism` must be less than or equal to 10^5.
//
// This field is alpha-level and is only honored by servers that enable the
// IndexedJob feature gate. More completion modes can be added in the future.
// If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job.
// +optional
CompletionMode CompletionMode `json:"completionMode,omitempty" protobuf:"bytes,9,opt,name=completionMode,casttype=CompletionMode"`
}
// JobStatus represents the current state of a Job.
@ -162,6 +202,16 @@ type JobStatus struct {
// The number of pods which reached phase Failed.
// +optional
Failed int32 `json:"failed,omitempty" protobuf:"varint,6,opt,name=failed"`
// 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".
// +optional
CompletedIndexes string `json:"completedIndexes,omitempty" protobuf:"bytes,7,opt,name=completedIndexes"`
}
type JobConditionType string

View File

@ -72,6 +72,7 @@ var map_JobSpec = map[string]string{
"manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector",
"template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
"ttlSecondsAfterFinished": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.",
"completionMode": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.alpha.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5.\n\nThis field is alpha-level and is only honored by servers that enable the IndexedJob feature gate. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
}
func (JobSpec) SwaggerDoc() map[string]string {
@ -79,13 +80,14 @@ func (JobSpec) SwaggerDoc() map[string]string {
}
var map_JobStatus = map[string]string{
"": "JobStatus represents the current state of a Job.",
"conditions": "The latest available observations of an object's current state. When a job fails, one of the conditions will have type == \"Failed\". More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
"startTime": "Represents time when the job was acknowledged by the job controller. It is not guaranteed to be set in happens-before order across separate operations. 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.",
"succeeded": "The number of pods which reached phase Succeeded.",
"failed": "The number of pods which reached phase Failed.",
"": "JobStatus represents the current state of a Job.",
"conditions": "The latest available observations of an object's current state. When a job fails, one of the conditions will have type == \"Failed\". More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/",
"startTime": "Represents time when the job was acknowledged by the job controller. It is not guaranteed to be set in happens-before order across separate operations. 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.",
"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\".",
}
func (JobStatus) SwaggerDoc() map[string]string {

View File

@ -1474,21 +1474,23 @@
"setHostnameAsFQDN": false
}
},
"ttlSecondsAfterFinished": 1192652907
"ttlSecondsAfterFinished": 1192652907,
"completionMode": "莾簏ì淵歔ųd,"
},
"status": {
"conditions": [
{
"type": "",
"status": "簏ì淵歔ųd,4",
"lastProbeTime": "2813-03-11T20:08:42Z",
"lastTransitionTime": "2793-11-20T00:30:11Z",
"type": ";蛡媈U",
"status": "Oa2ƒƈɈ达iʍjʒu+,妧縖%Á",
"lastProbeTime": "2823-10-04T11:14:04Z",
"lastTransitionTime": "2882-02-07T11:38:45Z",
"reason": "464",
"message": "465"
}
],
"active": -1983720493,
"succeeded": -2026748262,
"failed": 1049326966
"active": -1993578228,
"succeeded": 1971731732,
"failed": 165851549,
"completedIndexes": "466"
}
}

View File

@ -32,6 +32,7 @@ metadata:
spec:
activeDeadlineSeconds: -5584804243908071872
backoffLimit: -783752440
completionMode: 莾簏ì淵歔ųd,
completions: 1305381319
manualSelector: true
parallelism: 896585016
@ -1009,13 +1010,14 @@ spec:
volumePath: "101"
ttlSecondsAfterFinished: 1192652907
status:
active: -1983720493
active: -1993578228
completedIndexes: "466"
conditions:
- lastProbeTime: "2813-03-11T20:08:42Z"
lastTransitionTime: "2793-11-20T00:30:11Z"
- lastProbeTime: "2823-10-04T11:14:04Z"
lastTransitionTime: "2882-02-07T11:38:45Z"
message: "465"
reason: "464"
status: 簏ì淵歔ųd,4
type: ""
failed: 1049326966
succeeded: -2026748262
status: Oa2ƒƈɈ达iʍjʒu+,妧縖%Á
type: ;蛡媈U
failed: 165851549
succeeded: 1971731732

View File

@ -1522,11 +1522,12 @@
"setHostnameAsFQDN": false
}
},
"ttlSecondsAfterFinished": -339602975
"ttlSecondsAfterFinished": -339602975,
"completionMode": "泐ɻvŰ`Ǧɝ憑ǖ菐u鸚Y髬.ʂmD"
}
},
"successfulJobsHistoryLimit": 305459364,
"failedJobsHistoryLimit": -1565042829
"successfulJobsHistoryLimit": 1380163777,
"failedJobsHistoryLimit": -406189540
},
"status": {
"active": [
@ -1534,7 +1535,7 @@
"kind": "479",
"namespace": "480",
"name": "481",
"uid": "vÐ仆dždĄ跞肞=ɴ",
"uid": "ɅĀ埰ʀ",
"apiVersion": "482",
"resourceVersion": "483",
"fieldPath": "484"

View File

@ -31,7 +31,7 @@ metadata:
uid: "7"
spec:
concurrencyPolicy: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ
failedJobsHistoryLimit: -1565042829
failedJobsHistoryLimit: -406189540
jobTemplate:
metadata:
annotations:
@ -65,6 +65,7 @@ spec:
spec:
activeDeadlineSeconds: -1483125035702892746
backoffLimit: -1822122846
completionMode: 泐ɻvŰ`Ǧɝ憑ǖ菐u鸚Y髬.ʂmD
completions: -106888179
manualSelector: true
parallelism: -856030588
@ -1046,7 +1047,7 @@ spec:
ttlSecondsAfterFinished: -339602975
schedule: "19"
startingDeadlineSeconds: -2555947251840004808
successfulJobsHistoryLimit: 305459364
successfulJobsHistoryLimit: 1380163777
suspend: true
status:
active:
@ -1056,4 +1057,4 @@ status:
name: "481"
namespace: "480"
resourceVersion: "483"
uid: vÐ仆dždĄ跞肞=ɴ
uid: ɅĀ埰ʀ

View File

@ -1518,7 +1518,8 @@
"setHostnameAsFQDN": true
}
},
"ttlSecondsAfterFinished": -1766935785
"ttlSecondsAfterFinished": -1766935785,
"completionMode": "tS誖Śs垦"
}
}
}

View File

@ -62,6 +62,7 @@ template:
spec:
activeDeadlineSeconds: -9086179100394185427
backoffLimit: -1796008812
completionMode: tS誖Śs垦
completions: -1771909905
manualSelector: false
parallelism: -443114323

View File

@ -2160,6 +2160,9 @@ func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) {
} else {
w.Write(LEVEL_0, "Completions:\t<unset>\n")
}
if job.Spec.CompletionMode != "" {
w.Write(LEVEL_0, "Completion Mode:\t%s\n", job.Spec.CompletionMode)
}
if job.Status.StartTime != nil {
w.Write(LEVEL_0, "Start Time:\t%s\n", job.Status.StartTime.Time.Format(time.RFC1123Z))
}
@ -2173,6 +2176,9 @@ func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) {
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.Spec.CompletionMode == batchv1.IndexedCompletion {
w.Write(LEVEL_0, "Completed Indexes:\t%s\n", capIndexesListOrNone(job.Status.CompletedIndexes, 50))
}
DescribePodTemplate(&job.Spec.Template, w)
if events != nil {
DescribeEvents(events, w)
@ -2181,6 +2187,22 @@ func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) {
})
}
func capIndexesListOrNone(indexes string, softLimit int) string {
if len(indexes) == 0 {
return "<none>"
}
ix := softLimit
for ; ix < len(indexes); ix++ {
if indexes[ix] == ',' {
break
}
}
if ix >= len(indexes) {
return indexes
}
return indexes[:ix+1] + "..."
}
// CronJobDescriber generates information about a cron job and the jobs it has created.
type CronJobDescriber struct {
client clientset.Interface

View File

@ -2062,6 +2062,88 @@ func TestDescribeDeployment(t *testing.T) {
}
}
func TestDescribeJob(t *testing.T) {
cases := map[string]struct {
job *batchv1.Job
wantCompletedIndexes string
}{
"not indexed": {
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: batchv1.JobSpec{
CompletionMode: batchv1.NonIndexedCompletion,
},
},
},
"no indexes": {
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion,
},
},
wantCompletedIndexes: "<none>",
},
"few completed indexes": {
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion,
},
Status: batchv1.JobStatus{
CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32",
},
},
wantCompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32",
},
"too many completed indexes": {
job: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "foo",
},
Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion,
},
Status: batchv1.JobStatus{
CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32-34,36,37",
},
},
wantCompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32-34,...",
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
client := &describeClient{
T: t,
Namespace: tc.job.Namespace,
Interface: fake.NewSimpleClientset(tc.job),
}
describer := JobDescriber{Interface: client}
out, err := describer.Describe(tc.job.Namespace, tc.job.Name, DescriberSettings{ShowEvents: true})
if err != nil {
t.Fatalf("Unexpected error describing object: %v", err)
}
if tc.wantCompletedIndexes != "" {
if !strings.Contains(out, fmt.Sprintf("Completed Indexes: %s\n", tc.wantCompletedIndexes)) {
t.Errorf("Output didn't contain wanted Completed Indexes:\n%s", out)
}
} else if strings.Contains(out, fmt.Sprintf("Completed Indexes:")) {
t.Errorf("Output contains unexpected completed indexes:\n%s", out)
}
})
}
}
func TestDescribeIngress(t *testing.T) {
backendV1beta1 := networkingv1beta1.IngressBackend{
ServiceName: "default-backend",