Only default Job fields when feature gates are enabled

Also use pointer for completionMode enum
This commit is contained in:
Aldo Culquicondor 2021-03-12 17:26:40 +00:00
parent fcee7a0105
commit e6c3d7b34d
38 changed files with 304 additions and 250 deletions

View File

@ -47,11 +47,11 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
} else { } else {
j.ManualSelector = nil j.ManualSelector = nil
} }
if c.Rand.Int31()%2 == 0 { mode := batch.NonIndexedCompletion
j.CompletionMode = batch.NonIndexedCompletion if c.RandBool() {
} else { mode = batch.IndexedCompletion
j.CompletionMode = batch.IndexedCompletion
} }
j.CompletionMode = &mode
// We're fuzzing the internal JobSpec type, not the v1 type, so we don't // We're fuzzing the internal JobSpec type, not the v1 type, so we don't
// need to fuzz the nil value. // need to fuzz the nil value.
j.Suspend = pointer.BoolPtr(c.RandBool()) j.Suspend = pointer.BoolPtr(c.RandBool())

View File

@ -189,7 +189,7 @@ type JobSpec struct {
// If the Job controller observes a mode that it doesn't recognize, the // If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job. // controller skips updates for the Job.
// +optional // +optional
CompletionMode CompletionMode CompletionMode *CompletionMode
// Suspend specifies whether the Job controller should create Pods or not. If // Suspend specifies whether the Job controller should create Pods or not. If
// a Job is created with suspend set to true, no Pods are created by the Job // a Job is created with suspend set to true, no Pods are created by the Job

View File

@ -19,6 +19,8 @@ package v1
import ( import (
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
utilpointer "k8s.io/utils/pointer" utilpointer "k8s.io/utils/pointer"
) )
@ -43,10 +45,11 @@ func SetDefaults_Job(obj *batchv1.Job) {
if labels != nil && len(obj.Labels) == 0 { if labels != nil && len(obj.Labels) == 0 {
obj.Labels = labels obj.Labels = labels
} }
if len(obj.Spec.CompletionMode) == 0 { if utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && obj.Spec.CompletionMode == nil {
obj.Spec.CompletionMode = batchv1.NonIndexedCompletion mode := batchv1.NonIndexedCompletion
obj.Spec.CompletionMode = &mode
} }
if obj.Spec.Suspend == nil { if utilfeature.DefaultFeatureGate.Enabled(features.SuspendJob) && obj.Spec.Suspend == nil {
obj.Spec.Suspend = utilpointer.BoolPtr(false) obj.Spec.Suspend = utilpointer.BoolPtr(false)
} }
} }

View File

