1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
20	"fmt"
21	"reflect"
22	"strings"
23	"time"
24
25	apiequality "k8s.io/apimachinery/pkg/api/equality"
26	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
27	metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
28	"k8s.io/apimachinery/pkg/util/sets"
29	"k8s.io/apimachinery/pkg/util/validation/field"
30	api "k8s.io/kubernetes/pkg/apis/core"
31	"k8s.io/kubernetes/pkg/apis/core/helper"
32	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
33	"k8s.io/kubernetes/pkg/apis/storage"
34
35	utilfeature "k8s.io/apiserver/pkg/util/feature"
36	"k8s.io/kubernetes/pkg/features"
37)
38
39const (
40	maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB
41	maxProvisionerParameterLen  = 512
42
43	maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
44	maxVolumeErrorMessageSize     = 1024
45
46	csiNodeIDMaxLength       = 192
47	csiNodeIDLongerMaxLength = 256
48)
49
50// CSINodeValidationOptions contains the validation options for validating CSINode
51type CSINodeValidationOptions struct {
52	AllowLongNodeID bool
53}
54
55// ValidateStorageClass validates a StorageClass.
56func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList {
57	allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata"))
58	allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...)
59	allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...)
60	allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...)
61	allErrs = append(allErrs, validateVolumeBindingMode(storageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
62	allErrs = append(allErrs, validateAllowedTopologies(storageClass.AllowedTopologies, field.NewPath("allowedTopologies"))...)
63
64	return allErrs
65}
66
67// ValidateStorageClassUpdate tests if an update to StorageClass is valid.
68func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageClass) field.ErrorList {
69	allErrs := apivalidation.ValidateObjectMetaUpdate(&storageClass.ObjectMeta, &oldStorageClass.ObjectMeta, field.NewPath("metadata"))
70	if !reflect.DeepEqual(oldStorageClass.Parameters, storageClass.Parameters) {
71		allErrs = append(allErrs, field.Forbidden(field.NewPath("parameters"), "updates to parameters are forbidden."))
72	}
73
74	if storageClass.Provisioner != oldStorageClass.Provisioner {
75		allErrs = append(allErrs, field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden."))
76	}
77
78	if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy {
79		allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden."))
80	}
81
82	allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
83	return allErrs
84}
85
86// validateProvisioner tests if provisioner is a valid qualified name.
87func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList {
88	allErrs := field.ErrorList{}
89	if len(provisioner) == 0 {
90		allErrs = append(allErrs, field.Required(fldPath, provisioner))
91	}
92	if len(provisioner) > 0 {
93		allErrs = append(allErrs, apivalidation.ValidateQualifiedName(strings.ToLower(provisioner), fldPath)...)
94	}
95	return allErrs
96}
97
98// validateParameters tests that keys are qualified names and that provisionerParameter are < 256kB.
99func validateParameters(params map[string]string, fldPath *field.Path) field.ErrorList {
100	var totalSize int64
101	allErrs := field.ErrorList{}
102
103	if len(params) > maxProvisionerParameterLen {
104		allErrs = append(allErrs, field.TooLong(fldPath, "Provisioner Parameters exceeded max allowed", maxProvisionerParameterLen))
105		return allErrs
106	}
107
108	for k, v := range params {
109		if len(k) < 1 {
110			allErrs = append(allErrs, field.Invalid(fldPath, k, "field can not be empty."))
111		}
112		totalSize += (int64)(len(k)) + (int64)(len(v))
113	}
114
115	if totalSize > maxProvisionerParameterSize {
116		allErrs = append(allErrs, field.TooLong(fldPath, "", maxProvisionerParameterSize))
117	}
118	return allErrs
119}
120
121var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRetain))
122
123// validateReclaimPolicy tests that the reclaim policy is one of the supported. It is up to the volume plugin to reject
124// provisioning for storage classes with impossible reclaim policies, e.g. EBS is not Recyclable
125func validateReclaimPolicy(reclaimPolicy *api.PersistentVolumeReclaimPolicy, fldPath *field.Path) field.ErrorList {
126	allErrs := field.ErrorList{}
127	if len(string(*reclaimPolicy)) > 0 {
128		if !supportedReclaimPolicy.Has(string(*reclaimPolicy)) {
129			allErrs = append(allErrs, field.NotSupported(fldPath, reclaimPolicy, supportedReclaimPolicy.List()))
130		}
131	}
132	return allErrs
133}
134
135// ValidateVolumeAttachment validates a VolumeAttachment. This function is common for v1 and v1beta1 objects,
136func ValidateVolumeAttachment(volumeAttachment *storage.VolumeAttachment) field.ErrorList {
137	allErrs := apivalidation.ValidateObjectMeta(&volumeAttachment.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata"))
138	allErrs = append(allErrs, validateVolumeAttachmentSpec(&volumeAttachment.Spec, field.NewPath("spec"))...)
139	allErrs = append(allErrs, validateVolumeAttachmentStatus(&volumeAttachment.Status, field.NewPath("status"))...)
140	return allErrs
141}
142
143// ValidateVolumeAttachmentV1 validates a v1/VolumeAttachment. It contains only extra checks missing in
144// ValidateVolumeAttachment.
145func ValidateVolumeAttachmentV1(volumeAttachment *storage.VolumeAttachment) field.ErrorList {
146	allErrs := apivalidation.ValidateCSIDriverName(volumeAttachment.Spec.Attacher, field.NewPath("spec.attacher"))
147
148	if volumeAttachment.Spec.Source.PersistentVolumeName != nil {
149		pvName := *volumeAttachment.Spec.Source.PersistentVolumeName
150		for _, msg := range apivalidation.ValidatePersistentVolumeName(pvName, false) {
151			allErrs = append(allErrs, field.Invalid(field.NewPath("spec.source.persistentVolumeName"), pvName, msg))
152		}
153	}
154	return allErrs
155}
156
157// ValidateVolumeAttachmentSpec tests that the specified VolumeAttachmentSpec
158// has valid data.
159func validateVolumeAttachmentSpec(
160	spec *storage.VolumeAttachmentSpec, fldPath *field.Path) field.ErrorList {
161	allErrs := field.ErrorList{}
162	allErrs = append(allErrs, validateAttacher(spec.Attacher, fldPath.Child("attacher"))...)
163	allErrs = append(allErrs, validateVolumeAttachmentSource(&spec.Source, fldPath.Child("source"))...)
164	allErrs = append(allErrs, validateNodeName(spec.NodeName, fldPath.Child("nodeName"))...)
165	return allErrs
166}
167
168// validateAttacher tests if attacher is a valid qualified name.
169func validateAttacher(attacher string, fldPath *field.Path) field.ErrorList {
170	allErrs := field.ErrorList{}
171	if len(attacher) == 0 {
172		allErrs = append(allErrs, field.Required(fldPath, attacher))
173	}
174	return allErrs
175}
176
177// validateSource tests if the source is valid for VolumeAttachment.
178func validateVolumeAttachmentSource(source *storage.VolumeAttachmentSource, fldPath *field.Path) field.ErrorList {
179	allErrs := field.ErrorList{}
180	switch {
181	case source.InlineVolumeSpec == nil && source.PersistentVolumeName == nil:
182		if utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) {
183			allErrs = append(allErrs, field.Required(fldPath, "must specify exactly one of inlineVolumeSpec and persistentVolumeName"))
184		} else {
185			allErrs = append(allErrs, field.Required(fldPath, "must specify persistentVolumeName when CSIMigration feature is disabled"))
186		}
187	case source.InlineVolumeSpec != nil && source.PersistentVolumeName != nil:
188		allErrs = append(allErrs, field.Forbidden(fldPath, "must specify exactly one of inlineVolumeSpec and persistentVolumeName"))
189	case source.PersistentVolumeName != nil:
190		if len(*source.PersistentVolumeName) == 0 {
191			// Invalid err
192			allErrs = append(allErrs, field.Required(fldPath.Child("persistentVolumeName"), "must specify non empty persistentVolumeName"))
193		}
194	case source.InlineVolumeSpec != nil:
195		opts := apivalidation.PersistentVolumeSpecValidationOptions{}
196		allErrs = append(allErrs, apivalidation.ValidatePersistentVolumeSpec(source.InlineVolumeSpec, "", true, fldPath.Child("inlineVolumeSpec"), opts)...)
197	}
198	return allErrs
199}
200
201// validateNodeName tests if the nodeName is valid for VolumeAttachment.
202func validateNodeName(nodeName string, fldPath *field.Path) field.ErrorList {
203	allErrs := field.ErrorList{}
204	for _, msg := range apivalidation.ValidateNodeName(nodeName, false /* prefix */) {
205		allErrs = append(allErrs, field.Invalid(fldPath, nodeName, msg))
206	}
207	return allErrs
208}
209
210// validaVolumeAttachmentStatus tests if volumeAttachmentStatus is valid.
211func validateVolumeAttachmentStatus(status *storage.VolumeAttachmentStatus, fldPath *field.Path) field.ErrorList {
212	allErrs := field.ErrorList{}
213	allErrs = append(allErrs, validateAttachmentMetadata(status.AttachmentMetadata, fldPath.Child("attachmentMetadata"))...)
214	allErrs = append(allErrs, validateVolumeError(status.AttachError, fldPath.Child("attachError"))...)
215	allErrs = append(allErrs, validateVolumeError(status.DetachError, fldPath.Child("detachError"))...)
216	return allErrs
217}
218
219func validateAttachmentMetadata(metadata map[string]string, fldPath *field.Path) field.ErrorList {
220	allErrs := field.ErrorList{}
221
222	var size int64
223	for k, v := range metadata {
224		size += (int64)(len(k)) + (int64)(len(v))
225	}
226	if size > maxAttachedVolumeMetadataSize {
227		allErrs = append(allErrs, field.TooLong(fldPath, metadata, maxAttachedVolumeMetadataSize))
228	}
229	return allErrs
230}
231
232func validateVolumeError(e *storage.VolumeError, fldPath *field.Path) field.ErrorList {
233	allErrs := field.ErrorList{}
234
235	if e == nil {
236		return allErrs
237	}
238	if len(e.Message) > maxVolumeErrorMessageSize {
239		allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), e.Message, maxAttachedVolumeMetadataSize))
240	}
241	return allErrs
242}
243
244// ValidateVolumeAttachmentUpdate validates a VolumeAttachment.
245func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.ErrorList {
246	allErrs := ValidateVolumeAttachment(new)
247
248	// Spec is read-only
249	// If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate
250	if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) {
251		allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable"))
252	}
253	return allErrs
254}
255
256var supportedVolumeBindingModes = sets.NewString(string(storage.VolumeBindingImmediate), string(storage.VolumeBindingWaitForFirstConsumer))
257
258// validateVolumeBindingMode tests that VolumeBindingMode specifies valid values.
259func validateVolumeBindingMode(mode *storage.VolumeBindingMode, fldPath *field.Path) field.ErrorList {
260	allErrs := field.ErrorList{}
261	if mode == nil {
262		allErrs = append(allErrs, field.Required(fldPath, ""))
263	} else if !supportedVolumeBindingModes.Has(string(*mode)) {
264		allErrs = append(allErrs, field.NotSupported(fldPath, mode, supportedVolumeBindingModes.List()))
265	}
266
267	return allErrs
268}
269
270// validateAllowedTopology tests that AllowedTopologies specifies valid values.
271func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *field.Path) field.ErrorList {
272	allErrs := field.ErrorList{}
273
274	if len(topologies) == 0 {
275		return allErrs
276	}
277
278	rawTopologies := make([]map[string]sets.String, len(topologies))
279	for i, term := range topologies {
280		idxPath := fldPath.Index(i)
281		exprMap, termErrs := apivalidation.ValidateTopologySelectorTerm(term, fldPath.Index(i))
282		allErrs = append(allErrs, termErrs...)
283
284		// TODO (verult) consider improving runtime
285		for _, t := range rawTopologies {
286			if helper.Semantic.DeepEqual(exprMap, t) {
287				allErrs = append(allErrs, field.Duplicate(idxPath.Child("matchLabelExpressions"), ""))
288			}
289		}
290
291		rawTopologies = append(rawTopologies, exprMap)
292	}
293
294	return allErrs
295}
296
297// ValidateCSINode validates a CSINode.
298func ValidateCSINode(csiNode *storage.CSINode, validationOpts CSINodeValidationOptions) field.ErrorList {
299	allErrs := apivalidation.ValidateObjectMeta(&csiNode.ObjectMeta, false, apivalidation.ValidateNodeName, field.NewPath("metadata"))
300	allErrs = append(allErrs, validateCSINodeSpec(&csiNode.Spec, field.NewPath("spec"), validationOpts)...)
301	return allErrs
302}
303
304// ValidateCSINodeUpdate validates a CSINode.
305func ValidateCSINodeUpdate(new, old *storage.CSINode, validationOpts CSINodeValidationOptions) field.ErrorList {
306	allErrs := ValidateCSINode(new, validationOpts)
307
308	// Validate modifying fields inside an existing CSINodeDriver entry is not allowed
309	for _, oldDriver := range old.Spec.Drivers {
310		for _, newDriver := range new.Spec.Drivers {
311			if oldDriver.Name == newDriver.Name {
312				if !apiequality.Semantic.DeepEqual(oldDriver, newDriver) {
313					allErrs = append(allErrs, field.Invalid(field.NewPath("CSINodeDriver"), newDriver, "field is immutable"))
314				}
315			}
316		}
317	}
318
319	return allErrs
320}
321
322// ValidateCSINodeSpec tests that the specified CSINodeSpec has valid data.
323func validateCSINodeSpec(
324	spec *storage.CSINodeSpec, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList {
325	allErrs := field.ErrorList{}
326	allErrs = append(allErrs, validateCSINodeDrivers(spec.Drivers, fldPath.Child("drivers"), validationOpts)...)
327	return allErrs
328}
329
330// ValidateCSINodeDrivers tests that the specified CSINodeDrivers have valid data.
331func validateCSINodeDrivers(drivers []storage.CSINodeDriver, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList {
332	allErrs := field.ErrorList{}
333	driverNamesInSpecs := make(sets.String)
334	for i, driver := range drivers {
335		idxPath := fldPath.Index(i)
336		allErrs = append(allErrs, validateCSINodeDriver(driver, driverNamesInSpecs, idxPath, validationOpts)...)
337	}
338
339	return allErrs
340}
341
342// validateCSINodeDriverNodeID tests if Name in CSINodeDriver is a valid node id.
343func validateCSINodeDriverNodeID(nodeID string, fldPath *field.Path, validationOpts CSINodeValidationOptions) field.ErrorList {
344	allErrs := field.ErrorList{}
345
346	// nodeID is always required
347	if len(nodeID) == 0 {
348		allErrs = append(allErrs, field.Required(fldPath, nodeID))
349	}
350	maxLength := csiNodeIDMaxLength
351	if validationOpts.AllowLongNodeID {
352		maxLength = csiNodeIDLongerMaxLength
353	}
354	if len(nodeID) > maxLength {
355		allErrs = append(allErrs, field.Invalid(fldPath, nodeID, fmt.Sprintf("must be %d characters or less", maxLength)))
356	}
357	return allErrs
358}
359
360// CSINodeLongerID will check if the nodeID is longer than csiNodeIDMaxLength
361func CSINodeLongerID(nodeID string) bool {
362	return len(nodeID) > csiNodeIDMaxLength
363}
364
365// validateCSINodeDriverAllocatable tests if Allocatable in CSINodeDriver has valid volume limits.
366func validateCSINodeDriverAllocatable(a *storage.VolumeNodeResources, fldPath *field.Path) field.ErrorList {
367	allErrs := field.ErrorList{}
368
369	if a == nil || a.Count == nil {
370		return allErrs
371	}
372
373	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*a.Count), fldPath.Child("count"))...)
374	return allErrs
375}
376
377// validateCSINodeDriver tests if CSINodeDriver has valid entries
378func validateCSINodeDriver(driver storage.CSINodeDriver, driverNamesInSpecs sets.String, fldPath *field.Path,
379	validationOpts CSINodeValidationOptions) field.ErrorList {
380	allErrs := field.ErrorList{}
381
382	allErrs = append(allErrs, apivalidation.ValidateCSIDriverName(driver.Name, fldPath.Child("name"))...)
383	allErrs = append(allErrs, validateCSINodeDriverNodeID(driver.NodeID, fldPath.Child("nodeID"), validationOpts)...)
384	allErrs = append(allErrs, validateCSINodeDriverAllocatable(driver.Allocatable, fldPath.Child("allocatable"))...)
385
386	// check for duplicate entries for the same driver in specs
387	if driverNamesInSpecs.Has(driver.Name) {
388		allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), driver.Name))
389	}
390	driverNamesInSpecs.Insert(driver.Name)
391	topoKeys := make(sets.String)
392	for _, key := range driver.TopologyKeys {
393		if len(key) == 0 {
394			allErrs = append(allErrs, field.Required(fldPath, key))
395		}
396
397		if topoKeys.Has(key) {
398			allErrs = append(allErrs, field.Duplicate(fldPath, key))
399		}
400		topoKeys.Insert(key)
401
402		allErrs = append(allErrs, apivalidation.ValidateQualifiedName(key, fldPath)...)
403	}
404
405	return allErrs
406}
407
408// ValidateCSIDriverName checks that a name is appropriate for a
409// CSIDriver object.
410var ValidateCSIDriverName = apimachineryvalidation.NameIsDNSSubdomain
411
412// ValidateCSIDriver validates a CSIDriver.
413func ValidateCSIDriver(csiDriver *storage.CSIDriver) field.ErrorList {
414	allErrs := apivalidation.ValidateObjectMeta(&csiDriver.ObjectMeta, false, ValidateCSIDriverName, field.NewPath("metadata"))
415
416	allErrs = append(allErrs, validateCSIDriverSpec(&csiDriver.Spec, field.NewPath("spec"))...)
417	return allErrs
418}
419
420// ValidateCSIDriverUpdate validates a CSIDriver.
421func ValidateCSIDriverUpdate(new, old *storage.CSIDriver) field.ErrorList {
422	allErrs := apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
423
424	// immutable fields should not be mutated.
425	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec.AttachRequired, old.Spec.AttachRequired, field.NewPath("spec", "attachedRequired"))...)
426	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec.FSGroupPolicy, old.Spec.FSGroupPolicy, field.NewPath("spec", "fsGroupPolicy"))...)
427	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec.PodInfoOnMount, old.Spec.PodInfoOnMount, field.NewPath("spec", "podInfoOnMount"))...)
428	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec.VolumeLifecycleModes, old.Spec.VolumeLifecycleModes, field.NewPath("spec", "volumeLifecycleModes"))...)
429	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(new.Spec.StorageCapacity, old.Spec.StorageCapacity, field.NewPath("spec", "storageCapacity"))...)
430
431	allErrs = append(allErrs, validateTokenRequests(new.Spec.TokenRequests, field.NewPath("spec", "tokenRequests"))...)
432	return allErrs
433}
434
435// ValidateCSIDriverSpec tests that the specified CSIDriverSpec
436// has valid data.
437func validateCSIDriverSpec(
438	spec *storage.CSIDriverSpec, fldPath *field.Path) field.ErrorList {
439	allErrs := field.ErrorList{}
440	allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...)
441	allErrs = append(allErrs, validatePodInfoOnMount(spec.PodInfoOnMount, fldPath.Child("podInfoOnMount"))...)
442	allErrs = append(allErrs, validateStorageCapacity(spec.StorageCapacity, fldPath.Child("storageCapacity"))...)
443	allErrs = append(allErrs, validateFSGroupPolicy(spec.FSGroupPolicy, fldPath.Child("fsGroupPolicy"))...)
444	allErrs = append(allErrs, validateTokenRequests(spec.TokenRequests, fldPath.Child("tokenRequests"))...)
445	allErrs = append(allErrs, validateVolumeLifecycleModes(spec.VolumeLifecycleModes, fldPath.Child("volumeLifecycleModes"))...)
446	return allErrs
447}
448
449// validateAttachRequired tests if attachRequired is set for CSIDriver.
450func validateAttachRequired(attachRequired *bool, fldPath *field.Path) field.ErrorList {
451	allErrs := field.ErrorList{}
452	if attachRequired == nil {
453		allErrs = append(allErrs, field.Required(fldPath, ""))
454	}
455
456	return allErrs
457}
458
459// validatePodInfoOnMount tests if podInfoOnMount is set for CSIDriver.
460func validatePodInfoOnMount(podInfoOnMount *bool, fldPath *field.Path) field.ErrorList {
461	allErrs := field.ErrorList{}
462	if podInfoOnMount == nil {
463		allErrs = append(allErrs, field.Required(fldPath, ""))
464	}
465
466	return allErrs
467}
468
469// validateStorageCapacity tests if storageCapacity is set for CSIDriver.
470func validateStorageCapacity(storageCapacity *bool, fldPath *field.Path) field.ErrorList {
471	allErrs := field.ErrorList{}
472	if storageCapacity == nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
473		allErrs = append(allErrs, field.Required(fldPath, ""))
474	}
475
476	return allErrs
477}
478
479var supportedFSGroupPolicy = sets.NewString(string(storage.ReadWriteOnceWithFSTypeFSGroupPolicy), string(storage.FileFSGroupPolicy), string(storage.NoneFSGroupPolicy))
480
481// validateFSGroupPolicy tests if FSGroupPolicy contains an appropriate value.
482func validateFSGroupPolicy(fsGroupPolicy *storage.FSGroupPolicy, fldPath *field.Path) field.ErrorList {
483	allErrs := field.ErrorList{}
484	if fsGroupPolicy == nil {
485		// This is not a required field, so if nothing is provided simply return
486		return allErrs
487	}
488
489	if !supportedFSGroupPolicy.Has(string(*fsGroupPolicy)) {
490		allErrs = append(allErrs, field.NotSupported(fldPath, fsGroupPolicy, supportedFSGroupPolicy.List()))
491	}
492
493	return allErrs
494}
495
496// validateTokenRequests tests if the Audience in each TokenRequest are different.
497// Besides, at most one TokenRequest can ignore Audience.
498func validateTokenRequests(tokenRequests []storage.TokenRequest, fldPath *field.Path) field.ErrorList {
499	const min = 10 * time.Minute
500	allErrs := field.ErrorList{}
501	audiences := make(map[string]bool)
502	for i, tokenRequest := range tokenRequests {
503		path := fldPath.Index(i)
504		audience := tokenRequest.Audience
505		if _, ok := audiences[audience]; ok {
506			allErrs = append(allErrs, field.Duplicate(path.Child("audience"), audience))
507			continue
508		}
509		audiences[audience] = true
510
511		if tokenRequest.ExpirationSeconds == nil {
512			continue
513		}
514		if *tokenRequest.ExpirationSeconds < int64(min.Seconds()) {
515			allErrs = append(allErrs, field.Invalid(path.Child("expirationSeconds"), *tokenRequest.ExpirationSeconds, "may not specify a duration less than 10 minutes"))
516		}
517		if *tokenRequest.ExpirationSeconds > 1<<32 {
518			allErrs = append(allErrs, field.Invalid(path.Child("expirationSeconds"), *tokenRequest.ExpirationSeconds, "may not specify a duration larger than 2^32 seconds"))
519		}
520	}
521
522	return allErrs
523}
524
525// validateVolumeLifecycleModes tests if mode has one of the allowed values.
526func validateVolumeLifecycleModes(modes []storage.VolumeLifecycleMode, fldPath *field.Path) field.ErrorList {
527	allErrs := field.ErrorList{}
528	for _, mode := range modes {
529		switch mode {
530		case storage.VolumeLifecyclePersistent, storage.VolumeLifecycleEphemeral:
531		default:
532			allErrs = append(allErrs, field.NotSupported(fldPath, mode,
533				[]string{
534					string(storage.VolumeLifecyclePersistent),
535					string(storage.VolumeLifecycleEphemeral),
536				}))
537		}
538	}
539
540	return allErrs
541}
542
543// ValidateStorageCapacityName checks that a name is appropriate for a
544// CSIStorageCapacity object.
545var ValidateStorageCapacityName = apimachineryvalidation.NameIsDNSSubdomain
546
547// ValidateCSIStorageCapacity validates a CSIStorageCapacity.
548func ValidateCSIStorageCapacity(capacity *storage.CSIStorageCapacity) field.ErrorList {
549	allErrs := apivalidation.ValidateObjectMeta(&capacity.ObjectMeta, true, ValidateStorageCapacityName, field.NewPath("metadata"))
550	allErrs = append(allErrs, metav1validation.ValidateLabelSelector(capacity.NodeTopology, field.NewPath("nodeTopology"))...)
551	for _, msg := range apivalidation.ValidateClassName(capacity.StorageClassName, false) {
552		allErrs = append(allErrs, field.Invalid(field.NewPath("storageClassName"), capacity.StorageClassName, msg))
553	}
554	if capacity.Capacity != nil {
555		allErrs = append(allErrs, apivalidation.ValidateNonnegativeQuantity(*capacity.Capacity, field.NewPath("capacity"))...)
556	}
557	return allErrs
558}
559
560// ValidateCSIStorageCapacityUpdate tests if an update to CSIStorageCapacity is valid.
561func ValidateCSIStorageCapacityUpdate(capacity, oldCapacity *storage.CSIStorageCapacity) field.ErrorList {
562	allErrs := apivalidation.ValidateObjectMetaUpdate(&capacity.ObjectMeta, &oldCapacity.ObjectMeta, field.NewPath("metadata"))
563
564	// Input fields for CSI GetCapacity are immutable.
565	// If this ever relaxes in the future, make sure to increment the Generation number in PrepareForUpdate
566	if !apiequality.Semantic.DeepEqual(capacity.NodeTopology, oldCapacity.NodeTopology) {
567		allErrs = append(allErrs, field.Invalid(field.NewPath("nodeTopology"), capacity.NodeTopology, "field is immutable"))
568	}
569	if capacity.StorageClassName != oldCapacity.StorageClassName {
570		allErrs = append(allErrs, field.Invalid(field.NewPath("storageClassName"), capacity.StorageClassName, "field is immutable"))
571	}
572
573	return allErrs
574}
575