validation.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
  26. // metadata, and the labels on the pod template are as generated.
  27. //
  28. // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
  29. // move to new location. Replace batch.Job with an interface.
  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. // ValidateJob validates a Job and returns an ErrorList with any errors.
  66. func ValidateJob(job *batch.Job) field.ErrorList {
  67. // Jobs and rcs have the same name validation
  68. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  69. allErrs = append(allErrs, ValidateGeneratedSelector(job)...)
  70. allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...)
  71. return allErrs
  72. }
  73. // ValidateJobSpec validates a JobSpec and returns an ErrorList with any errors.
  74. func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  75. allErrs := validateJobSpec(spec, fldPath)
  76. if spec.Selector == nil {
  77. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  78. } else {
  79. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  80. }
  81. // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
  82. if selector, err := metav1.LabelSelectorAsSelector(spec.Selector); err == nil {
  83. labels := labels.Set(spec.Template.Labels)
  84. if !selector.Matches(labels) {
  85. allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
  86. }
  87. }
  88. return allErrs
  89. }
  90. func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  91. allErrs := field.ErrorList{}
  92. if spec.Parallelism != nil {
  93. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...)
  94. }
  95. if spec.Completions != nil {
  96. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...)
  97. }
  98. if spec.ActiveDeadlineSeconds != nil {
  99. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...)
  100. }
  101. if spec.BackoffLimit != nil {
  102. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.BackoffLimit), fldPath.Child("backoffLimit"))...)
  103. }
  104. if spec.TTLSecondsAfterFinished != nil {
  105. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...)
  106. }
  107. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
  108. if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
  109. spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
  110. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"),
  111. spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)}))
  112. }
  113. return allErrs
  114. }
  115. // ValidateJobStatus validates a JobStatus and returns an ErrorList with any errors.
  116. func ValidateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList {
  117. allErrs := field.ErrorList{}
  118. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...)
  119. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...)
  120. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...)
  121. return allErrs
  122. }
  123. // ValidateJobUpdate validates an update to a Job and returns an ErrorList with any errors.
  124. func ValidateJobUpdate(job, oldJob *batch.Job) field.ErrorList {
  125. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  126. allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...)
  127. return allErrs
  128. }
  129. // ValidateJobUpdateStatus validates an update to the status of a Job and returns an ErrorList with any errors.
  130. func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList {
  131. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  132. allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...)
  133. return allErrs
  134. }
  135. // ValidateJobSpecUpdate validates an update to a JobSpec and returns an ErrorList with any errors.
  136. func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList {
  137. allErrs := field.ErrorList{}
  138. allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...)
  139. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
  140. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
  141. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
  142. return allErrs
  143. }
  144. // ValidateJobStatusUpdate validates an update to a JobStatus and returns an ErrorList with any errors.
  145. func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList {
  146. allErrs := field.ErrorList{}
  147. allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...)
  148. return allErrs
  149. }
  150. // ValidateCronJob validates a CronJob and returns an ErrorList with any errors.
  151. func ValidateCronJob(scheduledJob *batch.CronJob) field.ErrorList {
  152. // CronJobs and rcs have the same name validation
  153. allErrs := apivalidation.ValidateObjectMeta(&scheduledJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  154. allErrs = append(allErrs, ValidateCronJobSpec(&scheduledJob.Spec, field.NewPath("spec"))...)
  155. if len(scheduledJob.ObjectMeta.Name) > apimachineryvalidation.DNS1035LabelMaxLength-11 {
  156. // The cronjob controller appends a 11-character suffix to the cronjob (`-$TIMESTAMP`) when
  157. // creating a job. The job name length limit is 63 characters.
  158. // Therefore cronjob names must have length <= 63-11=52. If we don't validate this here,
  159. // then job creation will fail later.
  160. allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), scheduledJob.ObjectMeta.Name, "must be no more than 52 characters"))
  161. }
  162. return allErrs
  163. }
  164. // ValidateCronJobUpdate validates an update to a CronJob and returns an ErrorList with any errors.
  165. func ValidateCronJobUpdate(job, oldJob *batch.CronJob) field.ErrorList {
  166. allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata"))
  167. allErrs = append(allErrs, ValidateCronJobSpec(&job.Spec, field.NewPath("spec"))...)
  168. // skip the 52-character name validation limit on update validation
  169. // to allow old cronjobs with names > 52 chars to be updated/deleted
  170. return allErrs
  171. }
  172. // ValidateCronJobSpec validates a CronJobSpec and returns an ErrorList with any errors.
  173. func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path) field.ErrorList {
  174. allErrs := field.ErrorList{}
  175. if len(spec.Schedule) == 0 {
  176. allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
  177. } else {
  178. allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...)
  179. }
  180. if spec.StartingDeadlineSeconds != nil {
  181. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
  182. }
  183. allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
  184. allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...)
  185. if spec.SuccessfulJobsHistoryLimit != nil {
  186. // zero is a valid SuccessfulJobsHistoryLimit
  187. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.SuccessfulJobsHistoryLimit), fldPath.Child("successfulJobsHistoryLimit"))...)
  188. }
  189. if spec.FailedJobsHistoryLimit != nil {
  190. // zero is a valid SuccessfulJobsHistoryLimit
  191. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.FailedJobsHistoryLimit), fldPath.Child("failedJobsHistoryLimit"))...)
  192. }
  193. return allErrs
  194. }
  195. func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPath *field.Path) field.ErrorList {
  196. allErrs := field.ErrorList{}
  197. switch *concurrencyPolicy {
  198. case batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent:
  199. break
  200. case "":
  201. allErrs = append(allErrs, field.Required(fldPath, ""))
  202. default:
  203. validValues := []string{string(batch.AllowConcurrent), string(batch.ForbidConcurrent), string(batch.ReplaceConcurrent)}
  204. allErrs = append(allErrs, field.NotSupported(fldPath, *concurrencyPolicy, validValues))
  205. }
  206. return allErrs
  207. }
  208. func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
  209. allErrs := field.ErrorList{}
  210. if _, err := cron.ParseStandard(schedule); err != nil {
  211. allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
  212. }
  213. return allErrs
  214. }
  215. // ValidateJobTemplate validates a JobTemplate and returns an ErrorList with any errors.
  216. func ValidateJobTemplate(job *batch.JobTemplate) field.ErrorList {
  217. // this method should be identical to ValidateJob
  218. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  219. allErrs = append(allErrs, ValidateJobTemplateSpec(&job.Template, field.NewPath("template"))...)
  220. return allErrs
  221. }
  222. // ValidateJobTemplateSpec validates a JobTemplateSpec and returns an ErrorList with any errors.
  223. func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path) field.ErrorList {
  224. allErrs := validateJobSpec(&spec.Spec, fldPath.Child("spec"))
  225. // jobtemplate will always have the selector automatically generated
  226. if spec.Spec.Selector != nil {
  227. allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "selector"), spec.Spec.Selector, "`selector` will be auto-generated"))
  228. }
  229. if spec.Spec.ManualSelector != nil && *spec.Spec.ManualSelector {
  230. allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "manualSelector"), spec.Spec.ManualSelector, []string{"nil", "false"}))
  231. }
  232. return allErrs
  233. }