@ -25,9 +25,12 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/features"
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
. "k8s.io/kubernetes/pkg/apis/batch/v1" . "k8s.io/kubernetes/pkg/apis/batch/v1"
@ -36,6 +39,8 @@ import (
func TestSetDefaultJob(t *testing.T) { func TestSetDefaultJob(t *testing.T) {
defaultLabels := map[string]string{"default": "default"} defaultLabels := map[string]string{"default": "default"}
tests := map[string]struct { tests := map[string]struct {
indexedJobEnabled bool
suspendJobEnabled bool
original *batchv1.Job original *batchv1.Job
expected *batchv1.Job expected *batchv1.Job
expectLabels bool expectLabels bool
@ -53,13 +58,50 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(1), Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1), Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion, },
},
expectLabels: true,
},
"All unspecified, indexed job enabled -> sets all to default values": {
indexedJobEnabled: true,
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
},
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
},
},
expectLabels: true,
},
"All unspecified, suspend job enabled -> sets all to default values": {
suspendJobEnabled: true,
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
},
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
Suspend: pointer.BoolPtr(false), Suspend: pointer.BoolPtr(false),
}, },
}, },
expectLabels: true, expectLabels: true,
}, },
"suspend set, everything else is defaulted": { "suspend set, everything else is defaulted": {
suspendJobEnabled: true,
original: &batchv1.Job{ original: &batchv1.Job{
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(true),
@ -73,13 +115,12 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(1), Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1), Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(true),
}, },
}, },
expectLabels: true, expectLabels: true,
}, },
"All unspecified -> all pointers, CompletionMode are defaulted and no default labels": { "All unspecified -> all pointers are defaulted and no default labels": {
original: &batchv1.Job{ original: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"mylabel": "myvalue"}, Labels: map[string]string{"mylabel": "myvalue"},
@ -95,8 +136,6 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(1), Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1), Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
}, },
}, },
}, },
@ -113,8 +152,6 @@ func TestSetDefaultJob(t *testing.T) {
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Parallelism: pointer.Int32Ptr(0), Parallelism: pointer.Int32Ptr(0),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
}, },
}, },
expectLabels: true, expectLabels: true,
@ -132,8 +169,6 @@ func TestSetDefaultJob(t *testing.T) {
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Parallelism: pointer.Int32Ptr(2), Parallelism: pointer.Int32Ptr(2),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
}, },
}, },
expectLabels: true, expectLabels: true,
@ -152,8 +187,6 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(2), Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(1), Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6), BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
}, },
}, },
expectLabels: true, expectLabels: true,
@ -172,8 +205,6 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(1), Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1), Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(5), BackoffLimit: pointer.Int32Ptr(5),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
}, },
}, },
expectLabels: true, expectLabels: true,
@ -184,8 +215,8 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(8), Completions: pointer.Int32Ptr(8),
Parallelism: pointer.Int32Ptr(9), Parallelism: pointer.Int32Ptr(9),
BackoffLimit: pointer.Int32Ptr(10), BackoffLimit: pointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion, CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(false),
Template: v1.PodTemplateSpec{ Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels}, ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
}, },
@ -196,8 +227,8 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(8), Completions: pointer.Int32Ptr(8),
Parallelism: pointer.Int32Ptr(9), Parallelism: pointer.Int32Ptr(9),
BackoffLimit: pointer.Int32Ptr(10), BackoffLimit: pointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion, CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(false),
Template: v1.PodTemplateSpec{ Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels}, ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
}, },
@ -211,7 +242,7 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(11), Completions: pointer.Int32Ptr(11),
Parallelism: pointer.Int32Ptr(10), Parallelism: pointer.Int32Ptr(10),
BackoffLimit: pointer.Int32Ptr(9), BackoffLimit: pointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion, CompletionMode: completionModePtr(batchv1.IndexedCompletion),
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(true),
Template: v1.PodTemplateSpec{ Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels}, ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
@ -223,7 +254,7 @@ func TestSetDefaultJob(t *testing.T) {
Completions: pointer.Int32Ptr(11), Completions: pointer.Int32Ptr(11),
Parallelism: pointer.Int32Ptr(10), Parallelism: pointer.Int32Ptr(10),
BackoffLimit: pointer.Int32Ptr(9), BackoffLimit: pointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion, CompletionMode: completionModePtr(batchv1.IndexedCompletion),
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(true),
}, },
}, },
@ -233,6 +264,8 @@ func TestSetDefaultJob(t *testing.T) {
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, test.indexedJobEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.SuspendJob, test.suspendJobEnabled)()
original := test.original original := test.original
expected := test.expected expected := test.expected
@ -256,8 +289,8 @@ func TestSetDefaultJob(t *testing.T) {
t.Errorf("Unexpected equality: %v", actual.Labels) t.Errorf("Unexpected equality: %v", actual.Labels)
} }
} }
if actual.Spec.CompletionMode != expected.Spec.CompletionMode { if diff := cmp.Diff(expected.Spec.CompletionMode, actual.Spec.CompletionMode); diff != "" {
t.Errorf("Got CompletionMode: %v, want: %v", actual.Spec.CompletionMode, expected.Spec.CompletionMode) t.Errorf("Unexpected CompletionMode (-want,+got):\n%s", diff)
} }
}) })
} }
@ -304,7 +337,7 @@ func TestSetDefaultCronJob(t *testing.T) {
expected: &batchv1.CronJob{ expected: &batchv1.CronJob{
Spec: batchv1.CronJobSpec{ Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.AllowConcurrent, ConcurrencyPolicy: batchv1.AllowConcurrent,
Suspend: newBool(false), Suspend: pointer.BoolPtr(false),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(3), SuccessfulJobsHistoryLimit: pointer.Int32Ptr(3),
FailedJobsHistoryLimit: pointer.Int32Ptr(1), FailedJobsHistoryLimit: pointer.Int32Ptr(1),
}, },
@ -314,7 +347,7 @@ func TestSetDefaultCronJob(t *testing.T) {
original: &batchv1.CronJob{ original: &batchv1.CronJob{
Spec: batchv1.CronJobSpec{ Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.ForbidConcurrent, ConcurrencyPolicy: batchv1.ForbidConcurrent,
Suspend: newBool(true), Suspend: pointer.BoolPtr(true),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5), SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5),
FailedJobsHistoryLimit: pointer.Int32Ptr(5), FailedJobsHistoryLimit: pointer.Int32Ptr(5),
}, },
@ -322,7 +355,7 @@ func TestSetDefaultCronJob(t *testing.T) {
expected: &batchv1.CronJob{ expected: &batchv1.CronJob{
Spec: batchv1.CronJobSpec{ Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.ForbidConcurrent, ConcurrencyPolicy: batchv1.ForbidConcurrent,
Suspend: newBool(true), Suspend: pointer.BoolPtr(true),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5), SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5),
FailedJobsHistoryLimit: pointer.Int32Ptr(5), FailedJobsHistoryLimit: pointer.Int32Ptr(5),
}, },
@ -354,8 +387,6 @@ func TestSetDefaultCronJob(t *testing.T) {
} }
} }
func newBool(val bool) *bool { func completionModePtr(m batchv1.CompletionMode) *batchv1.CompletionMode {
p := new(bool) return &m
*p = val
return p
} }

View File

@ -392,7 +392,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec,
return err return err
} }
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished)) out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = batch.CompletionMode(in.CompletionMode) out.CompletionMode = (*batch.CompletionMode)(unsafe.Pointer(in.CompletionMode))
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
return nil return nil
} }
@ -408,7 +408,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec,
return err return err
} }
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished)) out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = v1.CompletionMode(in.CompletionMode) out.CompletionMode = (*v1.CompletionMode)(unsafe.Pointer(in.CompletionMode))
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
return nil return nil
} }

View File

@ -129,12 +129,12 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidatio
if spec.TTLSecondsAfterFinished != nil { if spec.TTLSecondsAfterFinished != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...)
} }
// CompletionMode might be empty when IndexedJob feature gate is disabled. // CompletionMode might be nil when IndexedJob feature gate is disabled.
if spec.CompletionMode != "" { if spec.CompletionMode != nil {
if spec.CompletionMode != batch.NonIndexedCompletion && spec.CompletionMode != batch.IndexedCompletion { 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)})) allErrs = append(allErrs, field.NotSupported(fldPath.Child("completionMode"), spec.CompletionMode, []string{string(batch.NonIndexedCompletion), string(batch.IndexedCompletion)}))
} }
if spec.CompletionMode == batch.IndexedCompletion { if *spec.CompletionMode == batch.IndexedCompletion {
if spec.Completions == nil { if spec.Completions == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("completions"), fmt.Sprintf("when completion mode is %s", batch.IndexedCompletion))) allErrs = append(allErrs, field.Required(fldPath.Child("completions"), fmt.Sprintf("when completion mode is %s", batch.IndexedCompletion)))
} }

