Graduate ElasticIndexedJob to GA

This commit is contained in:
ahg-g 2024-06-27 06:17:32 +00:00
parent bffc02b955
commit be410c0dae
5 changed files with 7 additions and 72 deletions

View File

@ -838,13 +838,7 @@ func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path, o
} }
func validateCompletions(spec, oldSpec batch.JobSpec, fldPath *field.Path, opts JobValidationOptions) field.ErrorList { func validateCompletions(spec, oldSpec batch.JobSpec, fldPath *field.Path, opts JobValidationOptions) field.ErrorList {
if !opts.AllowElasticIndexedJobs { // Completions is immutable for non-indexed jobs, but mutable for Indexed Jobs.
return apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath)
}
// Completions is immutable for non-indexed jobs.
// For Indexed Jobs, if ElasticIndexedJob feature gate is not enabled,
// fall back to validating that spec.Completions is always immutable.
isIndexedJob := spec.CompletionMode != nil && *spec.CompletionMode == batch.IndexedCompletion isIndexedJob := spec.CompletionMode != nil && *spec.CompletionMode == batch.IndexedCompletion
if !isIndexedJob { if !isIndexedJob {
return apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath) return apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath)
@ -998,8 +992,6 @@ type JobValidationOptions struct {
apivalidation.PodValidationOptions apivalidation.PodValidationOptions
// Allow mutable node affinity, selector and tolerations of the template // Allow mutable node affinity, selector and tolerations of the template
AllowMutableSchedulingDirectives bool AllowMutableSchedulingDirectives bool
// Allow elastic indexed jobs
AllowElasticIndexedJobs bool
// Require Job to have the label on batch.kubernetes.io/job-name and batch.kubernetes.io/controller-uid // Require Job to have the label on batch.kubernetes.io/job-name and batch.kubernetes.io/controller-uid
RequirePrefixedLabels bool RequirePrefixedLabels bool
} }

View File

