mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
plumb feature gate value through job validation opts and modify validateCompletions function to only check completions == parallelism after the update, not before
This commit is contained in:
parent
c63f448451
commit
15077a0f28
@ -25,17 +25,16 @@ import (
|
|||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
|
apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/util/feature"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/batch"
|
"k8s.io/kubernetes/pkg/apis/batch"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -377,7 +376,7 @@ func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList {
|
|||||||
func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path, opts JobValidationOptions) field.ErrorList {
|
func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path, opts JobValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath, opts.PodValidationOptions)...)
|
allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath, opts.PodValidationOptions)...)
|
||||||
allErrs = append(allErrs, validateCompletions(spec, oldSpec, fldPath.Child("completions"))...)
|
allErrs = append(allErrs, validateCompletions(spec, oldSpec, fldPath.Child("completions"), opts)...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
|
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
|
||||||
allErrs = append(allErrs, validatePodTemplateUpdate(spec, oldSpec, fldPath, opts)...)
|
allErrs = append(allErrs, validatePodTemplateUpdate(spec, oldSpec, fldPath, opts)...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.CompletionMode, oldSpec.CompletionMode, fldPath.Child("completionMode"))...)
|
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.CompletionMode, oldSpec.CompletionMode, fldPath.Child("completionMode"))...)
|
||||||
@ -567,31 +566,31 @@ func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path, o
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCompletions(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList {
|
func validateCompletions(spec, oldSpec batch.JobSpec, fldPath *field.Path, opts JobValidationOptions) field.ErrorList {
|
||||||
if !feature.DefaultFeatureGate.Enabled(features.ElasticIndexedJob) {
|
if !opts.AllowElasticIndexedJobs {
|
||||||
return apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath)
|
return apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completions is immutable for non-indexed jobs.
|
// Completions is immutable for non-indexed jobs.
|
||||||
// For Indexed Jobs, if ElasticIndexedJob feature gate is not enabled,
|
// For Indexed Jobs, if ElasticIndexedJob feature gate is not enabled,
|
||||||
// fall back to validating that spec.Completions is always immutable.
|
// fall back to validating that spec.Completions is always immutable.
|
||||||
var allErrs field.ErrorList
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allErrs field.ErrorList
|
||||||
|
if apiequality.Semantic.DeepEqual(spec.Completions, oldSpec.Completions) {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
// Indexed Jobs cannot set completions to nil. The nil check
|
// Indexed Jobs cannot set completions to nil. The nil check
|
||||||
// is already performed in validateJobSpec, no need to add another error.
|
// is already performed in validateJobSpec, no need to add another error.
|
||||||
if spec.Completions == nil || oldSpec.Completions == nil {
|
if spec.Completions == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Indexed Jobs, spec.Completions can only be mutated in tandem
|
if *spec.Completions != *spec.Parallelism {
|
||||||
// with spec.Parallelism such that spec.Completions == spec.Parallelism
|
allErrs = append(allErrs, field.Invalid(fldPath, spec.Completions, fmt.Sprintf("can only be modified in tandem with %s", fldPath.Root().Child("parallelism").String())))
|
||||||
if *spec.Completions != *oldSpec.Completions {
|
|
||||||
if *spec.Completions != *spec.Parallelism || *oldSpec.Completions != *oldSpec.Parallelism {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, spec.Completions, fmt.Sprintf("can only be modified in tandem with %s", fldPath.Root().Child("parallelism").String())))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -602,4 +601,6 @@ type JobValidationOptions struct {
|
|||||||
AllowTrackingAnnotation bool
|
AllowTrackingAnnotation bool
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
@ -1298,6 +1298,9 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = pointer.Int32Ptr(2)
|
job.Spec.Completions = pointer.Int32Ptr(2)
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(2)
|
job.Spec.Parallelism = pointer.Int32Ptr(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{
|
||||||
@ -1314,9 +1317,8 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = pointer.Int32Ptr(3)
|
job.Spec.Completions = pointer.Int32Ptr(3)
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
||||||
},
|
},
|
||||||
err: &field.Error{
|
opts: JobValidationOptions{
|
||||||
Type: field.ErrorTypeInvalid,
|
AllowElasticIndexedJobs: true,
|
||||||
Field: "spec.completions",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"indexed job updating completions and parallelism to different values is invalid": {
|
"indexed job updating completions and parallelism to different values is invalid": {
|
||||||
@ -1334,6 +1336,9 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = pointer.Int32Ptr(2)
|
job.Spec.Completions = pointer.Int32Ptr(2)
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
||||||
},
|
},
|
||||||
|
opts: JobValidationOptions{
|
||||||
|
AllowElasticIndexedJobs: true,
|
||||||
|
},
|
||||||
err: &field.Error{
|
err: &field.Error{
|
||||||
Type: field.ErrorTypeInvalid,
|
Type: field.ErrorTypeInvalid,
|
||||||
Field: "spec.completions",
|
Field: "spec.completions",
|
||||||
@ -1354,6 +1359,9 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = nil
|
job.Spec.Completions = nil
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
||||||
},
|
},
|
||||||
|
opts: JobValidationOptions{
|
||||||
|
AllowElasticIndexedJobs: true,
|
||||||
|
},
|
||||||
err: &field.Error{
|
err: &field.Error{
|
||||||
Type: field.ErrorTypeRequired,
|
Type: field.ErrorTypeRequired,
|
||||||
Field: "spec.completions",
|
Field: "spec.completions",
|
||||||
@ -1374,6 +1382,9 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = pointer.Int32Ptr(2)
|
job.Spec.Completions = pointer.Int32Ptr(2)
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(1)
|
job.Spec.Parallelism = pointer.Int32Ptr(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{
|
||||||
@ -1390,6 +1401,9 @@ func TestValidateJobUpdate(t *testing.T) {
|
|||||||
job.Spec.Completions = pointer.Int32Ptr(2)
|
job.Spec.Completions = pointer.Int32Ptr(2)
|
||||||
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
job.Spec.Parallelism = pointer.Int32Ptr(3)
|
||||||
},
|
},
|
||||||
|
opts: JobValidationOptions{
|
||||||
|
AllowElasticIndexedJobs: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
|
ignoreValueAndDetail := cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
|
||||||
|
@ -185,6 +185,8 @@ func validationOptionsForJob(newJob, oldJob *batch.Job) batchvalidation.JobValid
|
|||||||
opts.AllowMutableSchedulingDirectives = utilfeature.DefaultFeatureGate.Enabled(features.JobMutableNodeSchedulingDirectives) &&
|
opts.AllowMutableSchedulingDirectives = utilfeature.DefaultFeatureGate.Enabled(features.JobMutableNodeSchedulingDirectives) &&
|
||||||
suspended && notStarted
|
suspended && notStarted
|
||||||
|
|
||||||
|
// Elastic indexed jobs (mutable completions iff updated parallelism == updated completions)
|
||||||
|
opts.AllowElasticIndexedJobs = utilfeature.DefaultFeatureGate.Enabled(features.ElasticIndexedJob)
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
|
Loading…
Reference in New Issue
Block a user