validation.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. Copyright 2015 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. "reflect"
  17. "strings"
  18. apiequality "k8s.io/apimachinery/pkg/api/equality"
  19. "k8s.io/apimachinery/pkg/util/sets"
  20. "k8s.io/apimachinery/pkg/util/validation"
  21. "k8s.io/apimachinery/pkg/util/validation/field"
  22. api "k8s.io/kubernetes/pkg/apis/core"
  23. "k8s.io/kubernetes/pkg/apis/core/helper"
  24. apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
  25. storagevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
  26. "k8s.io/kubernetes/pkg/apis/storage"
  27. utilfeature "k8s.io/apiserver/pkg/util/feature"
  28. "k8s.io/kubernetes/pkg/features"
  29. )
  30. const (
  31. maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB
  32. maxProvisionerParameterLen = 512
  33. maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
  34. maxVolumeErrorMessageSize = 1024
  35. csiNodeIDMaxLength = 128
  36. )
  37. // ValidateStorageClass validates a StorageClass.
  38. func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList {
  39. allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata"))
  40. allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...)
  41. allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...)
  42. allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...)
  43. allErrs = append(allErrs, validateVolumeBindingMode(storageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
  44. allErrs = append(allErrs, validateAllowedTopologies(storageClass.AllowedTopologies, field.NewPath("allowedTopologies"))...)
  45. return allErrs
  46. }
  47. // ValidateStorageClassUpdate tests if an update to StorageClass is valid.
  48. func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageClass) field.ErrorList {
  49. allErrs := apivalidation.ValidateObjectMetaUpdate(&storageClass.ObjectMeta, &oldStorageClass.ObjectMeta, field.NewPath("metadata"))
  50. if !reflect.DeepEqual(oldStorageClass.Parameters, storageClass.Parameters) {
  51. allErrs = append(allErrs, field.Forbidden(field.NewPath("parameters"), "updates to parameters are forbidden."))
  52. }
  53. if storageClass.Provisioner != oldStorageClass.Provisioner {
  54. allErrs = append(allErrs, field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden."))
  55. }
  56. if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy {
  57. allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden."))
  58. }
  59. allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
  60. return allErrs
  61. }
  62. // validateProvisioner tests if provisioner is a valid qualified name.
  63. func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList {
  64. allErrs := field.ErrorList{}
  65. if len(provisioner) == 0 {
  66. allErrs = append(allErrs, field.Required(fldPath, provisioner))
  67. }
  68. if len(provisioner) > 0 {
  69. for _, msg := range validation.IsQualifiedName(strings.ToLower(provisioner)) {
  70. allErrs = append(allErrs, field.Invalid(fldPath, provisioner, msg))
  71. }
  72. }
  73. return allErrs
  74. }
  75. // validateParameters tests that keys are qualified names and that provisionerParameter are < 256kB.
  76. func validateParameters(params map[string]string, fldPath *field.Path) field.ErrorList {
  77. var totalSize int64
  78. allErrs := field.ErrorList{}
  79. if len(params) > maxProvisionerParameterLen {
  80. allErrs = append(allErrs, field.TooLong(fldPath, "Provisioner Parameters exceeded max allowed", maxProvisionerParameterLen))
  81. return allErrs
  82. }
  83. for k, v := range params {
  84. if len(k) < 1 {
  85. allErrs = append(allErrs, field.Invalid(fldPath, k, "field can not be empty."))
  86. }
  87. totalSize += (int64)(len(k)) + (int64)(len(v))
  88. }
  89. if totalSize > maxProvisionerParameterSize {
  90. allErrs = append(allErrs, field.TooLong(fldPath, "", maxProvisionerParameterSize))
  91. }
  92. return allErrs
  93. }
  94. var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRetain))
  95. // validateReclaimPolicy tests that the reclaim policy is one of the supported. It is up to the volume plugin to reject
  96. // provisioning for storage classes with impossible reclaim policies, e.g. EBS is not Recyclable
  97. func validateReclaimPolicy(reclaimPolicy *api.PersistentVolumeReclaimPolicy, fldPath *field.Path) field.ErrorList {
  98. allErrs := field.ErrorList{}
  99. if len(string(*reclaimPolicy)) > 0 {
  100. if !supportedReclaimPolicy.Has(string(*reclaimPolicy)) {
  101. allErrs = append(allErrs, field.NotSupported(fldPath, reclaimPolicy, supportedReclaimPolicy.List()))
  102. }
  103. }
  104. return allErrs
  105. }
  106. // ValidateVolumeAttachment validates a VolumeAttachment. This function is common for v1 and v1beta1 objects,
  107. func ValidateVolumeAttachment(volumeAttachment *storage.VolumeAttachment) field.ErrorList {
  108. allErrs := apivalidation.ValidateObjectMeta(&volumeAttachment.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata"))
  109. allErrs = append(allErrs, validateVolumeAttachmentSpec(&volumeAttachment.Spec, field.NewPath("spec"))...)
  110. allErrs = append(allErrs, validateVolumeAttachmentStatus(&volumeAttachment.Status, field.NewPath("status"))...)
  111. return allErrs
  112. }
  113. // ValidateVolumeAttachmentV1 validates a v1/VolumeAttachment. It contains only extra checks missing in
  114. // ValidateVolumeAttachment.
  115. func ValidateVolumeAttachmentV1(volumeAttachment *storage.VolumeAttachment) field.ErrorList {
  116. allErrs := apivalidation.ValidateCSIDriverName(volumeAttachment.Spec.Attacher, field.NewPath("spec.attacher"))
  117. if volumeAttachment.Spec.Source.PersistentVolumeName != nil {
  118. pvName := *volumeAttachment.Spec.Source.PersistentVolumeName
  119. for _, msg := range apivalidation.ValidatePersistentVolumeName(pvName, false) {
  120. allErrs = append(allErrs, field.Invalid(field.NewPath("spec.source.persistentVolumeName"), pvName, msg))
  121. }
  122. }
  123. return allErrs
  124. }
  125. // ValidateVolumeAttachmentSpec tests that the specified VolumeAttachmentSpec
  126. // has valid data.
  127. func validateVolumeAttachmentSpec(
  128. spec *storage.VolumeAttachmentSpec, fldPath *field.Path) field.ErrorList {
  129. allErrs := field.ErrorList{}
  130. allErrs = append(allErrs, validateAttacher(spec.Attacher, fldPath.Child("attacher"))...)
  131. allErrs = append(allErrs, validateVolumeAttachmentSource(&spec.Source, fldPath.Child("source"))...)
  132. allErrs = append(allErrs, validateNodeName(spec.NodeName, fldPath.Child("nodeName"))...)
  133. return allErrs
  134. }
  135. // validateAttacher tests if attacher is a valid qualified name.
  136. func validateAttacher(attacher string, fldPath *field.Path) field.ErrorList {
  137. allErrs := field.ErrorList{}
  138. if len(attacher) == 0 {
  139. allErrs = append(allErrs, field.Required(fldPath, attacher))
  140. }
  141. return allErrs
  142. }
  143. // validateSource tests if the source is valid for VolumeAttachment.
  144. func validateVolumeAttachmentSource(source *storage.VolumeAttachmentSource, fldPath *field.Path) field.ErrorList {
  145. allErrs := field.ErrorList{}
  146. switch {
  147. case source.InlineVolumeSpec == nil && source.PersistentVolumeName == nil:
  148. if utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) {
  149. allErrs = append(allErrs, field.Required(fldPath, "must specify exactly one of inlineVolumeSpec and persistentVolumeName"))
  150. } else {
  151. allErrs = append(allErrs, field.Required(fldPath, "must specify persistentVolumeName when CSIMigration feature is disabled"))
  152. }
  153. case source.InlineVolumeSpec != nil && source.PersistentVolumeName != nil:
  154. allErrs = append(allErrs, field.Forbidden(fldPath, "must specify exactly one of inlineVolumeSpec and persistentVolumeName"))
  155. case source.PersistentVolumeName != nil:
  156. if len(*source.PersistentVolumeName) == 0 {
  157. // Invalid err
  158. allErrs = append(allErrs, field.Required(fldPath.Child("persistentVolumeName"), "must specify non empty persistentVolumeName"))
  159. }
  160. case source.InlineVolumeSpec != nil:
  161. allErrs = append(allErrs, storagevalidation.ValidatePersistentVolumeSpec(source.InlineVolumeSpec, "", true, fldPath.Child("inlineVolumeSpec"))...)
  162. }
  163. return allErrs
  164. }
  165. // validateNodeName tests if the nodeName is valid for VolumeAttachment.
  166. func validateNodeName(nodeName string, fldPath *field.Path) field.ErrorList {
  167. allErrs := field.ErrorList{}
  168. for _, msg := range apivalidation.ValidateNodeName(nodeName, false /* prefix */) {
  169. allErrs = append(allErrs, field.Invalid(fldPath, nodeName, msg))
  170. }
  171. return allErrs
  172. }
  173. // validaVolumeAttachmentStatus tests if volumeAttachmentStatus is valid.
  174. func validateVolumeAttachmentStatus(status *storage.VolumeAttachmentStatus, fldPath *field.Path) field.ErrorList {
  175. allErrs := field.ErrorList{}
  176. allErrs = append(allErrs, validateAttachmentMetadata(status.AttachmentMetadata, fldPath.Child("attachmentMetadata"))...)
  177. allErrs = append(allErrs, validateVolumeError(status.AttachError, fldPath.Child("attachError"))...)
  178. allErrs = append(allErrs, validateVolumeError(status.DetachError, fldPath.Child("detachError"))...)
  179. return allErrs
  180. }
  181. func validateAttachmentMetadata(metadata map[string]string, fldPath *field.Path) field.ErrorList {
  182. allErrs := field.ErrorList{}
  183. var size int64
  184. for k, v := range metadata {
  185. size += (int64)(len(k)) + (int64)(len(v))
  186. }
  187. if size > maxAttachedVolumeMetadataSize {
  188. allErrs = append(allErrs, field.TooLong(fldPath, metadata, maxAttachedVolumeMetadataSize))
  189. }
  190. return allErrs
  191. }
  192. func validateVolumeError(e *storage.VolumeError, fldPath *field.Path) field.ErrorList {
  193. allErrs := field.ErrorList{}
  194. if e == nil {
  195. return allErrs
  196. }
  197. if len(e.Message) > maxVolumeErrorMessageSize {
  198. allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), e.Message, maxAttachedVolumeMetadataSize))
  199. }
  200. return allErrs
  201. }
  202. // ValidateVolumeAttachmentUpdate validates a VolumeAttachment.
  203. func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.ErrorList {
  204. allErrs := ValidateVolumeAttachment(new)
  205. // Spec is read-only
  206. // If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate
  207. if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) {
  208. allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable"))
  209. }
  210. return allErrs
  211. }
  212. var supportedVolumeBindingModes = sets.NewString(string(storage.VolumeBindingImmediate), string(storage.VolumeBindingWaitForFirstConsumer))
  213. // validateVolumeBindingMode tests that VolumeBindingMode specifies valid values.
  214. func validateVolumeBindingMode(mode *storage.VolumeBindingMode, fldPath *field.Path) field.ErrorList {
  215. allErrs := field.ErrorList{}
  216. if mode == nil {
  217. allErrs = append(allErrs, field.Required(fldPath, ""))
  218. } else if !supportedVolumeBindingModes.Has(string(*mode)) {
  219. allErrs = append(allErrs, field.NotSupported(fldPath, mode, supportedVolumeBindingModes.List()))
  220. }
  221. return allErrs
  222. }
  223. // validateAllowedTopology tests that AllowedTopologies specifies valid values.
  224. func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *field.Path) field.ErrorList {
  225. allErrs := field.ErrorList{}
  226. if len(topologies) == 0 {
  227. return allErrs
  228. }
  229. rawTopologies := make([]map[string]sets.String, len(topologies))
  230. for i, term := range topologies {
  231. idxPath := fldPath.Index(i)
  232. exprMap, termErrs := apivalidation.ValidateTopologySelectorTerm(term, fldPath.Index(i))
  233. allErrs = append(allErrs, termErrs...)
  234. // TODO (verult) consider improving runtime
  235. for _, t := range rawTopologies {
  236. if helper.Semantic.DeepEqual(exprMap, t) {
  237. allErrs = append(allErrs, field.Duplicate(idxPath.Child("matchLabelExpressions"), ""))
  238. }
  239. }
  240. rawTopologies = append(rawTopologies, exprMap)
  241. }
  242. return allErrs
  243. }
  244. // ValidateCSINode validates a CSINode.
  245. func ValidateCSINode(csiNode *storage.CSINode) field.ErrorList {
  246. allErrs := apivalidation.ValidateObjectMeta(&csiNode.ObjectMeta, false, apivalidation.ValidateNodeName, field.NewPath("metadata"))
  247. allErrs = append(allErrs, validateCSINodeSpec(&csiNode.Spec, field.NewPath("spec"))...)
  248. return allErrs
  249. }
  250. // ValidateCSINodeUpdate validates a CSINode.
  251. func ValidateCSINodeUpdate(new, old *storage.CSINode) field.ErrorList {
  252. allErrs := ValidateCSINode(new)
  253. // Validate modifying fields inside an existing CSINodeDriver entry is not allowed
  254. for _, oldDriver := range old.Spec.Drivers {
  255. for _, newDriver := range new.Spec.Drivers {
  256. if oldDriver.Name == newDriver.Name {
  257. if !apiequality.Semantic.DeepEqual(oldDriver, newDriver) {
  258. allErrs = append(allErrs, field.Invalid(field.NewPath("CSINodeDriver"), newDriver, "field is immutable"))
  259. }
  260. }
  261. }
  262. }
  263. return allErrs
  264. }
  265. // ValidateCSINodeSpec tests that the specified CSINodeSpec has valid data.
  266. func validateCSINodeSpec(
  267. spec *storage.CSINodeSpec, fldPath *field.Path) field.ErrorList {
  268. allErrs := field.ErrorList{}
  269. allErrs = append(allErrs, validateCSINodeDrivers(spec.Drivers, fldPath.Child("drivers"))...)
  270. return allErrs
  271. }
  272. // ValidateCSINodeDrivers tests that the specified CSINodeDrivers have valid data.
  273. func validateCSINodeDrivers(drivers []storage.CSINodeDriver, fldPath *field.Path) field.ErrorList {
  274. allErrs := field.ErrorList{}
  275. driverNamesInSpecs := make(sets.String)
  276. for i, driver := range drivers {
  277. idxPath := fldPath.Index(i)
  278. allErrs = append(allErrs, validateCSINodeDriver(driver, driverNamesInSpecs, idxPath)...)
  279. }
  280. return allErrs
  281. }
  282. // validateCSINodeDriverNodeID tests if Name in CSINodeDriver is a valid node id.
  283. func validateCSINodeDriverNodeID(nodeID string, fldPath *field.Path) field.ErrorList {
  284. allErrs := field.ErrorList{}
  285. // nodeID is always required
  286. if len(nodeID) == 0 {
  287. allErrs = append(allErrs, field.Required(fldPath, nodeID))
  288. }
  289. if len(nodeID) > csiNodeIDMaxLength {
  290. allErrs = append(allErrs, field.Invalid(fldPath, nodeID, fmt.Sprintf("must be %d characters or less", csiNodeIDMaxLength)))
  291. }
  292. return allErrs
  293. }
  294. // validateCSINodeDriver tests if CSINodeDriver has valid entries
  295. func validateCSINodeDriver(driver storage.CSINodeDriver, driverNamesInSpecs sets.String, fldPath *field.Path) field.ErrorList {
  296. allErrs := field.ErrorList{}
  297. allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(driver.Name, fldPath.Child("name"))...)
  298. allErrs = append(allErrs, validateCSINodeDriverNodeID(driver.NodeID, fldPath.Child("nodeID"))...)
  299. // check for duplicate entries for the same driver in specs
  300. if driverNamesInSpecs.Has(driver.Name) {
  301. allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), driver.Name))
  302. }
  303. driverNamesInSpecs.Insert(driver.Name)
  304. topoKeys := make(sets.String)
  305. for _, key := range driver.TopologyKeys {
  306. if len(key) == 0 {
  307. allErrs = append(allErrs, field.Required(fldPath, key))
  308. }
  309. if topoKeys.Has(key) {
  310. allErrs = append(allErrs, field.Duplicate(fldPath, key))
  311. }
  312. topoKeys.Insert(key)
  313. for _, msg := range validation.IsQualifiedName(key) {
  314. allErrs = append(allErrs, field.Invalid(fldPath, driver.TopologyKeys, msg))
  315. }
  316. }
  317. return allErrs
  318. }
  319. // ValidateCSIDriver validates a CSIDriver.
  320. func ValidateCSIDriver(csiDriver *storage.CSIDriver) field.ErrorList {
  321. allErrs := field.ErrorList{}
  322. allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(csiDriver.Name, field.NewPath("name"))...)
  323. allErrs = append(allErrs, validateCSIDriverSpec(&csiDriver.Spec, field.NewPath("spec"))...)
  324. return allErrs
  325. }
  326. // ValidateCSIDriverUpdate validates a CSIDriver.
  327. func ValidateCSIDriverUpdate(new, old *storage.CSIDriver) field.ErrorList {
  328. allErrs := ValidateCSIDriver(new)
  329. // Spec is read-only
  330. // If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate
  331. if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) {
  332. allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable"))
  333. }
  334. return allErrs
  335. }
  336. // ValidateCSIDriverSpec tests that the specified CSIDriverSpec
  337. // has valid data.
  338. func validateCSIDriverSpec(
  339. spec *storage.CSIDriverSpec, fldPath *field.Path) field.ErrorList {
  340. allErrs := field.ErrorList{}
  341. allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...)
  342. allErrs = append(allErrs, validatePodInfoOnMount(spec.PodInfoOnMount, fldPath.Child("podInfoOnMount"))...)
  343. return allErrs
  344. }
  345. // validateAttachRequired tests if attachRequired is set for CSIDriver.
  346. func validateAttachRequired(attachRequired *bool, fldPath *field.Path) field.ErrorList {
  347. allErrs := field.ErrorList{}
  348. if attachRequired == nil {
  349. allErrs = append(allErrs, field.Required(fldPath, ""))
  350. }
  351. return allErrs
  352. }
  353. // validatePodInfoOnMount tests if podInfoOnMount is set for CSIDriver.
  354. func validatePodInfoOnMount(podInfoOnMount *bool, fldPath *field.Path) field.ErrorList {
  355. allErrs := field.ErrorList{}
  356. if podInfoOnMount == nil {
  357. allErrs = append(allErrs, field.Required(fldPath, ""))
  358. }
  359. return allErrs
  360. }