validation.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. "k8s.io/kubernetes/pkg/api"
  17. "k8s.io/kubernetes/pkg/api/unversioned"
  18. unversionedvalidation "k8s.io/kubernetes/pkg/api/unversioned/validation"
  19. apivalidation "k8s.io/kubernetes/pkg/api/validation"
  20. "k8s.io/kubernetes/pkg/apis/batch"
  21. "k8s.io/kubernetes/pkg/labels"
  22. "k8s.io/kubernetes/pkg/util/validation/field"
  23. )
  24. // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
  25. // move to new location. Replace batch.Job with an interface.
  26. //
  27. // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
  28. // metadata, and the labels on the pod template are as generated.
  29. func ValidateGeneratedSelector(obj *batch.Job) field.ErrorList {
  30. allErrs := field.ErrorList{}
  31. if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector {
  32. return allErrs
  33. }
  34. if obj.Spec.Selector == nil {
  35. return allErrs // This case should already have been checked in caller. No need for more errors.
  36. }
  37. // If somehow uid was unset then we would get "controller-uid=" as the selector
  38. // which is bad.
  39. if obj.ObjectMeta.UID == "" {
  40. allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
  41. }
  42. // If somehow uid was unset then we would get "controller-uid=" as the selector
  43. // which is bad.
  44. if obj.ObjectMeta.UID == "" {
  45. allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
  46. }
  47. // If selector generation was requested, then expected labels must be
  48. // present on pod template, and much match job's uid and name. The
  49. // generated (not-manual) selectors/labels ensure no overlap with other
  50. // controllers. The manual mode allows orphaning, adoption,
  51. // backward-compatibility, and experimentation with new
  52. // labeling/selection schemes. Automatic selector generation should
  53. // have placed certain labels on the pod, but this could have failed if
  54. // the user added coflicting labels. Validate that the expected
  55. // generated ones are there.
  56. allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...)
  57. allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...)
  58. expectedLabels := make(map[string]string)
  59. expectedLabels["controller-uid"] = string(obj.UID)
  60. expectedLabels["job-name"] = string(obj.Name)
  61. // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
  62. if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil {
  63. if !selector.Matches(labels.Set(expectedLabels)) {
  64. allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated"))
  65. }
  66. }
  67. return allErrs
  68. }
  69. func ValidateJob(job *batch.Job) field.ErrorList {
  70. // Jobs and rcs have the same name validation
  71. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  72. allErrs = append(allErrs, ValidateGeneratedSelector(job)...)
  73. allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...)
  74. return allErrs
  75. }
  76. func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  77. allErrs := validateJobSpec(spec, fldPath)
  78. if spec.Selector == nil {
  79. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  80. } else {
  81. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  82. }
  83. // Whether manually or automatically generated, the selector of the job must match the pods it will produce.
  84. if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil {
  85. labels := labels.Set(spec.Template.Labels)
  86. if !selector.Matches(labels) {
  87. allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
  88. }
  89. }
  90. return allErrs
  91. }
  92. func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList {
  93. allErrs := field.ErrorList{}
  94. if spec.Parallelism != nil {
  95. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...)
  96. }
  97. if spec.Completions != nil {
  98. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...)
  99. }
  100. if spec.ActiveDeadlineSeconds != nil {
  101. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...)
  102. }
  103. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
  104. if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
  105. spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
  106. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"),
  107. spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)}))
  108. }
  109. return allErrs
  110. }
  111. func ValidateJobStatus(status *batch.JobStatus, fldPath *field.Path) field.ErrorList {
  112. allErrs := field.ErrorList{}
  113. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Active), fldPath.Child("active"))...)
  114. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Succeeded), fldPath.Child("succeeded"))...)
  115. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Failed), fldPath.Child("failed"))...)
  116. return allErrs
  117. }
  118. func ValidateJobUpdate(job, oldJob *batch.Job) field.ErrorList {
  119. allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata"))
  120. allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, field.NewPath("spec"))...)
  121. return allErrs
  122. }
  123. func ValidateJobUpdateStatus(job, oldJob *batch.Job) field.ErrorList {
  124. allErrs := apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, field.NewPath("metadata"))
  125. allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...)
  126. return allErrs
  127. }
  128. func ValidateJobSpecUpdate(spec, oldSpec batch.JobSpec, fldPath *field.Path) field.ErrorList {
  129. allErrs := field.ErrorList{}
  130. allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...)
  131. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
  132. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
  133. allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
  134. return allErrs
  135. }
  136. func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList {
  137. allErrs := field.ErrorList{}
  138. allErrs = append(allErrs, ValidateJobStatus(&status, field.NewPath("status"))...)
  139. return allErrs
  140. }
  141. func ValidateScheduledJob(scheduledJob *batch.ScheduledJob) field.ErrorList {
  142. // ScheduledJobs and rcs have the same name validation
  143. allErrs := apivalidation.ValidateObjectMeta(&scheduledJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  144. allErrs = append(allErrs, ValidateScheduledJobSpec(&scheduledJob.Spec, field.NewPath("spec"))...)
  145. return allErrs
  146. }
  147. func ValidateScheduledJobSpec(spec *batch.ScheduledJobSpec, fldPath *field.Path) field.ErrorList {
  148. allErrs := field.ErrorList{}
  149. if len(spec.Schedule) == 0 {
  150. allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
  151. } else {
  152. allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...)
  153. }
  154. if spec.StartingDeadlineSeconds != nil {
  155. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
  156. }
  157. allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
  158. allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...)
  159. return allErrs
  160. }
  161. func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPath *field.Path) field.ErrorList {
  162. allErrs := field.ErrorList{}
  163. switch *concurrencyPolicy {
  164. case batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent:
  165. break
  166. case "":
  167. allErrs = append(allErrs, field.Required(fldPath, ""))
  168. default:
  169. validValues := []string{string(batch.AllowConcurrent), string(batch.ForbidConcurrent), string(batch.ReplaceConcurrent)}
  170. allErrs = append(allErrs, field.NotSupported(fldPath, *concurrencyPolicy, validValues))
  171. }
  172. return allErrs
  173. }
  174. func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList {
  175. allErrs := field.ErrorList{}
  176. // TODO soltysh: this should be removed when https://github.com/robfig/cron/issues/58 is fixed
  177. tmpSchedule := schedule
  178. if len(schedule) > 0 && schedule[0] != '@' {
  179. tmpSchedule = "0 " + schedule
  180. }
  181. if _, err := cron.Parse(tmpSchedule); err != nil {
  182. allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
  183. }
  184. return allErrs
  185. }
  186. func ValidateJobTemplate(job *batch.JobTemplate) field.ErrorList {
  187. // this method should be identical to ValidateJob
  188. allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
  189. allErrs = append(allErrs, ValidateJobTemplateSpec(&job.Template, field.NewPath("template"))...)
  190. return allErrs
  191. }
  192. func ValidateJobTemplateSpec(spec *batch.JobTemplateSpec, fldPath *field.Path) field.ErrorList {
  193. allErrs := validateJobSpec(&spec.Spec, fldPath.Child("spec"))
  194. // jobtemplate will always have the selector automatically generated
  195. if spec.Spec.Selector != nil {
  196. allErrs = append(allErrs, field.Invalid(fldPath.Child("spec", "selector"), spec.Spec.Selector, "`selector` will be auto-generated"))
  197. }
  198. if spec.Spec.ManualSelector != nil && *spec.Spec.ManualSelector {
  199. allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "manualSelector"), spec.Spec.ManualSelector, []string{"nil", "false"}))
  200. }
  201. return allErrs
  202. }