validation.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  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. "fmt"
  16. "strconv"
  17. apiequality "k8s.io/apimachinery/pkg/api/equality"
  18. apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
  21. "k8s.io/apimachinery/pkg/labels"
  22. "k8s.io/apimachinery/pkg/util/intstr"
  23. "k8s.io/apimachinery/pkg/util/validation"
  24. "k8s.io/apimachinery/pkg/util/validation/field"
  25. "k8s.io/kubernetes/pkg/apis/apps"
  26. api "k8s.io/kubernetes/pkg/apis/core"
  27. apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
  28. )
  29. // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.
  30. // Prefix indicates this name will be used as part of generation, in which case
  31. // trailing dashes are allowed.
  32. func ValidateStatefulSetName(name string, prefix bool) []string {
  33. // TODO: Validate that there's name for the suffix inserted by the pods.
  34. // Currently this is just "-index". In the future we may allow a user
  35. // specified list of suffixes and we need to validate the longest one.
  36. return apimachineryvalidation.NameIsDNSSubdomain(name, prefix)
  37. }
  38. // ValidatePodTemplateSpecForStatefulSet validates the given template and ensures that it is in accordance with the desired selector.
  39. func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path) field.ErrorList {
  40. allErrs := field.ErrorList{}
  41. if template == nil {
  42. allErrs = append(allErrs, field.Required(fldPath, ""))
  43. } else {
  44. if !selector.Empty() {
  45. // Verify that the StatefulSet selector matches the labels in template.
  46. labels := labels.Set(template.Labels)
  47. if !selector.Matches(labels) {
  48. allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
  49. }
  50. }
  51. // TODO: Add validation for PodSpec, currently this will check volumes, which we know will
  52. // fail. We should really check that the union of the given volumes and volumeClaims match
  53. // volume mounts in the containers.
  54. // allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...)
  55. allErrs = append(allErrs, unversionedvalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...)
  56. allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...)
  57. allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, &template.Spec, fldPath.Child("annotations"))...)
  58. }
  59. return allErrs
  60. }
  61. // ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set.
  62. func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path) field.ErrorList {
  63. allErrs := field.ErrorList{}
  64. switch spec.PodManagementPolicy {
  65. case "":
  66. allErrs = append(allErrs, field.Required(fldPath.Child("podManagementPolicy"), ""))
  67. case apps.OrderedReadyPodManagement, apps.ParallelPodManagement:
  68. default:
  69. allErrs = append(allErrs, field.Invalid(fldPath.Child("podManagementPolicy"), spec.PodManagementPolicy, fmt.Sprintf("must be '%s' or '%s'", apps.OrderedReadyPodManagement, apps.ParallelPodManagement)))
  70. }
  71. switch spec.UpdateStrategy.Type {
  72. case "":
  73. allErrs = append(allErrs, field.Required(fldPath.Child("updateStrategy"), ""))
  74. case apps.OnDeleteStatefulSetStrategyType:
  75. if spec.UpdateStrategy.RollingUpdate != nil {
  76. allErrs = append(
  77. allErrs,
  78. field.Invalid(
  79. fldPath.Child("updateStrategy").Child("rollingUpdate"),
  80. spec.UpdateStrategy.RollingUpdate,
  81. fmt.Sprintf("only allowed for updateStrategy '%s'", apps.RollingUpdateStatefulSetStrategyType)))
  82. }
  83. case apps.RollingUpdateStatefulSetStrategyType:
  84. if spec.UpdateStrategy.RollingUpdate != nil {
  85. allErrs = append(allErrs,
  86. apivalidation.ValidateNonnegativeField(
  87. int64(spec.UpdateStrategy.RollingUpdate.Partition),
  88. fldPath.Child("updateStrategy").Child("rollingUpdate").Child("partition"))...)
  89. }
  90. default:
  91. allErrs = append(allErrs,
  92. field.Invalid(fldPath.Child("updateStrategy"), spec.UpdateStrategy,
  93. fmt.Sprintf("must be '%s' or '%s'",
  94. apps.RollingUpdateStatefulSetStrategyType,
  95. apps.OnDeleteStatefulSetStrategyType)))
  96. }
  97. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
  98. if spec.Selector == nil {
  99. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  100. } else {
  101. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  102. if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  103. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for statefulset"))
  104. }
  105. }
  106. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  107. if err != nil {
  108. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, ""))
  109. } else {
  110. allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"))...)
  111. }
  112. if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
  113. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
  114. }
  115. if spec.Template.Spec.ActiveDeadlineSeconds != nil {
  116. allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in StatefulSet is not Supported"))
  117. }
  118. return allErrs
  119. }
  120. // ValidateStatefulSet validates a StatefulSet.
  121. func ValidateStatefulSet(statefulSet *apps.StatefulSet) field.ErrorList {
  122. allErrs := apivalidation.ValidateObjectMeta(&statefulSet.ObjectMeta, true, ValidateStatefulSetName, field.NewPath("metadata"))
  123. allErrs = append(allErrs, ValidateStatefulSetSpec(&statefulSet.Spec, field.NewPath("spec"))...)
  124. return allErrs
  125. }
  126. // ValidateStatefulSetUpdate tests if required fields in the StatefulSet are set.
  127. func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList {
  128. allErrs := apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))
  129. restoreReplicas := statefulSet.Spec.Replicas
  130. statefulSet.Spec.Replicas = oldStatefulSet.Spec.Replicas
  131. restoreTemplate := statefulSet.Spec.Template
  132. statefulSet.Spec.Template = oldStatefulSet.Spec.Template
  133. restoreStrategy := statefulSet.Spec.UpdateStrategy
  134. statefulSet.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy
  135. if !apiequality.Semantic.DeepEqual(statefulSet.Spec, oldStatefulSet.Spec) {
  136. allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden"))
  137. }
  138. statefulSet.Spec.Replicas = restoreReplicas
  139. statefulSet.Spec.Template = restoreTemplate
  140. statefulSet.Spec.UpdateStrategy = restoreStrategy
  141. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(statefulSet.Spec.Replicas), field.NewPath("spec", "replicas"))...)
  142. return allErrs
  143. }
  144. // ValidateStatefulSetStatus validates a StatefulSetStatus.
  145. func ValidateStatefulSetStatus(status *apps.StatefulSetStatus, fieldPath *field.Path) field.ErrorList {
  146. allErrs := field.ErrorList{}
  147. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fieldPath.Child("replicas"))...)
  148. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fieldPath.Child("readyReplicas"))...)
  149. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), fieldPath.Child("currentReplicas"))...)
  150. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fieldPath.Child("updatedReplicas"))...)
  151. if status.ObservedGeneration != nil {
  152. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.ObservedGeneration), fieldPath.Child("observedGeneration"))...)
  153. }
  154. if status.CollisionCount != nil {
  155. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fieldPath.Child("collisionCount"))...)
  156. }
  157. msg := "cannot be greater than status.replicas"
  158. if status.ReadyReplicas > status.Replicas {
  159. allErrs = append(allErrs, field.Invalid(fieldPath.Child("readyReplicas"), status.ReadyReplicas, msg))
  160. }
  161. if status.CurrentReplicas > status.Replicas {
  162. allErrs = append(allErrs, field.Invalid(fieldPath.Child("currentReplicas"), status.CurrentReplicas, msg))
  163. }
  164. if status.UpdatedReplicas > status.Replicas {
  165. allErrs = append(allErrs, field.Invalid(fieldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg))
  166. }
  167. return allErrs
  168. }
  169. // ValidateStatefulSetStatusUpdate tests if required fields in the StatefulSet are set.
  170. func ValidateStatefulSetStatusUpdate(statefulSet, oldStatefulSet *apps.StatefulSet) field.ErrorList {
  171. allErrs := field.ErrorList{}
  172. allErrs = append(allErrs, ValidateStatefulSetStatus(&statefulSet.Status, field.NewPath("status"))...)
  173. allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&statefulSet.ObjectMeta, &oldStatefulSet.ObjectMeta, field.NewPath("metadata"))...)
  174. // TODO: Validate status.
  175. if apivalidation.IsDecremented(statefulSet.Status.CollisionCount, oldStatefulSet.Status.CollisionCount) {
  176. value := int32(0)
  177. if statefulSet.Status.CollisionCount != nil {
  178. value = *statefulSet.Status.CollisionCount
  179. }
  180. allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented"))
  181. }
  182. return allErrs
  183. }
  184. // ValidateControllerRevisionName can be used to check whether the given ControllerRevision name is valid.
  185. // Prefix indicates this name will be used as part of generation, in which case
  186. // trailing dashes are allowed.
  187. var ValidateControllerRevisionName = apimachineryvalidation.NameIsDNSSubdomain
  188. // ValidateControllerRevision collects errors for the fields of state and returns those errors as an ErrorList. If the
  189. // returned list is empty, state is valid. Validation is performed to ensure that state is a valid ObjectMeta, its name
  190. // is valid, and that it doesn't exceed the MaxControllerRevisionSize.
  191. func ValidateControllerRevision(revision *apps.ControllerRevision) field.ErrorList {
  192. errs := field.ErrorList{}
  193. errs = append(errs, apivalidation.ValidateObjectMeta(&revision.ObjectMeta, true, ValidateControllerRevisionName, field.NewPath("metadata"))...)
  194. if revision.Data == nil {
  195. errs = append(errs, field.Required(field.NewPath("data"), "data is mandatory"))
  196. }
  197. errs = append(errs, apivalidation.ValidateNonnegativeField(revision.Revision, field.NewPath("revision"))...)
  198. return errs
  199. }
  200. // ValidateControllerRevisionUpdate collects errors pertaining to the mutation of an ControllerRevision Object. If the
  201. // returned ErrorList is empty the update operation is valid. Any mutation to the ControllerRevision's Data or Revision
  202. // is considered to be invalid.
  203. func ValidateControllerRevisionUpdate(newHistory, oldHistory *apps.ControllerRevision) field.ErrorList {
  204. errs := field.ErrorList{}
  205. errs = append(errs, apivalidation.ValidateObjectMetaUpdate(&newHistory.ObjectMeta, &oldHistory.ObjectMeta, field.NewPath("metadata"))...)
  206. errs = append(errs, ValidateControllerRevision(newHistory)...)
  207. errs = append(errs, apivalidation.ValidateImmutableField(newHistory.Data, oldHistory.Data, field.NewPath("data"))...)
  208. return errs
  209. }
  210. // ValidateDaemonSet tests if required fields in the DaemonSet are set.
  211. func ValidateDaemonSet(ds *apps.DaemonSet) field.ErrorList {
  212. allErrs := apivalidation.ValidateObjectMeta(&ds.ObjectMeta, true, ValidateDaemonSetName, field.NewPath("metadata"))
  213. allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"))...)
  214. return allErrs
  215. }
  216. // ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set.
  217. func ValidateDaemonSetUpdate(ds, oldDS *apps.DaemonSet) field.ErrorList {
  218. allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata"))
  219. allErrs = append(allErrs, ValidateDaemonSetSpecUpdate(&ds.Spec, &oldDS.Spec, field.NewPath("spec"))...)
  220. allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"))...)
  221. return allErrs
  222. }
  223. // ValidateDaemonSetSpecUpdate tests if an update to a DaemonSetSpec is valid.
  224. func ValidateDaemonSetSpecUpdate(newSpec, oldSpec *apps.DaemonSetSpec, fldPath *field.Path) field.ErrorList {
  225. allErrs := field.ErrorList{}
  226. // TemplateGeneration shouldn't be decremented
  227. if newSpec.TemplateGeneration < oldSpec.TemplateGeneration {
  228. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be decremented"))
  229. }
  230. // TemplateGeneration should be increased when and only when template is changed
  231. templateUpdated := !apiequality.Semantic.DeepEqual(newSpec.Template, oldSpec.Template)
  232. if newSpec.TemplateGeneration == oldSpec.TemplateGeneration && templateUpdated {
  233. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must be incremented upon template update"))
  234. } else if newSpec.TemplateGeneration > oldSpec.TemplateGeneration && !templateUpdated {
  235. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be incremented without template update"))
  236. }
  237. return allErrs
  238. }
  239. // validateDaemonSetStatus validates a DaemonSetStatus
  240. func validateDaemonSetStatus(status *apps.DaemonSetStatus, fldPath *field.Path) field.ErrorList {
  241. allErrs := field.ErrorList{}
  242. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentNumberScheduled), fldPath.Child("currentNumberScheduled"))...)
  243. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberMisscheduled), fldPath.Child("numberMisscheduled"))...)
  244. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...)
  245. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberReady), fldPath.Child("numberReady"))...)
  246. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
  247. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedNumberScheduled), fldPath.Child("updatedNumberScheduled"))...)
  248. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberAvailable), fldPath.Child("numberAvailable"))...)
  249. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...)
  250. if status.CollisionCount != nil {
  251. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
  252. }
  253. return allErrs
  254. }
  255. // ValidateDaemonSetStatusUpdate tests if required fields in the DaemonSet Status section
  256. func ValidateDaemonSetStatusUpdate(ds, oldDS *apps.DaemonSet) field.ErrorList {
  257. allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata"))
  258. allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...)
  259. if apivalidation.IsDecremented(ds.Status.CollisionCount, oldDS.Status.CollisionCount) {
  260. value := int32(0)
  261. if ds.Status.CollisionCount != nil {
  262. value = *ds.Status.CollisionCount
  263. }
  264. allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented"))
  265. }
  266. return allErrs
  267. }
  268. // ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set.
  269. func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path) field.ErrorList {
  270. allErrs := field.ErrorList{}
  271. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  272. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  273. if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) {
  274. allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
  275. }
  276. if spec.Selector != nil && len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  277. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for daemonset"))
  278. }
  279. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
  280. // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid.
  281. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, fldPath.Child("template", "spec", "volumes"))...)
  282. // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
  283. if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
  284. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
  285. }
  286. if spec.Template.Spec.ActiveDeadlineSeconds != nil {
  287. allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in DaemonSet is not Supported"))
  288. }
  289. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  290. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...)
  291. allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
  292. if spec.RevisionHistoryLimit != nil {
  293. // zero is a valid RevisionHistoryLimit
  294. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...)
  295. }
  296. return allErrs
  297. }
  298. // ValidateRollingUpdateDaemonSet validates a given RollingUpdateDaemonSet.
  299. func ValidateRollingUpdateDaemonSet(rollingUpdate *apps.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList {
  300. allErrs := field.ErrorList{}
  301. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  302. if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 {
  303. // MaxUnavailable cannot be 0.
  304. allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "cannot be 0"))
  305. }
  306. // Validate that MaxUnavailable is not more than 100%.
  307. allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  308. return allErrs
  309. }
  310. // ValidateDaemonSetUpdateStrategy validates a given DaemonSetUpdateStrategy.
  311. func ValidateDaemonSetUpdateStrategy(strategy *apps.DaemonSetUpdateStrategy, fldPath *field.Path) field.ErrorList {
  312. allErrs := field.ErrorList{}
  313. switch strategy.Type {
  314. case apps.OnDeleteDaemonSetStrategyType:
  315. case apps.RollingUpdateDaemonSetStrategyType:
  316. // Make sure RollingUpdate field isn't nil.
  317. if strategy.RollingUpdate == nil {
  318. allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), ""))
  319. return allErrs
  320. }
  321. allErrs = append(allErrs, ValidateRollingUpdateDaemonSet(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
  322. default:
  323. validValues := []string{string(apps.RollingUpdateDaemonSetStrategyType), string(apps.OnDeleteDaemonSetStrategyType)}
  324. allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues))
  325. }
  326. return allErrs
  327. }
  328. // ValidateDaemonSetName can be used to check whether the given daemon set name is valid.
  329. // Prefix indicates this name will be used as part of generation, in which case
  330. // trailing dashes are allowed.
  331. var ValidateDaemonSetName = apimachineryvalidation.NameIsDNSSubdomain
  332. // ValidateDeploymentName validates that the given name can be used as a deployment name.
  333. var ValidateDeploymentName = apimachineryvalidation.NameIsDNSSubdomain
  334. // ValidatePositiveIntOrPercent tests if a given value is a valid int or
  335. // percentage.
  336. func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList {
  337. allErrs := field.ErrorList{}
  338. switch intOrPercent.Type {
  339. case intstr.String:
  340. for _, msg := range validation.IsValidPercent(intOrPercent.StrVal) {
  341. allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, msg))
  342. }
  343. case intstr.Int:
  344. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...)
  345. default:
  346. allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')"))
  347. }
  348. return allErrs
  349. }
  350. func getPercentValue(intOrStringValue intstr.IntOrString) (int, bool) {
  351. if intOrStringValue.Type != intstr.String {
  352. return 0, false
  353. }
  354. if len(validation.IsValidPercent(intOrStringValue.StrVal)) != 0 {
  355. return 0, false
  356. }
  357. value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1])
  358. return value, true
  359. }
  360. func getIntOrPercentValue(intOrStringValue intstr.IntOrString) int {
  361. value, isPercent := getPercentValue(intOrStringValue)
  362. if isPercent {
  363. return value
  364. }
  365. return intOrStringValue.IntValue()
  366. }
  367. // IsNotMoreThan100Percent tests is a value can be represented as a percentage
  368. // and if this value is not more than 100%.
  369. func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *field.Path) field.ErrorList {
  370. allErrs := field.ErrorList{}
  371. value, isPercent := getPercentValue(intOrStringValue)
  372. if !isPercent || value <= 100 {
  373. return nil
  374. }
  375. allErrs = append(allErrs, field.Invalid(fldPath, intOrStringValue, "must not be greater than 100%"))
  376. return allErrs
  377. }
  378. // ValidateRollingUpdateDeployment validates a given RollingUpdateDeployment.
  379. func ValidateRollingUpdateDeployment(rollingUpdate *apps.RollingUpdateDeployment, fldPath *field.Path) field.ErrorList {
  380. allErrs := field.ErrorList{}
  381. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  382. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
  383. if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 {
  384. // Both MaxSurge and MaxUnavailable cannot be zero.
  385. allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "may not be 0 when `maxSurge` is 0"))
  386. }
  387. // Validate that MaxUnavailable is not more than 100%.
  388. allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  389. return allErrs
  390. }
  391. // ValidateDeploymentStrategy validates given DeploymentStrategy.
  392. func ValidateDeploymentStrategy(strategy *apps.DeploymentStrategy, fldPath *field.Path) field.ErrorList {
  393. allErrs := field.ErrorList{}
  394. switch strategy.Type {
  395. case apps.RecreateDeploymentStrategyType:
  396. if strategy.RollingUpdate != nil {
  397. allErrs = append(allErrs, field.Forbidden(fldPath.Child("rollingUpdate"), "may not be specified when strategy `type` is '"+string(apps.RecreateDeploymentStrategyType+"'")))
  398. }
  399. case apps.RollingUpdateDeploymentStrategyType:
  400. // This should never happen since it's set and checked in defaults.go
  401. if strategy.RollingUpdate == nil {
  402. allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "this should be defaulted and never be nil"))
  403. } else {
  404. allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
  405. }
  406. default:
  407. validValues := []string{string(apps.RecreateDeploymentStrategyType), string(apps.RollingUpdateDeploymentStrategyType)}
  408. allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues))
  409. }
  410. return allErrs
  411. }
  412. // ValidateRollback validates given RollbackConfig.
  413. func ValidateRollback(rollback *apps.RollbackConfig, fldPath *field.Path) field.ErrorList {
  414. allErrs := field.ErrorList{}
  415. v := rollback.Revision
  416. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(v), fldPath.Child("version"))...)
  417. return allErrs
  418. }
  419. // ValidateDeploymentSpec validates given deployment spec.
  420. func ValidateDeploymentSpec(spec *apps.DeploymentSpec, fldPath *field.Path) field.ErrorList {
  421. allErrs := field.ErrorList{}
  422. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
  423. if spec.Selector == nil {
  424. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  425. } else {
  426. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  427. if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  428. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
  429. }
  430. }
  431. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  432. if err != nil {
  433. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector"))
  434. } else {
  435. allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...)
  436. }
  437. allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...)
  438. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  439. if spec.RevisionHistoryLimit != nil {
  440. // zero is a valid RevisionHistoryLimit
  441. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...)
  442. }
  443. if spec.RollbackTo != nil {
  444. allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...)
  445. }
  446. if spec.ProgressDeadlineSeconds != nil {
  447. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ProgressDeadlineSeconds), fldPath.Child("progressDeadlineSeconds"))...)
  448. if *spec.ProgressDeadlineSeconds <= spec.MinReadySeconds {
  449. allErrs = append(allErrs, field.Invalid(fldPath.Child("progressDeadlineSeconds"), spec.ProgressDeadlineSeconds, "must be greater than minReadySeconds"))
  450. }
  451. }
  452. return allErrs
  453. }
  454. // ValidateDeploymentStatus validates given deployment status.
  455. func ValidateDeploymentStatus(status *apps.DeploymentStatus, fldPath *field.Path) field.ErrorList {
  456. allErrs := field.ErrorList{}
  457. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
  458. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...)
  459. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fldPath.Child("updatedReplicas"))...)
  460. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
  461. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
  462. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
  463. if status.CollisionCount != nil {
  464. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
  465. }
  466. msg := "cannot be greater than status.replicas"
  467. if status.UpdatedReplicas > status.Replicas {
  468. allErrs = append(allErrs, field.Invalid(fldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg))
  469. }
  470. if status.ReadyReplicas > status.Replicas {
  471. allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg))
  472. }
  473. if status.AvailableReplicas > status.Replicas {
  474. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
  475. }
  476. if status.AvailableReplicas > status.ReadyReplicas {
  477. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
  478. }
  479. return allErrs
  480. }
  481. // ValidateDeploymentUpdate tests if an update to a Deployment is valid.
  482. func ValidateDeploymentUpdate(update, old *apps.Deployment) field.ErrorList {
  483. allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
  484. allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, field.NewPath("spec"))...)
  485. return allErrs
  486. }
  487. // ValidateDeploymentStatusUpdate tests if a an update to a Deployment status
  488. // is valid.
  489. func ValidateDeploymentStatusUpdate(update, old *apps.Deployment) field.ErrorList {
  490. allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
  491. fldPath := field.NewPath("status")
  492. allErrs = append(allErrs, ValidateDeploymentStatus(&update.Status, fldPath)...)
  493. if apivalidation.IsDecremented(update.Status.CollisionCount, old.Status.CollisionCount) {
  494. value := int32(0)
  495. if update.Status.CollisionCount != nil {
  496. value = *update.Status.CollisionCount
  497. }
  498. allErrs = append(allErrs, field.Invalid(fldPath.Child("collisionCount"), value, "cannot be decremented"))
  499. }
  500. return allErrs
  501. }
  502. // ValidateDeployment validates a given Deployment.
  503. func ValidateDeployment(obj *apps.Deployment) field.ErrorList {
  504. allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata"))
  505. allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, field.NewPath("spec"))...)
  506. return allErrs
  507. }
  508. // ValidateDeploymentRollback validates a given DeploymentRollback.
  509. func ValidateDeploymentRollback(obj *apps.DeploymentRollback) field.ErrorList {
  510. allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations"))
  511. if len(obj.Name) == 0 {
  512. allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required"))
  513. }
  514. allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...)
  515. return allErrs
  516. }
  517. // ValidateReplicaSetName can be used to check whether the given ReplicaSet
  518. // name is valid.
  519. // Prefix indicates this name will be used as part of generation, in which case
  520. // trailing dashes are allowed.
  521. var ValidateReplicaSetName = apimachineryvalidation.NameIsDNSSubdomain
  522. // ValidateReplicaSet tests if required fields in the ReplicaSet are set.
  523. func ValidateReplicaSet(rs *apps.ReplicaSet) field.ErrorList {
  524. allErrs := apivalidation.ValidateObjectMeta(&rs.ObjectMeta, true, ValidateReplicaSetName, field.NewPath("metadata"))
  525. allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, field.NewPath("spec"))...)
  526. return allErrs
  527. }
  528. // ValidateReplicaSetUpdate tests if required fields in the ReplicaSet are set.
  529. func ValidateReplicaSetUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList {
  530. allErrs := field.ErrorList{}
  531. allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...)
  532. allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, field.NewPath("spec"))...)
  533. return allErrs
  534. }
  535. // ValidateReplicaSetStatusUpdate tests if required fields in the ReplicaSet are set.
  536. func ValidateReplicaSetStatusUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList {
  537. allErrs := field.ErrorList{}
  538. allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...)
  539. allErrs = append(allErrs, ValidateReplicaSetStatus(rs.Status, field.NewPath("status"))...)
  540. return allErrs
  541. }
  542. // ValidateReplicaSetStatus validates a given ReplicaSetStatus.
  543. func ValidateReplicaSetStatus(status apps.ReplicaSetStatus, fldPath *field.Path) field.ErrorList {
  544. allErrs := field.ErrorList{}
  545. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...)
  546. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.FullyLabeledReplicas), fldPath.Child("fullyLabeledReplicas"))...)
  547. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
  548. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
  549. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...)
  550. msg := "cannot be greater than status.replicas"
  551. if status.FullyLabeledReplicas > status.Replicas {
  552. allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg))
  553. }
  554. if status.ReadyReplicas > status.Replicas {
  555. allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg))
  556. }
  557. if status.AvailableReplicas > status.Replicas {
  558. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
  559. }
  560. if status.AvailableReplicas > status.ReadyReplicas {
  561. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
  562. }
  563. return allErrs
  564. }
  565. // ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set.
  566. func ValidateReplicaSetSpec(spec *apps.ReplicaSetSpec, fldPath *field.Path) field.ErrorList {
  567. allErrs := field.ErrorList{}
  568. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
  569. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  570. if spec.Selector == nil {
  571. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  572. } else {
  573. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  574. if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  575. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
  576. }
  577. }
  578. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  579. if err != nil {
  580. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector"))
  581. } else {
  582. allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...)
  583. }
  584. return allErrs
  585. }
  586. // ValidatePodTemplateSpecForReplicaSet validates the given template and ensures that it is in accordance with the desired selector and replicas.
  587. func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selector labels.Selector, replicas int32, fldPath *field.Path) field.ErrorList {
  588. allErrs := field.ErrorList{}
  589. if template == nil {
  590. allErrs = append(allErrs, field.Required(fldPath, ""))
  591. } else {
  592. if !selector.Empty() {
  593. // Verify that the ReplicaSet selector matches the labels in template.
  594. labels := labels.Set(template.Labels)
  595. if !selector.Matches(labels) {
  596. allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
  597. }
  598. }
  599. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...)
  600. if replicas > 1 {
  601. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(template.Spec.Volumes, fldPath.Child("spec", "volumes"))...)
  602. }
  603. // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
  604. if template.Spec.RestartPolicy != api.RestartPolicyAlways {
  605. allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
  606. }
  607. if template.Spec.ActiveDeadlineSeconds != nil {
  608. allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicaSet is not Supported"))
  609. }
  610. }
  611. return allErrs
  612. }