123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- /*
- Copyright 2016 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package validation
- import (
- "github.com/robfig/cron"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/unversioned"
- unversionedvalidation "k8s.io/kubernetes/pkg/api/unversioned/validation"
- apivalidation "k8s.io/kubernetes/pkg/api/validation"
- "k8s.io/kubernetes/pkg/apis/batch"
- "k8s.io/kubernetes/pkg/labels"
- "k8s.io/kubernetes/pkg/util/validation/field"
- )
- // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
- // move to new location. Replace batch.Job with an interface.
- //
- // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
- // metadata, and the labels on the pod template are as generated.
- func ValidateGeneratedSelector(obj *batch.Job) field.ErrorList {
- allErrs := field.ErrorList{}
- if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector {
- return allErrs
- }
- if obj.Spec.Selector == nil {
- return allErrs // This case should already have been checked in caller. No need for more errors.
- }
- // If somehow uid was unset then we would get "controller-uid=" as the selector
- // which is bad.
- if obj.ObjectMeta.UID == "" {
- allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
- }
- // If somehow uid was unset then we would get "controller-uid=" as the selector
- // which is bad.
- if obj.ObjectMeta.UID == "" {
- allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
- }
- // If selector generation was requested, then expected labels must be
- // present on pod template, and much match job's uid and name. The
- // generated (not-manual) selectors/labels ensure no overlap with other
- // controllers. The manual mode allows orphaning, adoption,
- // backward-compatibility, and experimentation with new
- // labeling/selection schemes. Automatic selector generation should
- // have placed certain labels on the pod, but this could have failed if
- // the user added coflicting labels. Validate that the expected
- // generated ones are there.
- allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...)
- allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...)
- expectedLabels := make(map[string]string)
- expectedLabels["controller-uid"] = string(obj.UID)
- expectedLabels["job-name"] = string(obj.Name)
- // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
- if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil {
- if !selector.Matches(labels.Set(expectedLabels)) {
- allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated"))
- }
- }
- return allErrs
- }
- func ValidateJob(job *batch.Job) field.ErrorList {
- // Jobs and rcs have the same name validation
- allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateGeneratedSelector(job)...)
- allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
- allErrs := validateJobSpec(spec, fldPath)
- if spec.Selector == nil {
- allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
- } else {
- allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
- }
- // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
- if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil {
- labels := labels.Set(spec.Template.Labels)
- if !selector.Matches(labels) {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
- }
- }
- return allErrs
- }
- func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if spec.Parallelism != nil {
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...)
- }
- if spec.Completions != nil {
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...)
- }
- if spec.ActiveDeadlineSeconds != nil {
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...)
- }
- allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
- if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
- spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
- allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"),
- spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)}))
- }
- return allErrs
- }
- func ValidateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...)
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...)
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...)
- return allErrs
- }
- func ValidateJobUpdate(job, oldJob *batch.Job) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList {
- allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...)
- return allErrs
- }
- func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...)
- allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
- allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
- allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
- return allErrs
- }
- func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList {
- allErrs := field.ErrorList{}
- allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...)
- return allErrs
- }
- func ValidateScheduledJob(scheduledJob *batch.ScheduledJob) field.ErrorList {
- // ScheduledJobs and rcs have the same name validation
- allErrs := apivalidation.ValidateObjectMeta(&scheduledJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateScheduledJobSpec(&scheduledJob.Spec, field.NewPath("spec"))...)
- return allErrs
- }
- func ValidateScheduledJobSpec(spec *batch.ScheduledJobSpec, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- if len(spec.Schedule) == 0 {
- allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
- } else {
- allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...)
- }
- if spec.StartingDeadlineSeconds != nil {
- allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
- }
- allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
- allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...)
- return allErrs
- }
- func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- switch *concurrencyPolicy {
- case batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent:
- break
- case "":
- allErrs = append(allErrs, field.Required(fldPath, ""))
- default:
- validValues := []string{string(batch.AllowConcurrent), string(batch.ForbidConcurrent), string(batch.ReplaceConcurrent)}
- allErrs = append(allErrs, field.NotSupported(fldPath, *concurrencyPolicy, validValues))
- }
- return allErrs
- }
- func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
- allErrs := field.ErrorList{}
- // TODO soltysh: this should be removed when https://github.com/robfig/cron/issues/58 is fixed
- tmpSchedule := schedule
- if len(schedule) > 0 && schedule[0] != '@' {
- tmpSchedule = "0 " + schedule
- }
- if _, err := cron.Parse(tmpSchedule); err != nil {
- allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
- }
- return allErrs
- }
- func ValidateJobTemplate(job *batch.JobTemplate) field.ErrorList {
- // this method should be identical to ValidateJob
- allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
- allErrs = append(allErrs, ValidateJobTemplateSpec(&job.Template, field.NewPath("template"))...)
- return allErrs
- }
- func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path) field.ErrorList {
- allErrs := validateJobSpec(&spec.Spec, fldPath.Child("spec"))
- // jobtemplate will always have the selector automatically generated
- if spec.Spec.Selector != nil {
- allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "selector"), spec.Spec.Selector, "`selector` will be auto-generated"))
- }
- if spec.Spec.ManualSelector != nil && *spec.Spec.ManualSelector {
- allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "manualSelector"), spec.Spec.ManualSelector, []string{"nil", "false"}))
- }
- return allErrs
- }
|