View File

@ -86,7 +86,7 @@ func TestValidateJob(t *testing.T) {
}, },
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validManualSelector, Selector: validManualSelector,
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: validPodTemplateSpecForManual, Template: validPodTemplateSpecForManual,
}, },
}, },
@ -110,7 +110,7 @@ func TestValidateJob(t *testing.T) {
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validGeneratedSelector, Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated, Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.NonIndexedCompletion, CompletionMode: completionModePtr(batch.NonIndexedCompletion),
}, },
}, },
"valid Indexed completion mode": { "valid Indexed completion mode": {
@ -122,7 +122,7 @@ func TestValidateJob(t *testing.T) {
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validGeneratedSelector, Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated, Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion, CompletionMode: completionModePtr(batch.IndexedCompletion),
Completions: pointer.Int32Ptr(2), Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(100000), Parallelism: pointer.Int32Ptr(100000),
}, },
@ -192,7 +192,7 @@ func TestValidateJob(t *testing.T) {
}, },
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validManualSelector, Selector: validManualSelector,
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"y": "z"}, Labels: map[string]string{"y": "z"},
@ -213,7 +213,7 @@ func TestValidateJob(t *testing.T) {
}, },
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validManualSelector, Selector: validManualSelector,
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"controller-uid": "4d5e6f"}, Labels: map[string]string{"controller-uid": "4d5e6f"},
@ -234,7 +234,7 @@ func TestValidateJob(t *testing.T) {
}, },
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validManualSelector, Selector: validManualSelector,
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: validManualSelector.MatchLabels, Labels: validManualSelector.MatchLabels,
@ -255,7 +255,7 @@ func TestValidateJob(t *testing.T) {
}, },
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validManualSelector, Selector: validManualSelector,
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: validManualSelector.MatchLabels, Labels: validManualSelector.MatchLabels,
@ -289,7 +289,7 @@ func TestValidateJob(t *testing.T) {
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validGeneratedSelector, Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated, Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion, CompletionMode: completionModePtr(batch.IndexedCompletion),
}, },
}, },
"spec.parallelism: must be less than or equal to 100000 when completion mode is Indexed": { "spec.parallelism: must be less than or equal to 100000 when completion mode is Indexed": {
@ -301,7 +301,7 @@ func TestValidateJob(t *testing.T) {
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validGeneratedSelector, Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated, Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion, CompletionMode: completionModePtr(batch.IndexedCompletion),
Completions: pointer.Int32Ptr(2), Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(100001), Parallelism: pointer.Int32Ptr(100001),
}, },
@ -404,12 +404,12 @@ func TestValidateJobUpdate(t *testing.T) {
Spec: batch.JobSpec{ Spec: batch.JobSpec{
Selector: validGeneratedSelector, Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated, Template: validPodTemplateSpecForGenerated,
CompletionMode: batch.IndexedCompletion, CompletionMode: completionModePtr(batch.IndexedCompletion),
Completions: pointer.Int32Ptr(2), Completions: pointer.Int32Ptr(2),
}, },
}, },
update: func(job *batch.Job) { update: func(job *batch.Job) {
job.Spec.CompletionMode = batch.NonIndexedCompletion job.Spec.CompletionMode = completionModePtr(batch.NonIndexedCompletion)
}, },
err: &field.Error{ err: &field.Error{
Type: field.ErrorTypeInvalid, Type: field.ErrorTypeInvalid,
@ -762,7 +762,7 @@ func TestValidateCronJob(t *testing.T) {
ConcurrencyPolicy: batch.AllowConcurrent, ConcurrencyPolicy: batch.AllowConcurrent,
JobTemplate: batch.JobTemplateSpec{ JobTemplate: batch.JobTemplateSpec{
Spec: batch.JobSpec{ Spec: batch.JobSpec{
ManualSelector: newBool(true), ManualSelector: pointer.BoolPtr(true),
Template: validPodTemplateSpec, Template: validPodTemplateSpec,
}, },
}, },
@ -865,8 +865,6 @@ func TestValidateCronJob(t *testing.T) {
} }
} }
func newBool(val bool) *bool { func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
p := new(bool) return &m
*p = val
return p
} }

View File

