validation.go 18 KB

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