Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
49
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/BUILD
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/BUILD
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["validation.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/storage/validation",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validation_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
261
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/validation.go
generated
vendored
Normal file
261
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/validation.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
const (
|
||||
maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB
|
||||
maxProvisionerParameterLen = 512
|
||||
|
||||
maxAttachedVolumeMetadataSize = 256 * (1 << 10) // 256 kB
|
||||
maxVolumeErrorMessageSize = 1024
|
||||
)
|
||||
|
||||
// ValidateStorageClass validates a StorageClass.
|
||||
func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList {
|
||||
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"))...)
|
||||
allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...)
|
||||
allErrs = append(allErrs, validateAllowVolumeExpansion(storageClass.AllowVolumeExpansion, field.NewPath("allowVolumeExpansion"))...)
|
||||
allErrs = append(allErrs, validateVolumeBindingMode(storageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
|
||||
allErrs = append(allErrs, validateAllowedTopologies(storageClass.AllowedTopologies, field.NewPath("allowedTopologies"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateStorageClassUpdate tests if an update to StorageClass is valid.
|
||||
func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageClass) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMetaUpdate(&storageClass.ObjectMeta, &oldStorageClass.ObjectMeta, field.NewPath("metadata"))
|
||||
if !reflect.DeepEqual(oldStorageClass.Parameters, storageClass.Parameters) {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("parameters"), "updates to parameters are forbidden."))
|
||||
}
|
||||
|
||||
if storageClass.Provisioner != oldStorageClass.Provisioner {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden."))
|
||||
}
|
||||
|
||||
if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden."))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateProvisioner tests if provisioner is a valid qualified name.
|
||||
func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(provisioner) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath, provisioner))
|
||||
}
|
||||
if len(provisioner) > 0 {
|
||||
for _, msg := range validation.IsQualifiedName(strings.ToLower(provisioner)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, provisioner, msg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateParameters tests that keys are qualified names and that provisionerParameter are < 256kB.
|
||||
func validateParameters(params map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
var totalSize int64
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(params) > maxProvisionerParameterLen {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, "Provisioner Parameters exceeded max allowed", maxProvisionerParameterLen))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
if len(k) < 1 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, k, "field can not be empty."))
|
||||
}
|
||||
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||
}
|
||||
|
||||
if totalSize > maxProvisionerParameterSize {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, "", maxProvisionerParameterSize))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var supportedReclaimPolicy = sets.NewString(string(api.PersistentVolumeReclaimDelete), string(api.PersistentVolumeReclaimRetain))
|
||||
|
||||
// validateReclaimPolicy tests that the reclaim policy is one of the supported. It is up to the volume plugin to reject
|
||||
// provisioning for storage classes with impossible reclaim policies, e.g. EBS is not Recyclable
|
||||
func validateReclaimPolicy(reclaimPolicy *api.PersistentVolumeReclaimPolicy, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(string(*reclaimPolicy)) > 0 {
|
||||
if !supportedReclaimPolicy.Has(string(*reclaimPolicy)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath, reclaimPolicy, supportedReclaimPolicy.List()))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateAllowVolumeExpansion tests that if ExpandPersistentVolumes feature gate is disabled, whether the AllowVolumeExpansion filed
|
||||
// of storage class is set
|
||||
func validateAllowVolumeExpansion(allowExpand *bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if allowExpand != nil && !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate ExpandPersistentVolumes"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateVolumeAttachment validates a VolumeAttachment.
|
||||
func ValidateVolumeAttachment(volumeAttachment *storage.VolumeAttachment) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMeta(&volumeAttachment.ObjectMeta, false, apivalidation.ValidateClassName, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, validateVolumeAttachmentSpec(&volumeAttachment.Spec, field.NewPath("spec"))...)
|
||||
allErrs = append(allErrs, validateVolumeAttachmentStatus(&volumeAttachment.Status, field.NewPath("status"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateVolumeAttachmentSpec tests that the specified VolumeAttachmentSpec
|
||||
// has valid data.
|
||||
func validateVolumeAttachmentSpec(
|
||||
spec *storage.VolumeAttachmentSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, validateAttacher(spec.Attacher, fldPath.Child("attacher"))...)
|
||||
allErrs = append(allErrs, validateVolumeAttachmentSource(&spec.Source, fldPath.Child("source"))...)
|
||||
allErrs = append(allErrs, validateNodeName(spec.NodeName, fldPath.Child("nodeName"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateAttacher tests if attacher is a valid qualified name.
|
||||
func validateAttacher(attacher string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(attacher) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath, attacher))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateSource tests if the source is valid for VolumeAttachment.
|
||||
func validateVolumeAttachmentSource(source *storage.VolumeAttachmentSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if source.PersistentVolumeName == nil || len(*source.PersistentVolumeName) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath, ""))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateNodeName tests if the nodeName is valid for VolumeAttachment.
|
||||
func validateNodeName(nodeName string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for _, msg := range apivalidation.ValidateNodeName(nodeName, false /* prefix */) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, nodeName, msg))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validaVolumeAttachmentStatus tests if volumeAttachmentStatus is valid.
|
||||
func validateVolumeAttachmentStatus(status *storage.VolumeAttachmentStatus, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allErrs = append(allErrs, validateAttachmentMetadata(status.AttachmentMetadata, fldPath.Child("attachmentMetadata"))...)
|
||||
allErrs = append(allErrs, validateVolumeError(status.AttachError, fldPath.Child("attachError"))...)
|
||||
allErrs = append(allErrs, validateVolumeError(status.DetachError, fldPath.Child("detachError"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateAttachmentMetadata(metadata map[string]string, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
var size int64
|
||||
for k, v := range metadata {
|
||||
size += (int64)(len(k)) + (int64)(len(v))
|
||||
}
|
||||
if size > maxAttachedVolumeMetadataSize {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, metadata, maxAttachedVolumeMetadataSize))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateVolumeError(e *storage.VolumeError, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if e == nil {
|
||||
return allErrs
|
||||
}
|
||||
if len(e.Message) > maxVolumeErrorMessageSize {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), e.Message, maxAttachedVolumeMetadataSize))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateVolumeAttachmentUpdate validates a VolumeAttachment.
|
||||
func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.ErrorList {
|
||||
allErrs := ValidateVolumeAttachment(new)
|
||||
|
||||
// Spec is read-only
|
||||
if !apiequality.Semantic.DeepEqual(old.Spec, new.Spec) {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), new.Spec, "field is immutable"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var supportedVolumeBindingModes = sets.NewString(string(storage.VolumeBindingImmediate), string(storage.VolumeBindingWaitForFirstConsumer))
|
||||
|
||||
// validateVolumeBindingMode tests that VolumeBindingMode specifies valid values.
|
||||
func validateVolumeBindingMode(mode *storage.VolumeBindingMode, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
if mode == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath, ""))
|
||||
} else if !supportedVolumeBindingModes.Has(string(*mode)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath, mode, supportedVolumeBindingModes.List()))
|
||||
}
|
||||
} else if mode != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate VolumeScheduling"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateAllowedTopology tests that AllowedTopologies specifies valid values.
|
||||
func validateAllowedTopologies(topologies []api.TopologySelectorTerm, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if topologies == nil || len(topologies) == 0 {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.DynamicProvisioningScheduling) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate DynamicProvisioningScheduling"))
|
||||
}
|
||||
|
||||
for i, term := range topologies {
|
||||
allErrs = append(allErrs, apivalidation.ValidateTopologySelectorTerm(term, fldPath.Index(i))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
693
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/validation_test.go
generated
vendored
Normal file
693
vendor/k8s.io/kubernetes/pkg/apis/storage/validation/validation_test.go
generated
vendored
Normal file
@@ -0,0 +1,693 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
deleteReclaimPolicy = api.PersistentVolumeReclaimDelete
|
||||
immediateMode1 = storage.VolumeBindingImmediate
|
||||
immediateMode2 = storage.VolumeBindingImmediate
|
||||
waitingMode = storage.VolumeBindingWaitForFirstConsumer
|
||||
invalidMode = storage.VolumeBindingMode("foo")
|
||||
)
|
||||
|
||||
func TestValidateStorageClass(t *testing.T) {
|
||||
deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete")
|
||||
retainReclaimPolicy := api.PersistentVolumeReclaimPolicy("Retain")
|
||||
recycleReclaimPolicy := api.PersistentVolumeReclaimPolicy("Recycle")
|
||||
successCases := []storage.StorageClass{
|
||||
{
|
||||
// empty parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
Parameters: map[string]string{},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// nil parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// some parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
Parameters: map[string]string{
|
||||
"kubernetes.io/foo-parameter": "free/form/string",
|
||||
"foo-parameter": "free-form-string",
|
||||
"foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
{
|
||||
// retain reclaimPolicy
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &retainReclaimPolicy,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
},
|
||||
}
|
||||
|
||||
// Success cases are expected to pass validation.
|
||||
for k, v := range successCases {
|
||||
if errs := ValidateStorageClass(&v); len(errs) != 0 {
|
||||
t.Errorf("Expected success for %d, got %v", k, errs)
|
||||
}
|
||||
}
|
||||
|
||||
// generate a map longer than maxProvisionerParameterSize
|
||||
longParameters := make(map[string]string)
|
||||
totalSize := 0
|
||||
for totalSize < maxProvisionerParameterSize {
|
||||
k := fmt.Sprintf("param/%d", totalSize)
|
||||
v := fmt.Sprintf("value-%d", totalSize)
|
||||
longParameters[k] = v
|
||||
totalSize = totalSize + len(k) + len(v)
|
||||
}
|
||||
|
||||
errorCases := map[string]storage.StorageClass{
|
||||
"namespace is present": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
},
|
||||
"invalid provisioner": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/invalid/provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
},
|
||||
"invalid empty parameter name": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo",
|
||||
Parameters: map[string]string{
|
||||
"": "value",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
},
|
||||
"provisioner: Required value": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
},
|
||||
"too long parameters": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo",
|
||||
Parameters: longParameters,
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
},
|
||||
"invalid reclaimpolicy": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo",
|
||||
ReclaimPolicy: &recycleReclaimPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
// Error cases are not expected to pass validation.
|
||||
for testName, storageClass := range errorCases {
|
||||
if errs := ValidateStorageClass(&storageClass); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %s", testName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) {
|
||||
deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete")
|
||||
falseVar := false
|
||||
testSC := &storage.StorageClass{
|
||||
// empty parameters
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
Parameters: map[string]string{},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
AllowVolumeExpansion: &falseVar,
|
||||
VolumeBindingMode: &immediateMode1,
|
||||
}
|
||||
|
||||
// Enable alpha feature ExpandPersistentVolumes
|
||||
err := utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=true")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to enable feature gate for ExpandPersistentVolumes: %v", err)
|
||||
return
|
||||
}
|
||||
if errs := ValidateStorageClass(testSC); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
// Disable alpha feature ExpandPersistentVolumes
|
||||
err = utilfeature.DefaultFeatureGate.Set("ExpandPersistentVolumes=false")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to disable feature gate for ExpandPersistentVolumes: %v", err)
|
||||
return
|
||||
}
|
||||
if errs := ValidateStorageClass(testSC); len(errs) == 0 {
|
||||
t.Errorf("expected failure, but got no error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestVolumeAttachmentValidation(t *testing.T) {
|
||||
volumeName := "pv-name"
|
||||
empty := ""
|
||||
successCases := []storage.VolumeAttachment{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo-with-status"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
Status: storage.VolumeAttachmentStatus{
|
||||
Attached: true,
|
||||
AttachmentMetadata: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AttachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
DetachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, volumeAttachment := range successCases {
|
||||
if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
errorCases := []storage.VolumeAttachment{
|
||||
{
|
||||
// Empty attacher name
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "",
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Invalid attacher name
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "invalid!@#$%^&*()",
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Empty node name
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
NodeName: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
// No volume name
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
NodeName: "node",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Empty volume name
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
NodeName: "node",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &empty,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Too long error message
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
NodeName: "node",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
},
|
||||
Status: storage.VolumeAttachmentStatus{
|
||||
Attached: true,
|
||||
AttachmentMetadata: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AttachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
DetachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: strings.Repeat("a", maxVolumeErrorMessageSize+1),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Too long metadata
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
NodeName: "node",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
},
|
||||
Status: storage.VolumeAttachmentStatus{
|
||||
Attached: true,
|
||||
AttachmentMetadata: map[string]string{
|
||||
"foo": strings.Repeat("a", maxAttachedVolumeMetadataSize),
|
||||
},
|
||||
AttachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
DetachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, volumeAttachment := range errorCases {
|
||||
if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %v", volumeAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeAttachmentUpdateValidation(t *testing.T) {
|
||||
volumeName := "foo"
|
||||
newVolumeName := "bar"
|
||||
|
||||
old := storage.VolumeAttachment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
}
|
||||
successCases := []storage.VolumeAttachment{
|
||||
{
|
||||
// no change
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// modify status
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
Status: storage.VolumeAttachmentStatus{
|
||||
Attached: true,
|
||||
AttachmentMetadata: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AttachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
DetachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, volumeAttachment := range successCases {
|
||||
if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []storage.VolumeAttachment{
|
||||
{
|
||||
// change attacher
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "another-attacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// change volume
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &newVolumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// change node
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "anothernode",
|
||||
},
|
||||
},
|
||||
{
|
||||
// add invalid status
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||
Spec: storage.VolumeAttachmentSpec{
|
||||
Attacher: "myattacher",
|
||||
Source: storage.VolumeAttachmentSource{
|
||||
PersistentVolumeName: &volumeName,
|
||||
},
|
||||
NodeName: "mynode",
|
||||
},
|
||||
Status: storage.VolumeAttachmentStatus{
|
||||
Attached: true,
|
||||
AttachmentMetadata: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AttachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: strings.Repeat("a", maxAttachedVolumeMetadataSize),
|
||||
},
|
||||
DetachError: &storage.VolumeError{
|
||||
Time: metav1.Time{},
|
||||
Message: "hello world",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, volumeAttachment := range errorCases {
|
||||
if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %v", volumeAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeClass(mode *storage.VolumeBindingMode, topologies []api.TopologySelectorTerm) *storage.StorageClass {
|
||||
return &storage.StorageClass{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: mode,
|
||||
AllowedTopologies: topologies,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove these tests once feature gate is not required
|
||||
func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) {
|
||||
errorCases := map[string]*storage.StorageClass{
|
||||
"immediate mode": makeClass(&immediateMode1, nil),
|
||||
"waiting mode": makeClass(&waitingMode, nil),
|
||||
"invalid mode": makeClass(&invalidMode, nil),
|
||||
}
|
||||
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
for testName, storageClass := range errorCases {
|
||||
if errs := ValidateStorageClass(storageClass); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %v", testName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type bindingTest struct {
|
||||
class *storage.StorageClass
|
||||
shouldSucceed bool
|
||||
}
|
||||
|
||||
func TestValidateVolumeBindingMode(t *testing.T) {
|
||||
cases := map[string]bindingTest{
|
||||
"no mode": {
|
||||
class: makeClass(nil, nil),
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"immediate mode": {
|
||||
class: makeClass(&immediateMode1, nil),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"waiting mode": {
|
||||
class: makeClass(&waitingMode, nil),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"invalid mode": {
|
||||
class: makeClass(&invalidMode, nil),
|
||||
shouldSucceed: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: remove when feature gate not required
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClass(testCase.class)
|
||||
if testCase.shouldSucceed && len(errs) != 0 {
|
||||
t.Errorf("Expected success for test %q, got %v", testName, errs)
|
||||
}
|
||||
if !testCase.shouldSucceed && len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test %q, got success", testName)
|
||||
}
|
||||
}
|
||||
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type updateTest struct {
|
||||
oldClass *storage.StorageClass
|
||||
newClass *storage.StorageClass
|
||||
shouldSucceed bool
|
||||
}
|
||||
|
||||
func TestValidateUpdateVolumeBindingMode(t *testing.T) {
|
||||
noBinding := makeClass(nil, nil)
|
||||
immediateBinding1 := makeClass(&immediateMode1, nil)
|
||||
immediateBinding2 := makeClass(&immediateMode2, nil)
|
||||
waitBinding := makeClass(&waitingMode, nil)
|
||||
|
||||
cases := map[string]updateTest{
|
||||
"old and new no mode": {
|
||||
oldClass: noBinding,
|
||||
newClass: noBinding,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old and new same mode ptr": {
|
||||
oldClass: immediateBinding1,
|
||||
newClass: immediateBinding1,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old and new same mode value": {
|
||||
oldClass: immediateBinding1,
|
||||
newClass: immediateBinding2,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old no mode, new mode": {
|
||||
oldClass: noBinding,
|
||||
newClass: waitBinding,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"old mode, new no mode": {
|
||||
oldClass: waitBinding,
|
||||
newClass: noBinding,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"old and new different modes": {
|
||||
oldClass: waitBinding,
|
||||
newClass: immediateBinding1,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: remove when feature gate not required
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClassUpdate(testCase.newClass, testCase.oldClass)
|
||||
if testCase.shouldSucceed && len(errs) != 0 {
|
||||
t.Errorf("Expected success for %v, got %v", testName, errs)
|
||||
}
|
||||
if !testCase.shouldSucceed && len(errs) == 0 {
|
||||
t.Errorf("Expected failure for %v, got success", testName)
|
||||
}
|
||||
}
|
||||
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAllowedTopologies(t *testing.T) {
|
||||
|
||||
validTopology := []api.TopologySelectorTerm{
|
||||
{
|
||||
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: "failure-domain.beta.kubernetes.io/zone",
|
||||
Values: []string{"zone1"},
|
||||
},
|
||||
{
|
||||
Key: "kubernetes.io/hostname",
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: "failure-domain.beta.kubernetes.io/zone",
|
||||
Values: []string{"zone2"},
|
||||
},
|
||||
{
|
||||
Key: "kubernetes.io/hostname",
|
||||
Values: []string{"node2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
topologyInvalidKey := []api.TopologySelectorTerm{
|
||||
{
|
||||
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: "/invalidkey",
|
||||
Values: []string{"zone1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
topologyLackOfValues := []api.TopologySelectorTerm{
|
||||
{
|
||||
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: "kubernetes.io/hostname",
|
||||
Values: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := map[string]bindingTest{
|
||||
"no topology": {
|
||||
class: makeClass(nil, nil),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"valid topology": {
|
||||
class: makeClass(nil, validTopology),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"topology invalid key": {
|
||||
class: makeClass(nil, topologyInvalidKey),
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"topology lack of values": {
|
||||
class: makeClass(nil, topologyLackOfValues),
|
||||
shouldSucceed: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: remove when feature gate not required
|
||||
err := utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for DynamicProvisioningScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClass(testCase.class)
|
||||
if testCase.shouldSucceed && len(errs) != 0 {
|
||||
t.Errorf("Expected success for test %q, got %v", testName, errs)
|
||||
}
|
||||
if !testCase.shouldSucceed && len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test %q, got success", testName)
|
||||
}
|
||||
}
|
||||
|
||||
err = utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for DynamicProvisioningScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClass(testCase.class)
|
||||
if len(errs) == 0 && testCase.class.AllowedTopologies != nil {
|
||||
t.Errorf("Expected failure for test %q, got success", testName)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user