@ -271,6 +271,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
*out = new(int32) *out = new(int32)
**out = **in **out = **in
} }
if in.CompletionMode != nil {
in, out := &in.CompletionMode, &out.CompletionMode
*out = new(CompletionMode)
**out = **in
}
if in.Suspend != nil { if in.Suspend != nil {
in, out := &in.Suspend, &out.Suspend in, out := &in.Suspend, &out.Suspend
*out = new(bool) *out = new(bool)

View File

@ -34,6 +34,10 @@ const (
unknownCompletionIndex = -1 unknownCompletionIndex = -1
) )
func isIndexedJob(job *batch.Job) bool {
return job.Spec.CompletionMode != nil && *job.Spec.CompletionMode == batch.IndexedCompletion
}
// calculateSucceededIndexes returns a string representation of the list of // calculateSucceededIndexes returns a string representation of the list of
// succeeded indexes in compressed format and the number of succeeded indexes. // succeeded indexes in compressed format and the number of succeeded indexes.
func calculateSucceededIndexes(pods []*v1.Pod) (string, int32) { func calculateSucceededIndexes(pods []*v1.Pod) (string, int32) {

View File

@ -470,10 +470,14 @@ func (jm *Controller) syncJob(key string) (bool, error) {
} }
// Cannot create Pods if this is an Indexed Job and the feature is disabled. // Cannot create Pods if this is an Indexed Job and the feature is disabled.
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && job.Spec.CompletionMode == batch.IndexedCompletion { if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && isIndexedJob(&job) {
jm.recorder.Event(&job, v1.EventTypeWarning, "IndexedJobDisabled", "Skipped Indexed Job sync because feature is disabled.") jm.recorder.Event(&job, v1.EventTypeWarning, "IndexedJobDisabled", "Skipped Indexed Job sync because feature is disabled.")
return false, nil return false, nil
} }
if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode != batch.NonIndexedCompletion && *job.Spec.CompletionMode != batch.IndexedCompletion {
jm.recorder.Event(&job, v1.EventTypeWarning, "UnknownCompletionMode", "Skipped Job sync because completion mode is unknown")
return false, nil
}
// Check the expectations of the job before counting active pods, otherwise a new pod can sneak in // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in
// and update the expectations after we've retrieved active pods from the store. If a new pod enters // and update the expectations after we've retrieved active pods from the store. If a new pod enters
@ -525,7 +529,7 @@ func (jm *Controller) syncJob(key string) (bool, error) {
} }
var succeededIndexes string var succeededIndexes string
if job.Spec.CompletionMode == batch.IndexedCompletion { if isIndexedJob(&job) {
succeededIndexes, succeeded = calculateSucceededIndexes(pods) succeededIndexes, succeeded = calculateSucceededIndexes(pods)
} }
jobConditionsChanged := false jobConditionsChanged := false
@ -625,7 +629,7 @@ func (jm *Controller) syncJob(key string) (bool, error) {
job.Status.Active = active job.Status.Active = active
job.Status.Succeeded = succeeded job.Status.Succeeded = succeeded
job.Status.Failed = failed job.Status.Failed = failed
if job.Spec.CompletionMode == batch.IndexedCompletion { if isIndexedJob(&job) {
job.Status.CompletedIndexes = succeededIndexes job.Status.CompletedIndexes = succeededIndexes
} }
@ -807,14 +811,14 @@ func (jm *Controller) manageJob(job *batch.Job, activePods []*v1.Pod, succeeded
wait := sync.WaitGroup{} wait := sync.WaitGroup{}
var indexesToAdd []int var indexesToAdd []int
if job.Spec.Completions != nil && job.Spec.CompletionMode == batch.IndexedCompletion { if job.Spec.Completions != nil && isIndexedJob(job) {
indexesToAdd = firstPendingIndexes(allPods, int(diff), int(*job.Spec.Completions)) indexesToAdd = firstPendingIndexes(allPods, int(diff), int(*job.Spec.Completions))
diff = int32(len(indexesToAdd)) diff = int32(len(indexesToAdd))
} }
active += diff active += diff
podTemplate := job.Spec.Template.DeepCopy() podTemplate := job.Spec.Template.DeepCopy()
if job.Spec.CompletionMode == batch.IndexedCompletion { if isIndexedJob(job) {
addCompletionIndexEnvVariables(podTemplate) addCompletionIndexEnvVariables(podTemplate)
} }
@ -893,7 +897,7 @@ func (jm *Controller) manageJob(job *batch.Job, activePods []*v1.Pod, succeeded
func activePodsForRemoval(job *batch.Job, pods []*v1.Pod, rmAtLeast int) []*v1.Pod { func activePodsForRemoval(job *batch.Job, pods []*v1.Pod, rmAtLeast int) []*v1.Pod {
var rm, left []*v1.Pod var rm, left []*v1.Pod
if job.Spec.CompletionMode == batch.IndexedCompletion { if isIndexedJob(job) {
rm = make([]*v1.Pod, 0, rmAtLeast) rm = make([]*v1.Pod, 0, rmAtLeast)
left = make([]*v1.Pod, 0, len(pods)-rmAtLeast) left = make([]*v1.Pod, 0, len(pods)-rmAtLeast)
rm, left = appendDuplicatedIndexPodsForRemoval(rm, left, pods) rm, left = appendDuplicatedIndexPodsForRemoval(rm, left, pods)
@ -950,7 +954,7 @@ func getBackoff(queue workqueue.RateLimitingInterface, key interface{}) time.Dur
func countPodsByPhase(job *batch.Job, pods []*v1.Pod, phase v1.PodPhase) int { func countPodsByPhase(job *batch.Job, pods []*v1.Pod, phase v1.PodPhase) int {
result := 0 result := 0
for _, p := range pods { for _, p := range pods {
if phase == p.Status.Phase && (job.Spec.CompletionMode != batch.IndexedCompletion || getCompletionIndex(p.Annotations) != unknownCompletionIndex) { if phase == p.Status.Phase && (!isIndexedJob(job) || getCompletionIndex(p.Annotations) != unknownCompletionIndex) {
result++ result++
} }
} }

View File

@ -64,7 +64,6 @@ func newJob(parallelism, completions, backoffLimit int32, completionMode batch.C
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"}, MatchLabels: map[string]string{"foo": "bar"},
}, },
CompletionMode: completionMode,
Template: v1.PodTemplateSpec{ Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{ Labels: map[string]string{
@ -79,6 +78,9 @@ func newJob(parallelism, completions, backoffLimit int32, completionMode batch.C
}, },
}, },
} }
if completionMode != "" {
j.Spec.CompletionMode = &completionMode
}
// Special case: -1 for either completions or parallelism means leave nil (negative is not allowed // Special case: -1 for either completions or parallelism means leave nil (negative is not allowed
// in practice by validation. // in practice by validation.
if completions >= 0 { if completions >= 0 {

View File

@ -39,7 +39,6 @@ import (
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/batch/validation" "k8s.io/kubernetes/pkg/apis/batch/validation"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/utils/pointer"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
) )
@ -95,11 +94,11 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) { if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) {
job.Spec.CompletionMode = batch.NonIndexedCompletion job.Spec.CompletionMode = nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.SuspendJob) { if !utilfeature.DefaultFeatureGate.Enabled(features.SuspendJob) {
job.Spec.Suspend = pointer.BoolPtr(false) job.Spec.Suspend = nil
} }
pod.DropDisabledTemplateFields(&job.Spec.Template, nil) pod.DropDisabledTemplateFields(&job.Spec.Template, nil)
@ -115,8 +114,8 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
newJob.Spec.TTLSecondsAfterFinished = nil newJob.Spec.TTLSecondsAfterFinished = nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && oldJob.Spec.CompletionMode == batch.NonIndexedCompletion { if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && oldJob.Spec.CompletionMode == nil {
newJob.Spec.CompletionMode = batch.NonIndexedCompletion newJob.Spec.CompletionMode = nil
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.SuspendJob) { if !utilfeature.DefaultFeatureGate.Enabled(features.SuspendJob) {

View File

@ -99,7 +99,7 @@ func testJobStrategy(t *testing.T) {
// Set gated values. // Set gated values.
Suspend: pointer.BoolPtr(true), Suspend: pointer.BoolPtr(true),
TTLSecondsAfterFinished: pointer.Int32Ptr(0), TTLSecondsAfterFinished: pointer.Int32Ptr(0),
CompletionMode: batch.IndexedCompletion, CompletionMode: completionModePtr(batch.IndexedCompletion),
}, },
Status: batch.JobStatus{ Status: batch.JobStatus{
Active: 11, Active: 11,
@ -117,11 +117,11 @@ func testJobStrategy(t *testing.T) {
if ttlEnabled != (job.Spec.TTLSecondsAfterFinished != nil) { if ttlEnabled != (job.Spec.TTLSecondsAfterFinished != nil) {
t.Errorf("Job should allow setting .spec.ttlSecondsAfterFinished only 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) { if indexedJobEnabled != (job.Spec.CompletionMode != nil) {
t.Errorf("Job should allow setting .spec.completionMode=Indexed only when %v feature is enabled", features.IndexedJob) t.Errorf("Job should allow setting .spec.completionMode only when %v feature is enabled", features.IndexedJob)
} }
if !suspendJobEnabled && *job.Spec.Suspend { if !suspendJobEnabled && (job.Spec.Suspend != nil) {
t.Errorf("[SuspendJob=%v] .spec.suspend should be set to true", suspendJobEnabled) t.Errorf("Job should allow setting .spec.suspend only when %v feature is enabled", features.SuspendJob)
} }
parallelism := int32(10) parallelism := int32(10)
@ -132,7 +132,7 @@ func testJobStrategy(t *testing.T) {
Completions: pointer.Int32Ptr(2), Completions: pointer.Int32Ptr(2),
// Update gated features. // Update gated features.
TTLSecondsAfterFinished: pointer.Int32Ptr(1), TTLSecondsAfterFinished: pointer.Int32Ptr(1),
CompletionMode: batch.IndexedCompletion, // No change because field is immutable. CompletionMode: completionModePtr(batch.IndexedCompletion), // No change because field is immutable.
}, },
Status: batch.JobStatus{ Status: batch.JobStatus{
Active: 11, Active: 11,
@ -153,21 +153,10 @@ func testJobStrategy(t *testing.T) {
t.Errorf("Expected a validation error") t.Errorf("Expected a validation error")
} }
// Existing gated fields should be preserved
job.Spec.TTLSecondsAfterFinished = pointer.Int32Ptr(1)
job.Spec.CompletionMode = batch.IndexedCompletion
updatedJob.Spec.TTLSecondsAfterFinished = pointer.Int32Ptr(2)
updatedJob.Spec.CompletionMode = batch.IndexedCompletion
// Test updating suspend false->true and nil-> true when the feature gate is // Test updating suspend false->true and nil-> true when the feature gate is
// disabled. We don't care about other combinations. // disabled. We don't care about other combinations.
job.Spec.Suspend, updatedJob.Spec.Suspend = pointer.BoolPtr(false), pointer.BoolPtr(true) job.Spec.Suspend, updatedJob.Spec.Suspend = pointer.BoolPtr(false), pointer.BoolPtr(true)
Strategy.PrepareForUpdate(ctx, updatedJob, job) Strategy.PrepareForUpdate(ctx, updatedJob, job)
if job.Spec.TTLSecondsAfterFinished == nil || updatedJob.Spec.TTLSecondsAfterFinished == nil {
t.Errorf("existing .spec.ttlSecondsAfterFinished should be preserved")
}
if job.Spec.CompletionMode == "" || updatedJob.Spec.CompletionMode == "" {
t.Errorf("existing completionMode should be preserved")
}
if !suspendJobEnabled && *updatedJob.Spec.Suspend { if !suspendJobEnabled && *updatedJob.Spec.Suspend {
t.Errorf("[SuspendJob=%v] .spec.suspend should not be updated from false to true", suspendJobEnabled) t.Errorf("[SuspendJob=%v] .spec.suspend should not be updated from false to true", suspendJobEnabled)
} }
@ -324,3 +313,7 @@ func TestSelectableFieldLabelConversions(t *testing.T) {
nil, nil,
) )
} }
func completionModePtr(m batch.CompletionMode) *batch.CompletionMode {
return &m
}

View File

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

View File

@ -168,7 +168,7 @@ type JobSpec struct {
// If the Job controller observes a mode that it doesn't recognize, the // If the Job controller observes a mode that it doesn't recognize, the
// controller skips updates for the Job. // controller skips updates for the Job.
// +optional // +optional
CompletionMode CompletionMode `json:"completionMode,omitempty" protobuf:"bytes,9,opt,name=completionMode,casttype=CompletionMode"` CompletionMode *CompletionMode `json:"completionMode,omitempty" protobuf:"bytes,9,opt,name=completionMode,casttype=CompletionMode"`
// Suspend specifies whether the Job controller should create Pods or not. If // Suspend specifies whether the Job controller should create Pods or not. If
// a Job is created with suspend set to true, no Pods are created by the Job // a Job is created with suspend set to true, no Pods are created by the Job

View File

@ -271,6 +271,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
*out = new(int32) *out = new(int32)
**out = **in **out = **in
} }
if in.CompletionMode != nil {
in, out := &in.CompletionMode, &out.CompletionMode
*out = new(CompletionMode)
**out = **in
}
if in.Suspend != nil { if in.Suspend != nil {
in, out := &in.Suspend, &out.Suspend in, out := &in.Suspend, &out.Suspend
*out = new(bool) *out = new(bool)

View File

@ -1577,11 +1577,12 @@
} }
}, },
"ttlSecondsAfterFinished": -1285029915, "ttlSecondsAfterFinished": -1285029915,
"suspend": true "completionMode": "{ȃ騑ȫ(踶NJđƟÝɹ橽ƴåj}c殶",
"suspend": false
} }
}, },
"successfulJobsHistoryLimit": 1729066291, "successfulJobsHistoryLimit": -2006986560,
"failedJobsHistoryLimit": -908823020 "failedJobsHistoryLimit": -380889943
}, },
"status": { "status": {
"active": [ "active": [
@ -1589,7 +1590,7 @@
"kind": "505", "kind": "505",
"namespace": "506", "namespace": "506",
"name": "507", "name": "507",
"uid": "`", "uid": "暉Ŝ!ȣ绰",
"apiVersion": "508", "apiVersion": "508",
"resourceVersion": "509", "resourceVersion": "509",
"fieldPath": "510" "fieldPath": "510"

View File

@ -31,7 +31,7 @@ metadata:
uid: "7" uid: "7"
spec: spec:
concurrencyPolicy: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ concurrencyPolicy: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ
failedJobsHistoryLimit: -908823020 failedJobsHistoryLimit: -380889943
jobTemplate: jobTemplate:
metadata: metadata:
annotations: annotations:
@ -65,6 +65,7 @@ spec:
spec: spec:
activeDeadlineSeconds: -1483125035702892746 activeDeadlineSeconds: -1483125035702892746
backoffLimit: -1822122846 backoffLimit: -1822122846
completionMode: '{ȃ騑ȫ(踶NJđƟÝɹ橽ƴåj}c殶'
completions: -106888179 completions: -106888179
manualSelector: true manualSelector: true
parallelism: -856030588 parallelism: -856030588
@ -74,7 +75,7 @@ spec:
operator: DoesNotExist operator: DoesNotExist
matchLabels: matchLabels:
2_kS91.e5K-_e63_-_3-n-_-__3u-.__P__.7U-Uo_4_-D7r__.am6-4_WE-_T: cd-2.-__E_Sv__26KX_R_.-.Nth._--S_4DAm 2_kS91.e5K-_e63_-_3-n-_-__3u-.__P__.7U-Uo_4_-D7r__.am6-4_WE-_T: cd-2.-__E_Sv__26KX_R_.-.Nth._--S_4DAm
suspend: true suspend: false
template: template:
metadata: metadata:
annotations: annotations:
@ -1081,7 +1082,7 @@ spec:
ttlSecondsAfterFinished: -1285029915 ttlSecondsAfterFinished: -1285029915
schedule: "19" schedule: "19"
startingDeadlineSeconds: -2555947251840004808 startingDeadlineSeconds: -2555947251840004808
successfulJobsHistoryLimit: 1729066291 successfulJobsHistoryLimit: -2006986560
suspend: true suspend: true
status: status:
active: active:
@ -1091,4 +1092,4 @@ status:
name: "507" name: "507"
namespace: "506" namespace: "506"
resourceVersion: "509" resourceVersion: "509"
uid: '`' uid: 暉Ŝ!ȣ绰

View File

@ -1530,23 +1530,23 @@
} }
}, },
"ttlSecondsAfterFinished": 1020403419, "ttlSecondsAfterFinished": 1020403419,
"completionMode": "ʉiUȡɭĮ庺%#囨q砅ƎXÄdƦ;", "completionMode": "汸\u003cƋlɋN磋镮ȺPÈɥ偁髕ģƗ鐫",
"suspend": false "suspend": true
}, },
"status": { "status": {
"conditions": [ "conditions": [
{ {
"type": "氮怉ƥ;\"薑Ȣ#闬輙", "type": "穌砊ʑȩ硘(ǒ[ȼ罦¦褅",
"status": "褅桃|", "status": "bCũw¼ ǫđ槴Ċį軠\u003e桼劑",
"lastProbeTime": "2625-10-20T09:03:25Z", "lastProbeTime": "2377-08-03T07:30:10Z",
"lastTransitionTime": "2222-01-27T15:06:59Z", "lastTransitionTime": "2619-06-09T02:29:16Z",
"reason": "482", "reason": "482",
"message": "483" "message": "483"
} }
], ],
"active": -882920248, "active": 157401294,
"succeeded": -1163607463, "succeeded": -702718077,
"failed": -758431192, "failed": 648978003,
"completedIndexes": "484" "completedIndexes": "484"
} }
} }

