validation.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validation
  14. import (
  15. "github.com/robfig/cron"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
  18. "k8s.io/apimachinery/pkg/labels"
  19. apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
  20. "k8s.io/apimachinery/pkg/util/validation/field"
  21. "k8s.io/kubernetes/pkg/apis/batch"
  22. api "k8s.io/kubernetes/pkg/apis/core"
  23. apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
  24. )
  25. // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
  26. // move to new location. Replace batch.Job with an interface.
  27. //
  28. // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
  29. // metadata, and the labels on the pod template are as generated.
  30. func ValidateGeneratedSelector(obj *batch.Job) field.ErrorList {
  31. allErrs := field.ErrorList{}
  32. if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector {
  33. return allErrs
  34. }
  35. if obj.Spec.Selector == nil {
  36. return allErrs // This case should already have been checked in caller. No need for more errors.
  37. }
  38. // If somehow uid was unset then we would get "controller-uid=" as the selector
  39. // which is bad.
  40. if obj.ObjectMeta.UID == "" {
  41. allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
  42. }
  43. // If selector generation was requested, then expected labels must be
  44. // present on pod template, and must match job's uid and name. The
  45. // generated (not-manual) selectors/labels ensure no overlap with other
  46. // controllers. The manual mode allows orphaning, adoption,
  47. // backward-compatibility, and experimentation with new
  48. // labeling/selection schemes. Automatic selector generation should
  49. // have placed certain labels on the pod, but this could have failed if
  50. // the user added coflicting labels. Validate that the expected
  51. // generated ones are there.
  52. allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...)
  53. allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...)
  54. expectedLabels := make(map[string]string)
  55. expectedLabels["controller-uid"] = string(obj.UID)
  56. expectedLabels["job-name"] = string(obj.Name)
  57. // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
  58. if selector, err := metav1.LabelSelectorAsSelector(obj.Spec.Selector); err == nil {
  59. if !selector.Matches(labels.Set(expectedLabels)) {
  60. allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated"))
  61. }
  62. }
  63. return allErrs
  64. }
  65. func ValidateJob(job *batch.Job) field.ErrorList {
  66. // Jobs and rcs have the same name validation
  67. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  68. allErrs = append(allErrs, ValidateGeneratedSelector(job)...)
  69. allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...)
  70. return allErrs
  71. }
  72. func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  73. allErrs := validateJobSpec(spec, fldPath)
  74. if spec.Selector == nil {
  75. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  76. } else {
  77. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  78. }
  79. // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
  80. if selector, err := metav1.LabelSelectorAsSelector(spec.Selector); err == nil {
  81. labels := labels.Set(spec.Template.Labels)
  82. if !selector.Matches(labels) {
  83. allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
  84. }
  85. }
  86. return allErrs
  87. }
  88. func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  89. allErrs := field.ErrorList{}
  90. if spec.Parallelism != nil {
  91. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...)
  92. }
  93. if spec.Completions != nil {
  94. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...)
  95. }
  96. if spec.ActiveDeadlineSeconds != nil {
  97. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...)
  98. }
  99. if spec.BackoffLimit != nil {
  100. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.BackoffLimit), fldPath.Child("backoffLimit"))...)
  101. }
  102. if spec.TTLSecondsAfterFinished != nil {
  103. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...)
  104. }
  105. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
  106. if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
  107. spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
  108. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"),
  109. spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)}))
  110. }
  111. return allErrs
  112. }
  113. func ValidateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList {
  114. allErrs := field.ErrorList{}
  115. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...)
  116. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...)
  117. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...)
  118. return allErrs
  119. }
  120. func ValidateJobUpdate(job, oldJob *batch.Job) field.ErrorList {
  121. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  122. allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...)
  123. return allErrs
  124. }
  125. func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList {
  126. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  127. allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...)
  128. return allErrs
  129. }
  130. func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList {
  131. allErrs := field.ErrorList{}
  132. allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...)
  133. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
  134. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
  135. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
  136. return allErrs
  137. }
  138. func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList {
  139. allErrs := field.ErrorList{}
  140. allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...)
  141. return allErrs
  142. }
  143. func ValidateCronJob(scheduledJob *batch.CronJob) field.ErrorList {
  144. // CronJobs and rcs have the same name validation
  145. allErrs := apivalidation.ValidateObjectMeta(&scheduledJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  146. allErrs = append(allErrs, ValidateCronJobSpec(&scheduledJob.Spec, field.NewPath("spec"))...)
  147. if len(scheduledJob.ObjectMeta.Name) > apimachineryvalidation.DNS1035LabelMaxLength-11 {
  148. // The cronjob controller appends a 11-character suffix to the cronjob (`-$TIMESTAMP`) when
  149. // creating a job. The job name length limit is 63 characters.
  150. // Therefore cronjob names must have length <= 63-11=52. If we don't validate this here,
  151. // then job creation will fail later.
  152. allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), scheduledJob.ObjectMeta.Name, "must be no more than 52 characters"))
  153. }
  154. return allErrs
  155. }
  156. func ValidateCronJobUpdate(job, oldJob *batch.CronJob) field.ErrorList {
  157. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  158. allErrs = append(allErrs, ValidateCronJobSpec(&job.Spec, field.NewPath("spec"))...)
  159. // skip the 52-character name validation limit on update validation
  160. // to allow old cronjobs with names > 52 chars to be updated/deleted
  161. return allErrs
  162. }
  163. func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path) field.ErrorList {
  164. allErrs := field.ErrorList{}
  165. if len(spec.Schedule) == 0 {
  166. allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
  167. } else {
  168. allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...)
  169. }
  170. if spec.StartingDeadlineSeconds != nil {
  171. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
  172. }
  173. allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
  174. allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...)
  175. if spec.SuccessfulJobsHistoryLimit != nil {
  176. // zero is a valid SuccessfulJobsHistoryLimit
  177. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.SuccessfulJobsHistoryLimit), fldPath.Child("successfulJobsHistoryLimit"))...)
  178. }
  179. if spec.FailedJobsHistoryLimit != nil {
  180. // zero is a valid SuccessfulJobsHistoryLimit
  181. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.FailedJobsHistoryLimit), fldPath.Child("failedJobsHistoryLimit"))...)
  182. }
  183. return allErrs
  184. }
  185. func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPath *field.Path) field.ErrorList {
  186. allErrs := field.ErrorList{}
  187. switch *concurrencyPolicy {
  188. case batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent:
  189. break
  190. case "":
  191. allErrs = append(allErrs, field.Required(fldPath, ""))
  192. default:
  193. validValues := []string{string(batch.AllowConcurrent), string(batch.ForbidConcurrent), string(batch.ReplaceConcurrent)}
  194. allErrs = append(allErrs, field.NotSupported(fldPath, *concurrencyPolicy, validValues))
  195. }
  196. return allErrs
  197. }
  198. func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
  199. allErrs := field.ErrorList{}
  200. if _, err := cron.ParseStandard(schedule); err != nil {
  201. allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
  202. }
  203. return allErrs
  204. }
  205. func ValidateJobTemplate(job *batch.JobTemplate) field.ErrorList {
  206. // this method should be identical to ValidateJob
  207. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  208. allErrs = append(allErrs, ValidateJobTemplateSpec(&job.Template, field.NewPath("template"))...)
  209. return allErrs
  210. }
  211. func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path) field.ErrorList {
  212. allErrs := validateJobSpec(&spec.Spec, fldPath.Child("spec"))
  213. // jobtemplate will always have the selector automatically generated
  214. if spec.Spec.Selector != nil {
  215. allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "selector"), spec.Spec.Selector, "`selector` will be auto-generated"))
  216. }
  217. if spec.Spec.ManualSelector != nil && *spec.Spec.ManualSelector {
  218. allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "manualSelector"), spec.Spec.ManualSelector, []string{"nil", "false"}))
  219. }
  220. return allErrs
  221. }