validation.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  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. func ValidateDaemonSetSpecUpdate(newSpec, oldSpec *apps.DaemonSetSpec, fldPath *field.Path) field.ErrorList {
  224. allErrs := field.ErrorList{}
  225. // TemplateGeneration shouldn't be decremented
  226. if newSpec.TemplateGeneration < oldSpec.TemplateGeneration {
  227. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be decremented"))
  228. }
  229. // TemplateGeneration should be increased when and only when template is changed
  230. templateUpdated := !apiequality.Semantic.DeepEqual(newSpec.Template, oldSpec.Template)
  231. if newSpec.TemplateGeneration == oldSpec.TemplateGeneration && templateUpdated {
  232. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must be incremented upon template update"))
  233. } else if newSpec.TemplateGeneration > oldSpec.TemplateGeneration && !templateUpdated {
  234. allErrs = append(allErrs, field.Invalid(fldPath.Child("templateGeneration"), newSpec.TemplateGeneration, "must not be incremented without template update"))
  235. }
  236. return allErrs
  237. }
  238. // validateDaemonSetStatus validates a DaemonSetStatus
  239. func validateDaemonSetStatus(status *apps.DaemonSetStatus, fldPath *field.Path) field.ErrorList {
  240. allErrs := field.ErrorList{}
  241. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentNumberScheduled), fldPath.Child("currentNumberScheduled"))...)
  242. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberMisscheduled), fldPath.Child("numberMisscheduled"))...)
  243. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...)
  244. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberReady), fldPath.Child("numberReady"))...)
  245. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
  246. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedNumberScheduled), fldPath.Child("updatedNumberScheduled"))...)
  247. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberAvailable), fldPath.Child("numberAvailable"))...)
  248. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.NumberUnavailable), fldPath.Child("numberUnavailable"))...)
  249. if status.CollisionCount != nil {
  250. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
  251. }
  252. return allErrs
  253. }
  254. // ValidateDaemonSetStatusUpdate validates tests if required fields in the DaemonSet Status section
  255. func ValidateDaemonSetStatusUpdate(ds, oldDS *apps.DaemonSet) field.ErrorList {
  256. allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata"))
  257. allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...)
  258. if apivalidation.IsDecremented(ds.Status.CollisionCount, oldDS.Status.CollisionCount) {
  259. value := int32(0)
  260. if ds.Status.CollisionCount != nil {
  261. value = *ds.Status.CollisionCount
  262. }
  263. allErrs = append(allErrs, field.Invalid(field.NewPath("status").Child("collisionCount"), value, "cannot be decremented"))
  264. }
  265. return allErrs
  266. }
  267. // ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set.
  268. func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path) field.ErrorList {
  269. allErrs := field.ErrorList{}
  270. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  271. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  272. if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) {
  273. allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`"))
  274. }
  275. if spec.Selector != nil && len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  276. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for daemonset"))
  277. }
  278. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
  279. // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid.
  280. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, fldPath.Child("template", "spec", "volumes"))...)
  281. // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
  282. if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
  283. allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
  284. }
  285. if spec.Template.Spec.ActiveDeadlineSeconds != nil {
  286. allErrs = append(allErrs, field.Forbidden(fldPath.Child("template", "spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in DaemonSet is not Supported"))
  287. }
  288. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  289. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.TemplateGeneration), fldPath.Child("templateGeneration"))...)
  290. allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
  291. if spec.RevisionHistoryLimit != nil {
  292. // zero is a valid RevisionHistoryLimit
  293. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...)
  294. }
  295. return allErrs
  296. }
  297. func ValidateRollingUpdateDaemonSet(rollingUpdate *apps.RollingUpdateDaemonSet, fldPath *field.Path) field.ErrorList {
  298. allErrs := field.ErrorList{}
  299. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  300. if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 {
  301. // MaxUnavailable cannot be 0.
  302. allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "cannot be 0"))
  303. }
  304. // Validate that MaxUnavailable is not more than 100%.
  305. allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  306. return allErrs
  307. }
  308. func ValidateDaemonSetUpdateStrategy(strategy *apps.DaemonSetUpdateStrategy, fldPath *field.Path) field.ErrorList {
  309. allErrs := field.ErrorList{}
  310. switch strategy.Type {
  311. case apps.OnDeleteDaemonSetStrategyType:
  312. case apps.RollingUpdateDaemonSetStrategyType:
  313. // Make sure RollingUpdate field isn't nil.
  314. if strategy.RollingUpdate == nil {
  315. allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), ""))
  316. return allErrs
  317. }
  318. allErrs = append(allErrs, ValidateRollingUpdateDaemonSet(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
  319. default:
  320. validValues := []string{string(apps.RollingUpdateDaemonSetStrategyType), string(apps.OnDeleteDaemonSetStrategyType)}
  321. allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues))
  322. }
  323. return allErrs
  324. }
  325. // ValidateDaemonSetName can be used to check whether the given daemon set name is valid.
  326. // Prefix indicates this name will be used as part of generation, in which case
  327. // trailing dashes are allowed.
  328. var ValidateDaemonSetName = apimachineryvalidation.NameIsDNSSubdomain
  329. // ValidateDeploymentName validates that the given name can be used as a deployment name.
  330. var ValidateDeploymentName = apimachineryvalidation.NameIsDNSSubdomain
  331. func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList {
  332. allErrs := field.ErrorList{}
  333. switch intOrPercent.Type {
  334. case intstr.String:
  335. for _, msg := range validation.IsValidPercent(intOrPercent.StrVal) {
  336. allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, msg))
  337. }
  338. case intstr.Int:
  339. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...)
  340. default:
  341. allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')"))
  342. }
  343. return allErrs
  344. }
  345. func getPercentValue(intOrStringValue intstr.IntOrString) (int, bool) {
  346. if intOrStringValue.Type != intstr.String {
  347. return 0, false
  348. }
  349. if len(validation.IsValidPercent(intOrStringValue.StrVal)) != 0 {
  350. return 0, false
  351. }
  352. value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1])
  353. return value, true
  354. }
  355. func getIntOrPercentValue(intOrStringValue intstr.IntOrString) int {
  356. value, isPercent := getPercentValue(intOrStringValue)
  357. if isPercent {
  358. return value
  359. }
  360. return intOrStringValue.IntValue()
  361. }
  362. func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *field.Path) field.ErrorList {
  363. allErrs := field.ErrorList{}
  364. value, isPercent := getPercentValue(intOrStringValue)
  365. if !isPercent || value <= 100 {
  366. return nil
  367. }
  368. allErrs = append(allErrs, field.Invalid(fldPath, intOrStringValue, "must not be greater than 100%"))
  369. return allErrs
  370. }
  371. func ValidateRollingUpdateDeployment(rollingUpdate *apps.RollingUpdateDeployment, fldPath *field.Path) field.ErrorList {
  372. allErrs := field.ErrorList{}
  373. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  374. allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
  375. if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 {
  376. // Both MaxSurge and MaxUnavailable cannot be zero.
  377. allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "may not be 0 when `maxSurge` is 0"))
  378. }
  379. // Validate that MaxUnavailable is not more than 100%.
  380. allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
  381. return allErrs
  382. }
  383. func ValidateDeploymentStrategy(strategy *apps.DeploymentStrategy, fldPath *field.Path) field.ErrorList {
  384. allErrs := field.ErrorList{}
  385. switch strategy.Type {
  386. case apps.RecreateDeploymentStrategyType:
  387. if strategy.RollingUpdate != nil {
  388. allErrs = append(allErrs, field.Forbidden(fldPath.Child("rollingUpdate"), "may not be specified when strategy `type` is '"+string(apps.RecreateDeploymentStrategyType+"'")))
  389. }
  390. case apps.RollingUpdateDeploymentStrategyType:
  391. // This should never happen since it's set and checked in defaults.go
  392. if strategy.RollingUpdate == nil {
  393. allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "this should be defaulted and never be nil"))
  394. } else {
  395. allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
  396. }
  397. default:
  398. validValues := []string{string(apps.RecreateDeploymentStrategyType), string(apps.RollingUpdateDeploymentStrategyType)}
  399. allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues))
  400. }
  401. return allErrs
  402. }
  403. func ValidateRollback(rollback *apps.RollbackConfig, fldPath *field.Path) field.ErrorList {
  404. allErrs := field.ErrorList{}
  405. v := rollback.Revision
  406. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(v), fldPath.Child("version"))...)
  407. return allErrs
  408. }
  409. // ValidateDeploymentSpec validates given deployment spec.
  410. func ValidateDeploymentSpec(spec *apps.DeploymentSpec, fldPath *field.Path) field.ErrorList {
  411. allErrs := field.ErrorList{}
  412. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
  413. if spec.Selector == nil {
  414. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  415. } else {
  416. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  417. if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  418. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
  419. }
  420. }
  421. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  422. if err != nil {
  423. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector"))
  424. } else {
  425. allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...)
  426. }
  427. allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...)
  428. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  429. if spec.RevisionHistoryLimit != nil {
  430. // zero is a valid RevisionHistoryLimit
  431. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...)
  432. }
  433. if spec.RollbackTo != nil {
  434. allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...)
  435. }
  436. if spec.ProgressDeadlineSeconds != nil {
  437. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ProgressDeadlineSeconds), fldPath.Child("progressDeadlineSeconds"))...)
  438. if *spec.ProgressDeadlineSeconds <= spec.MinReadySeconds {
  439. allErrs = append(allErrs, field.Invalid(fldPath.Child("progressDeadlineSeconds"), spec.ProgressDeadlineSeconds, "must be greater than minReadySeconds"))
  440. }
  441. }
  442. return allErrs
  443. }
  444. // ValidateDeploymentStatus validates given deployment status.
  445. func ValidateDeploymentStatus(status *apps.DeploymentStatus, fldPath *field.Path) field.ErrorList {
  446. allErrs := field.ErrorList{}
  447. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...)
  448. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...)
  449. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fldPath.Child("updatedReplicas"))...)
  450. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
  451. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
  452. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
  453. if status.CollisionCount != nil {
  454. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
  455. }
  456. msg := "cannot be greater than status.replicas"
  457. if status.UpdatedReplicas > status.Replicas {
  458. allErrs = append(allErrs, field.Invalid(fldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg))
  459. }
  460. if status.ReadyReplicas > status.Replicas {
  461. allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg))
  462. }
  463. if status.AvailableReplicas > status.Replicas {
  464. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
  465. }
  466. if status.AvailableReplicas > status.ReadyReplicas {
  467. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
  468. }
  469. return allErrs
  470. }
  471. func ValidateDeploymentUpdate(update, old *apps.Deployment) field.ErrorList {
  472. allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
  473. allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, field.NewPath("spec"))...)
  474. return allErrs
  475. }
  476. func ValidateDeploymentStatusUpdate(update, old *apps.Deployment) field.ErrorList {
  477. allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
  478. fldPath := field.NewPath("status")
  479. allErrs = append(allErrs, ValidateDeploymentStatus(&update.Status, fldPath)...)
  480. if apivalidation.IsDecremented(update.Status.CollisionCount, old.Status.CollisionCount) {
  481. value := int32(0)
  482. if update.Status.CollisionCount != nil {
  483. value = *update.Status.CollisionCount
  484. }
  485. allErrs = append(allErrs, field.Invalid(fldPath.Child("collisionCount"), value, "cannot be decremented"))
  486. }
  487. return allErrs
  488. }
  489. func ValidateDeployment(obj *apps.Deployment) field.ErrorList {
  490. allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata"))
  491. allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, field.NewPath("spec"))...)
  492. return allErrs
  493. }
  494. func ValidateDeploymentRollback(obj *apps.DeploymentRollback) field.ErrorList {
  495. allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations"))
  496. if len(obj.Name) == 0 {
  497. allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required"))
  498. }
  499. allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...)
  500. return allErrs
  501. }
  502. // ValidateReplicaSetName can be used to check whether the given ReplicaSet
  503. // name is valid.
  504. // Prefix indicates this name will be used as part of generation, in which case
  505. // trailing dashes are allowed.
  506. var ValidateReplicaSetName = apimachineryvalidation.NameIsDNSSubdomain
  507. // ValidateReplicaSet tests if required fields in the ReplicaSet are set.
  508. func ValidateReplicaSet(rs *apps.ReplicaSet) field.ErrorList {
  509. allErrs := apivalidation.ValidateObjectMeta(&rs.ObjectMeta, true, ValidateReplicaSetName, field.NewPath("metadata"))
  510. allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, field.NewPath("spec"))...)
  511. return allErrs
  512. }
  513. // ValidateReplicaSetUpdate tests if required fields in the ReplicaSet are set.
  514. func ValidateReplicaSetUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList {
  515. allErrs := field.ErrorList{}
  516. allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...)
  517. allErrs = append(allErrs, ValidateReplicaSetSpec(&rs.Spec, field.NewPath("spec"))...)
  518. return allErrs
  519. }
  520. // ValidateReplicaSetStatusUpdate tests if required fields in the ReplicaSet are set.
  521. func ValidateReplicaSetStatusUpdate(rs, oldRs *apps.ReplicaSet) field.ErrorList {
  522. allErrs := field.ErrorList{}
  523. allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...)
  524. allErrs = append(allErrs, ValidateReplicaSetStatus(rs.Status, field.NewPath("status"))...)
  525. return allErrs
  526. }
  527. func ValidateReplicaSetStatus(status apps.ReplicaSetStatus, fldPath *field.Path) field.ErrorList {
  528. allErrs := field.ErrorList{}
  529. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...)
  530. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.FullyLabeledReplicas), fldPath.Child("fullyLabeledReplicas"))...)
  531. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
  532. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
  533. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...)
  534. msg := "cannot be greater than status.replicas"
  535. if status.FullyLabeledReplicas > status.Replicas {
  536. allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg))
  537. }
  538. if status.ReadyReplicas > status.Replicas {
  539. allErrs = append(allErrs, field.Invalid(fldPath.Child("readyReplicas"), status.ReadyReplicas, msg))
  540. }
  541. if status.AvailableReplicas > status.Replicas {
  542. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
  543. }
  544. if status.AvailableReplicas > status.ReadyReplicas {
  545. allErrs = append(allErrs, field.Invalid(fldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
  546. }
  547. return allErrs
  548. }
  549. // ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set.
  550. func ValidateReplicaSetSpec(spec *apps.ReplicaSetSpec, fldPath *field.Path) field.ErrorList {
  551. allErrs := field.ErrorList{}
  552. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
  553. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
  554. if spec.Selector == nil {
  555. allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
  556. } else {
  557. allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
  558. if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
  559. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
  560. }
  561. }
  562. selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
  563. if err != nil {
  564. allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector"))
  565. } else {
  566. allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...)
  567. }
  568. return allErrs
  569. }
  570. // ValidatePodTemplateSpecForReplicaSet validates the given template and ensures that it is in accordance with the desired selector and replicas.
  571. func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selector labels.Selector, replicas int32, fldPath *field.Path) field.ErrorList {
  572. allErrs := field.ErrorList{}
  573. if template == nil {
  574. allErrs = append(allErrs, field.Required(fldPath, ""))
  575. } else {
  576. if !selector.Empty() {
  577. // Verify that the ReplicaSet selector matches the labels in template.
  578. labels := labels.Set(template.Labels)
  579. if !selector.Matches(labels) {
  580. allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
  581. }
  582. }
  583. allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...)
  584. if replicas > 1 {
  585. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(template.Spec.Volumes, fldPath.Child("spec", "volumes"))...)
  586. }
  587. // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
  588. if template.Spec.RestartPolicy != api.RestartPolicyAlways {
  589. allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
  590. }
  591. if template.Spec.ActiveDeadlineSeconds != nil {
  592. allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicaSet is not Supported"))
  593. }
  594. }
  595. return allErrs
  596. }