View File

@ -32,7 +32,7 @@ metadata:
spec: spec:
activeDeadlineSeconds: -5584804243908071872 activeDeadlineSeconds: -5584804243908071872
backoffLimit: -783752440 backoffLimit: -783752440
completionMode: ʉiUȡɭĮ庺%#囨q砅ƎXÄdƦ; completionMode: 汸<ƋlɋN磋镮ȺPÈɥ偁髕ģƗ鐫
completions: 1305381319 completions: 1305381319
manualSelector: true manualSelector: true
parallelism: 896585016 parallelism: 896585016
@ -44,7 +44,7 @@ spec:
- 3_bQw.-dG6c-.x - 3_bQw.-dG6c-.x
matchLabels: matchLabels:
hjT9s-j41-0-6p-JFHn7y-74.-0MUORQQ.N4: 3L.u hjT9s-j41-0-6p-JFHn7y-74.-0MUORQQ.N4: 3L.u
suspend: false suspend: true
template: template:
metadata: metadata:
annotations: annotations:
@ -1046,14 +1046,14 @@ spec:
volumePath: "101" volumePath: "101"
ttlSecondsAfterFinished: 1020403419 ttlSecondsAfterFinished: 1020403419
status: status:
active: -882920248 active: 157401294
completedIndexes: "484" completedIndexes: "484"
conditions: conditions:
- lastProbeTime: "2625-10-20T09:03:25Z" - lastProbeTime: "2377-08-03T07:30:10Z"
lastTransitionTime: "2222-01-27T15:06:59Z" lastTransitionTime: "2619-06-09T02:29:16Z"
message: "483" message: "483"
reason: "482" reason: "482"
status: 褅桃| status: bCũw¼ ǫđ槴Ċį軠>桼劑
type: 氮怉ƥ;"薑Ȣ#闬輙 type: 穌砊ʑȩ硘(ǒ[ȼ罦¦褅
failed: -758431192 failed: 648978003
succeeded: -1163607463 succeeded: -702718077

