Merge pull request #125751 from ahg-g/elastic-job
Graduate ElasticIndexedJob to GA
This commit is contained in:
		| @@ -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 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1562,22 +1562,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}, | ||||||
| @@ -1883,7 +1867,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{ | ||||||
| @@ -1900,7 +1884,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": { | ||||||
| @@ -2153,9 +2136,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{ | ||||||
| @@ -2172,9 +2152,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{ | ||||||
| @@ -2191,9 +2168,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", | ||||||
| @@ -2214,9 +2188,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", | ||||||
| @@ -2237,9 +2208,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{ | ||||||
| @@ -2256,9 +2224,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") | ||||||
|   | |||||||
| @@ -1144,7 +1144,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}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot