diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 324c6047465..1f4d681f29f 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -247,6 +247,11 @@ var ValidateEndpointsName = NameIsDNSSubdomain // ValidateClusterName can be used to check whether the given cluster name is valid. var ValidateClusterName = genericvalidation.ValidateClusterName +// ValidateClassName can be used to check whether the given class name is valid. +// It is defined here to avoid import cycle between pkg/apis/storage/validation +// (where it should be) and this file. +var ValidateClassName = NameIsDNSSubdomain + // TODO update all references to these functions to point to the genericvalidation ones // NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. func NameIsDNSSubdomain(name string, prefix bool) []string { @@ -1194,6 +1199,12 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { allErrs = append(allErrs, field.Forbidden(specPath.Child("persistentVolumeReclaimPolicy"), "may not be 'recycle' for a hostPath mount of '/'")) } + if len(pv.Spec.StorageClassName) > 0 { + for _, msg := range ValidateClassName(pv.Spec.StorageClassName, false) { + allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg)) + } + } + return allErrs } @@ -1244,6 +1255,12 @@ func ValidatePersistentVolumeClaimSpec(spec *api.PersistentVolumeClaimSpec, fldP } else { allErrs = append(allErrs, ValidateResourceQuantityValue(string(api.ResourceStorage), storageValue, fldPath.Child("resources").Key(string(api.ResourceStorage)))...) } + + if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 { + for _, msg := range ValidateClassName(*spec.StorageClassName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg)) + } + } return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index df78584d311..6d22077cd1c 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -80,6 +80,7 @@ func TestValidatePersistentVolumes(t *testing.T) { PersistentVolumeSource: api.PersistentVolumeSource{ HostPath: &api.HostPathVolumeSource{Path: "/foo"}, }, + StorageClassName: "valid", }), }, "good-volume-with-retain-policy": { @@ -230,6 +231,19 @@ func TestValidatePersistentVolumes(t *testing.T) { }, }), }, + "invalid-storage-class-name": { + isExpectedFailure: true, + volume: testVolume("invalid-storage-class-name", "", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + }, + StorageClassName: "-invalid-", + }), + }, } for name, scenario := range scenarios { @@ -301,6 +315,8 @@ func testVolumeClaimAnnotation(name string, namespace string, ann string, annval } func TestValidatePersistentVolumeClaim(t *testing.T) { + invalidClassName := "-invalid-" + validClassName := "valid" scenarios := map[string]struct { isExpectedFailure bool claim *api.PersistentVolumeClaim @@ -325,6 +341,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) { api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), }, }, + StorageClassName: &validClassName, }), }, "invalid-label-selector": { @@ -428,6 +445,29 @@ func TestValidatePersistentVolumeClaim(t *testing.T) { }, }), }, + "invalid-storage-class-name": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key2", + Operator: "Exists", + }, + }, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }, + StorageClassName: &invalidClassName, + }), + }, } for name, scenario := range scenarios { diff --git a/pkg/apis/storage/validation/validation.go b/pkg/apis/storage/validation/validation.go index 117ec7427bd..d72288f9784 100644 --- a/pkg/apis/storage/validation/validation.go +++ b/pkg/apis/storage/validation/validation.go @@ -28,7 +28,7 @@ import ( // ValidateStorageClass validates a StorageClass. func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList { - allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata")) allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...) allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...)