View File

@ -1577,11 +1577,12 @@
} }
}, },
"ttlSecondsAfterFinished": -1285029915, "ttlSecondsAfterFinished": -1285029915,
"suspend": true "completionMode": "{ȃ騑ȫ(踶NJđƟÝɹ橽ƴåj}c殶",
"suspend": false
} }
}, },
"successfulJobsHistoryLimit": 1729066291, "successfulJobsHistoryLimit": -2006986560,
"failedJobsHistoryLimit": -908823020 "failedJobsHistoryLimit": -380889943
}, },
"status": { "status": {
"active": [ "active": [
@ -1589,7 +1590,7 @@
"kind": "505", "kind": "505",
"namespace": "506", "namespace": "506",
"name": "507", "name": "507",
"uid": "`", "uid": "暉Ŝ!ȣ绰",
"apiVersion": "508", "apiVersion": "508",
"resourceVersion": "509", "resourceVersion": "509",
"fieldPath": "510" "fieldPath": "510"

View File

@ -31,7 +31,7 @@ metadata:
uid: "7" uid: "7"
spec: spec:
concurrencyPolicy: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ concurrencyPolicy: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ
failedJobsHistoryLimit: -908823020 failedJobsHistoryLimit: -380889943
jobTemplate: jobTemplate:
metadata: metadata:
annotations: annotations:
@ -65,6 +65,7 @@ spec:
spec: spec:
activeDeadlineSeconds: -1483125035702892746 activeDeadlineSeconds: -1483125035702892746
backoffLimit: -1822122846 backoffLimit: -1822122846
completionMode: '{ȃ騑ȫ(踶NJđƟÝɹ橽ƴåj}c殶'
completions: -106888179 completions: -106888179
manualSelector: true manualSelector: true
parallelism: -856030588 parallelism: -856030588
@ -74,7 +75,7 @@ spec:
operator: DoesNotExist operator: DoesNotExist
matchLabels: matchLabels:
2_kS91.e5K-_e63_-_3-n-_-__3u-.__P__.7U-Uo_4_-D7r__.am6-4_WE-_T: cd-2.-__E_Sv__26KX_R_.-.Nth._--S_4DAm 2_kS91.e5K-_e63_-_3-n-_-__3u-.__P__.7U-Uo_4_-D7r__.am6-4_WE-_T: cd-2.-__E_Sv__26KX_R_.-.Nth._--S_4DAm
suspend: true suspend: false
template: template:
metadata: metadata:
annotations: annotations:
@ -1081,7 +1082,7 @@ spec:
ttlSecondsAfterFinished: -1285029915 ttlSecondsAfterFinished: -1285029915
schedule: "19" schedule: "19"
startingDeadlineSeconds: -2555947251840004808 startingDeadlineSeconds: -2555947251840004808
successfulJobsHistoryLimit: 1729066291 successfulJobsHistoryLimit: -2006986560
suspend: true suspend: true
status: status:
active: active:
@ -1091,4 +1092,4 @@ status:
name: "507" name: "507"
namespace: "506" namespace: "506"
resourceVersion: "509" resourceVersion: "509"
uid: '`' uid: 暉Ŝ!ȣ绰

View File

@ -1574,7 +1574,7 @@
} }
}, },
"ttlSecondsAfterFinished": -2143422853, "ttlSecondsAfterFinished": -2143422853,
"completionMode": "Ŀř岈ǎǏ]S5:œƌ嵃ǁǞŢm珢\\%", "completionMode": "烡Z树Ȁ謁Ƹɮ-nʣž吞Ƞ唄",
"suspend": true "suspend": true
} }
} }

View File

@ -62,7 +62,7 @@ template:
spec: spec:
activeDeadlineSeconds: -9086179100394185427 activeDeadlineSeconds: -9086179100394185427
backoffLimit: -1796008812 backoffLimit: -1796008812
completionMode: Ŀř岈ǎǏ]S5:œƌ嵃ǁǞŢm珢\% completionMode: 烡Z树Ȁ謁Ƹɮ-nʣž吞Ƞ唄
completions: -1771909905 completions: -1771909905
manualSelector: false manualSelector: false
parallelism: -443114323 parallelism: -443114323

View File

@ -2165,7 +2165,7 @@ func describeJob(job *batchv1.Job, events *corev1.EventList) (string, error) {
} else { } else {
w.Write(LEVEL_0, "Completions:\t<unset>\n") w.Write(LEVEL_0, "Completions:\t<unset>\n")
} }
if job.Spec.CompletionMode != "" { if job.Spec.CompletionMode != nil {
w.Write(LEVEL_0, "Completion Mode:\t%s\n", job.Spec.CompletionMode) w.Write(LEVEL_0, "Completion Mode:\t%s\n", job.Spec.CompletionMode)
} }
if job.Status.StartTime != nil { if job.Status.StartTime != nil {
@ -2181,7 +2181,7 @@ 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, "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) 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 { if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode == batchv1.IndexedCompletion {
w.Write(LEVEL_0, "Completed Indexes:\t%s\n", capIndexesListOrNone(job.Status.CompletedIndexes, 50)) w.Write(LEVEL_0, "Completed Indexes:\t%s\n", capIndexesListOrNone(job.Status.CompletedIndexes, 50))
} }
DescribePodTemplate(&job.Spec.Template, w) DescribePodTemplate(&job.Spec.Template, w)

