mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Merge pull request #125751 from ahg-g/elastic-job
Graduate ElasticIndexedJob to GA
This commit is contained in:
commit
cdcaea687c
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user