@ -1609,22 +1609,6 @@ func TestValidateJobUpdate(t *testing.T) {
Field: "spec.completions", Field: "spec.completions",
}, },
}, },
"immutable completions for indexed job when AllowElasticIndexedJobs is false": {
old: batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: batch.JobSpec{
Selector: validGeneratedSelector,
Template: validPodTemplateSpecForGenerated,
},
},
update: func(job *batch.Job) {
job.Spec.Completions = pointer.Int32(1)
},
err: &field.Error{
Type: field.ErrorTypeInvalid,
Field: "spec.completions",
},
},
"immutable selector": { "immutable selector": {
old: batch.Job{ old: batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
@ -1930,7 +1914,7 @@ func TestValidateJobUpdate(t *testing.T) {
Field: "spec.completionMode", Field: "spec.completionMode",
}, },
}, },
"immutable completions for non-indexed job when AllowElasticIndexedJobs is true": { "immutable completions for non-indexed job": {
old: batch.Job{ old: batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: batch.JobSpec{ Spec: batch.JobSpec{
@ -1947,7 +1931,6 @@ func TestValidateJobUpdate(t *testing.T) {
Type: field.ErrorTypeInvalid, Type: field.ErrorTypeInvalid,
Field: "spec.completions", Field: "spec.completions",
}, },
opts: JobValidationOptions{AllowElasticIndexedJobs: true},
}, },
"immutable node affinity": { "immutable node affinity": {
@ -2200,9 +2183,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = pointer.Int32(2) job.Spec.Completions = pointer.Int32(2)
job.Spec.Parallelism = pointer.Int32(2) job.Spec.Parallelism = pointer.Int32(2)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
}, },
"previous parallelism != previous completions, new parallelism == new completions": { "previous parallelism != previous completions, new parallelism == new completions": {
old: batch.Job{ old: batch.Job{
@ -2219,9 +2199,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = pointer.Int32(3) job.Spec.Completions = pointer.Int32(3)
job.Spec.Parallelism = pointer.Int32(3) job.Spec.Parallelism = pointer.Int32(3)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
}, },
"indexed job updating completions and parallelism to different values is invalid": { "indexed job updating completions and parallelism to different values is invalid": {
old: batch.Job{ old: batch.Job{
@ -2238,9 +2215,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = pointer.Int32(2) job.Spec.Completions = pointer.Int32(2)
job.Spec.Parallelism = pointer.Int32(3) job.Spec.Parallelism = pointer.Int32(3)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
err: &field.Error{ err: &field.Error{
Type: field.ErrorTypeInvalid, Type: field.ErrorTypeInvalid,
Field: "spec.completions", Field: "spec.completions",
@ -2261,9 +2235,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = nil job.Spec.Completions = nil
job.Spec.Parallelism = pointer.Int32(3) job.Spec.Parallelism = pointer.Int32(3)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
err: &field.Error{ err: &field.Error{
Type: field.ErrorTypeRequired, Type: field.ErrorTypeRequired,
Field: "spec.completions", Field: "spec.completions",
@ -2284,9 +2255,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = pointer.Int32(2) job.Spec.Completions = pointer.Int32(2)
job.Spec.Parallelism = pointer.Int32(1) job.Spec.Parallelism = pointer.Int32(1)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
}, },
"indexed job with completions unchanged, parallelism increased higher than completions": { "indexed job with completions unchanged, parallelism increased higher than completions": {
old: batch.Job{ old: batch.Job{
@ -2303,9 +2271,6 @@ func TestValidateJobUpdate(t *testing.T) {
job.Spec.Completions = pointer.Int32(2) job.Spec.Completions = pointer.Int32(2)
job.Spec.Parallelism = pointer.Int32(3) job.Spec.Parallelism = pointer.Int32(3)
}, },
opts: JobValidationOptions{
AllowElasticIndexedJobs: true,
},
}, },
} }
ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail") ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")

View File

@ -1131,7 +1131,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
RuntimeClassInImageCriAPI: {Default: false, PreRelease: featuregate.Alpha}, RuntimeClassInImageCriAPI: {Default: false, PreRelease: featuregate.Alpha},
ElasticIndexedJob: {Default: true, PreRelease: featuregate.Beta}, ElasticIndexedJob: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.31, remove in 1.32
SchedulerQueueingHints: {Default: false, PreRelease: featuregate.Beta}, SchedulerQueueingHints: {Default: false, PreRelease: featuregate.Beta},

View File

@ -187,9 +187,8 @@ func validationOptionsForJob(newJob, oldJob *batch.Job) batchvalidation.JobValid
oldPodTemplate = &oldJob.Spec.Template oldPodTemplate = &oldJob.Spec.Template
} }
opts := batchvalidation.JobValidationOptions{ opts := batchvalidation.JobValidationOptions{
PodValidationOptions: pod.GetValidationOptionsFromPodTemplate(newPodTemplate, oldPodTemplate), PodValidationOptions: pod.GetValidationOptionsFromPodTemplate(newPodTemplate, oldPodTemplate),
AllowElasticIndexedJobs: utilfeature.DefaultFeatureGate.Enabled(features.ElasticIndexedJob), RequirePrefixedLabels: true,
RequirePrefixedLabels: true,
} }
if oldJob != nil { if oldJob != nil {
opts.AllowInvalidLabelValueInSelector = opts.AllowInvalidLabelValueInSelector || metav1validation.LabelSelectorHasInvalidLabelValue(oldJob.Spec.Selector) opts.AllowInvalidLabelValueInSelector = opts.AllowInvalidLabelValueInSelector || metav1validation.LabelSelectorHasInvalidLabelValue(oldJob.Spec.Selector)

View File

@ -34,10 +34,8 @@ import (
eventsv1 "k8s.io/api/events/v1" eventsv1 "k8s.io/api/events/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/feature"
@ -2756,25 +2754,10 @@ func TestElasticIndexedJob(t *testing.T) {
wantTerminating *int32 wantTerminating *int32
} }
cases := map[string]struct { cases := map[string]struct {
featureGate bool jobUpdates []jobUpdate
jobUpdates []jobUpdate wantErr *apierrors.StatusError
wantErr *apierrors.StatusError
}{ }{
"feature flag off, mutation not allowed": {
jobUpdates: []jobUpdate{
{
completions: ptr.To[int32](4),
wantTerminating: ptr.To[int32](0),
},
},
wantErr: apierrors.NewInvalid(
schema.GroupKind{Group: "batch", Kind: "Job"},
"test-job",
field.ErrorList{field.Invalid(field.NewPath("spec", "completions"), 4, "field is immutable")},
),
},
"scale up": { "scale up": {
featureGate: true,
jobUpdates: []jobUpdate{ jobUpdates: []jobUpdate{
{ {
// Scale up completions 3->4 then succeed indexes 0-3 // Scale up completions 3->4 then succeed indexes 0-3
@ -2786,7 +2769,6 @@ func TestElasticIndexedJob(t *testing.T) {
}, },
}, },
"scale down": { "scale down": {
featureGate: true,
jobUpdates: []jobUpdate{ jobUpdates: []jobUpdate{
// First succeed index 1 and fail index 2 while completions is still original value (3). // First succeed index 1 and fail index 2 while completions is still original value (3).
{ {
@ -2810,7 +2792,6 @@ func TestElasticIndexedJob(t *testing.T) {
}, },
}, },
"index finishes successfully, scale down, scale up": { "index finishes successfully, scale down, scale up": {
featureGate: true,
jobUpdates: []jobUpdate{ jobUpdates: []jobUpdate{
// First succeed index 2 while completions is still original value (3). // First succeed index 2 while completions is still original value (3).
{ {
@ -2837,7 +2818,6 @@ func TestElasticIndexedJob(t *testing.T) {
}, },
}, },
"scale down to 0, verify that the job succeeds": { "scale down to 0, verify that the job succeeds": {
featureGate: true,
jobUpdates: []jobUpdate{ jobUpdates: []jobUpdate{
{ {
completions: ptr.To[int32](0), completions: ptr.To[int32](0),
@ -2850,7 +2830,6 @@ func TestElasticIndexedJob(t *testing.T) {
for name, tc := range cases { for name, tc := range cases {
tc := tc tc := tc
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ElasticIndexedJob, tc.featureGate)
closeFn, restConfig, clientSet, ns := setup(t, "indexed") closeFn, restConfig, clientSet, ns := setup(t, "indexed")
defer closeFn() defer closeFn()
ctx, cancel := startJobControllerAndWaitForCaches(t, restConfig) ctx, cancel := startJobControllerAndWaitForCaches(t, restConfig)