View File

@ -2065,6 +2065,7 @@ func TestDescribeDeployment(t *testing.T) {
} }
func TestDescribeJob(t *testing.T) { func TestDescribeJob(t *testing.T) {
indexedCompletion := batchv1.IndexedCompletion
cases := map[string]struct { cases := map[string]struct {
job *batchv1.Job job *batchv1.Job
wantCompletedIndexes string wantCompletedIndexes string
@ -2075,9 +2076,7 @@ func TestDescribeJob(t *testing.T) {
Name: "bar", Name: "bar",
Namespace: "foo", Namespace: "foo",
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{},
CompletionMode: batchv1.NonIndexedCompletion,
},
}, },
}, },
"no indexes": { "no indexes": {
@ -2087,7 +2086,7 @@ func TestDescribeJob(t *testing.T) {
Namespace: "foo", Namespace: "foo",
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion, CompletionMode: &indexedCompletion,
}, },
}, },
wantCompletedIndexes: "<none>", wantCompletedIndexes: "<none>",
@ -2099,7 +2098,7 @@ func TestDescribeJob(t *testing.T) {
Namespace: "foo", Namespace: "foo",
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion, CompletionMode: &indexedCompletion,
}, },
Status: batchv1.JobStatus{ Status: batchv1.JobStatus{
CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32", CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32",
@ -2114,7 +2113,7 @@ func TestDescribeJob(t *testing.T) {
Namespace: "foo", Namespace: "foo",
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
CompletionMode: batchv1.IndexedCompletion, CompletionMode: &indexedCompletion,
}, },
Status: batchv1.JobStatus{ 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", CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32-34,36,37",

View File

@ -156,7 +156,8 @@ var _ = SIGDescribe("Job", func() {
ginkgo.It("[Feature:IndexedJob] should create pods for an Indexed job with completion indexes", func() { ginkgo.It("[Feature:IndexedJob] should create pods for an Indexed job with completion indexes", func() {
ginkgo.By("Creating Indexed job") ginkgo.By("Creating Indexed job")
job := e2ejob.NewTestJob("succeed", "indexed-job", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit) job := e2ejob.NewTestJob("succeed", "indexed-job", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
job.Spec.CompletionMode = batchv1.IndexedCompletion mode := batchv1.IndexedCompletion
job.Spec.CompletionMode = &mode
job, err := e2ejob.CreateJob(f.ClientSet, f.Namespace.Name, job) job, err := e2ejob.CreateJob(f.ClientSet, f.Namespace.Name, job)
framework.ExpectNoError(err, "failed to create indexed job in namespace %s", f.Namespace.Name) framework.ExpectNoError(err, "failed to create indexed job in namespace %s", f.Namespace.Name)

View File

@ -200,11 +200,12 @@ func TestIndexedJob(t *testing.T) {
cancel() cancel()
}() }()
mode := batchv1.IndexedCompletion
jobObj, err := createJobWithDefaults(ctx, clientSet, ns.Name, &batchv1.Job{ jobObj, err := createJobWithDefaults(ctx, clientSet, ns.Name, &batchv1.Job{
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
Parallelism: pointer.Int32Ptr(3), Parallelism: pointer.Int32Ptr(3),
Completions: pointer.Int32Ptr(4), Completions: pointer.Int32Ptr(4),
CompletionMode: batchv1.IndexedCompletion, CompletionMode: &mode,
}, },
}) })
if err != nil { if err != nil {