kubernetes validation_test 源码
kubernetes validation_test 代码
文件路径:/pkg/apis/storage/validation/validation_test.go
/*
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"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage"
utilpointer "k8s.io/utils/pointer"
)
var (
deleteReclaimPolicy = api.PersistentVolumeReclaimDelete
immediateMode1 = storage.VolumeBindingImmediate
immediateMode2 = storage.VolumeBindingImmediate
waitingMode = storage.VolumeBindingWaitForFirstConsumer
invalidMode = storage.VolumeBindingMode("foo")
inlineSpec = api.PersistentVolumeSpec{
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
CSI: &api.CSIPersistentVolumeSource{
Driver: "com.test.foo",
VolumeHandle: "foobar",
},
},
}
longerIDValidateOption = CSINodeValidationOptions{
AllowLongNodeID: true,
}
shorterIDValidationOption = CSINodeValidationOptions{
AllowLongNodeID: false,
}
)
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 TestVolumeAttachmentValidation(t *testing.T) {
volumeName := "pv-name"
empty := ""
migrationEnabledSuccessCases := []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-inlinespec"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{
InlineVolumeSpec: &inlineSpec,
},
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",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "foo-with-inlinespec-and-status"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{
InlineVolumeSpec: &inlineSpec,
},
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 migrationEnabledSuccessCases {
if errs := ValidateVolumeAttachment(&volumeAttachment); len(errs) != 0 {
t.Errorf("expected success: %v %v", volumeAttachment, errs)
}
}
migrationEnabledErrorCases := []storage.VolumeAttachment{
{
// Empty attacher name
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "",
NodeName: "mynode",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
},
},
},
{
// Empty node name
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
NodeName: "",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
},
},
},
{
// 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",
},
},
},
{
// VolumeAttachmentSource with no PersistentVolumeName nor InlineSpec
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
NodeName: "node",
Source: storage.VolumeAttachmentSource{},
},
},
{
// VolumeAttachmentSource with PersistentVolumeName and InlineSpec
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
NodeName: "node",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
InlineVolumeSpec: &inlineSpec,
},
},
},
{
// VolumeAttachmentSource with InlineSpec without CSI PV Source
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
NodeName: "node",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
InlineVolumeSpec: &api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
FlexVolume: &api.FlexPersistentVolumeSource{
Driver: "kubernetes.io/blue",
FSType: "ext4",
},
},
StorageClassName: "test-storage-class",
},
},
},
},
}
for _, volumeAttachment := range migrationEnabledErrorCases {
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{},
NodeName: "mynode",
},
}
successCases := []storage.VolumeAttachment{
{
// no change
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{},
NodeName: "mynode",
},
},
{
// modify status
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{},
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 {
volumeAttachment.Spec.Source = storage.VolumeAttachmentSource{}
old.Spec.Source = storage.VolumeAttachmentSource{}
// test scenarios with PersistentVolumeName set
volumeAttachment.Spec.Source.PersistentVolumeName = &volumeName
old.Spec.Source.PersistentVolumeName = &volumeName
if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 {
t.Errorf("expected success: %+v", errs)
}
volumeAttachment.Spec.Source = storage.VolumeAttachmentSource{}
old.Spec.Source = storage.VolumeAttachmentSource{}
// test scenarios with InlineVolumeSpec set
volumeAttachment.Spec.Source.InlineVolumeSpec = &inlineSpec
old.Spec.Source.InlineVolumeSpec = &inlineSpec
if errs := ValidateVolumeAttachmentUpdate(&volumeAttachment, &old); len(errs) != 0 {
t.Errorf("expected success: %+v", errs)
}
}
// reset old's source with volumeName in case it was left with something else by earlier tests
old.Spec.Source = storage.VolumeAttachmentSource{}
old.Spec.Source.PersistentVolumeName = &volumeName
errorCases := []storage.VolumeAttachment{
{
// change attacher
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "another-attacher",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
},
NodeName: "mynode",
},
},
{
// change source volume name
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",
},
},
{
// change source
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{
InlineVolumeSpec: &inlineSpec,
},
NodeName: "mynode",
},
},
{
// 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 TestVolumeAttachmentValidationV1(t *testing.T) {
volumeName := "pv-name"
invalidVolumeName := "-invalid-@#$%^&*()-"
successCases := []storage.VolumeAttachment{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
},
NodeName: "mynode",
},
},
}
for _, volumeAttachment := range successCases {
if errs := ValidateVolumeAttachmentV1(&volumeAttachment); len(errs) != 0 {
t.Errorf("expected success: %+v", errs)
}
}
errorCases := []storage.VolumeAttachment{
{
// Invalid attacher name
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "invalid-@#$%^&*()",
NodeName: "mynode",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &volumeName,
},
},
},
{
// Invalid PV name
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: storage.VolumeAttachmentSpec{
Attacher: "myattacher",
NodeName: "mynode",
Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &invalidVolumeName,
},
},
},
}
for _, volumeAttachment := range errorCases {
if errs := ValidateVolumeAttachmentV1(&volumeAttachment); 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,
}
}
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,
},
}
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)
}
}
}
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,
},
}
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)
}
}
}
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{},
},
},
},
}
topologyDupValues := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1", "node1"},
},
},
},
}
topologyMultiValues := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1", "node2"},
},
},
},
}
topologyEmptyMatchLabelExpressions := []api.TopologySelectorTerm{
{
MatchLabelExpressions: nil,
},
}
topologyDupKeys := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node2"},
},
},
},
}
topologyMultiTerm := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
},
},
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node2"},
},
},
},
}
topologyDupTermsIdentical := []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{"zone1"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
},
},
}
topologyExprsOneSameOneDiff := []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{"zone1"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node2"},
},
},
},
}
topologyValuesOneSameOneDiff := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1", "node2"},
},
},
},
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1", "node3"},
},
},
},
}
topologyDupTermsDiffExprOrder := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone1"},
},
},
},
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone1"},
},
{
Key: "kubernetes.io/hostname",
Values: []string{"node1"},
},
},
},
}
topologyDupTermsDiffValueOrder := []api.TopologySelectorTerm{
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone1", "zone2"},
},
},
},
{
MatchLabelExpressions: []api.TopologySelectorLabelRequirement{
{
Key: "failure-domain.beta.kubernetes.io/zone",
Values: []string{"zone2", "zone1"},
},
},
},
}
cases := map[string]bindingTest{
"no topology": {
class: makeClass(&waitingMode, nil),
shouldSucceed: true,
},
"valid topology": {
class: makeClass(&waitingMode, validTopology),
shouldSucceed: true,
},
"topology invalid key": {
class: makeClass(&waitingMode, topologyInvalidKey),
shouldSucceed: false,
},
"topology lack of values": {
class: makeClass(&waitingMode, topologyLackOfValues),
shouldSucceed: false,
},
"duplicate TopologySelectorRequirement values": {
class: makeClass(&waitingMode, topologyDupValues),
shouldSucceed: false,
},
"multiple TopologySelectorRequirement values": {
class: makeClass(&waitingMode, topologyMultiValues),
shouldSucceed: true,
},
"empty MatchLabelExpressions": {
class: makeClass(&waitingMode, topologyEmptyMatchLabelExpressions),
shouldSucceed: false,
},
"duplicate MatchLabelExpression keys": {
class: makeClass(&waitingMode, topologyDupKeys),
shouldSucceed: false,
},
"duplicate MatchLabelExpression keys but across separate terms": {
class: makeClass(&waitingMode, topologyMultiTerm),
shouldSucceed: true,
},
"duplicate AllowedTopologies terms - identical": {
class: makeClass(&waitingMode, topologyDupTermsIdentical),
shouldSucceed: false,
},
"two AllowedTopologies terms, with a pair of the same MatchLabelExpressions and a pair of different ones": {
class: makeClass(&waitingMode, topologyExprsOneSameOneDiff),
shouldSucceed: true,
},
"two AllowedTopologies terms, with a pair of the same Values and a pair of different ones": {
class: makeClass(&waitingMode, topologyValuesOneSameOneDiff),
shouldSucceed: true,
},
"duplicate AllowedTopologies terms - different MatchLabelExpressions order": {
class: makeClass(&waitingMode, topologyDupTermsDiffExprOrder),
shouldSucceed: false,
},
"duplicate AllowedTopologies terms - different TopologySelectorRequirement values order": {
class: makeClass(&waitingMode, topologyDupTermsDiffValueOrder),
shouldSucceed: false,
},
}
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)
}
}
}
func TestCSINodeValidation(t *testing.T) {
driverName := "driver-name"
driverName2 := "1io.kubernetes-storage-2-csi-driver3"
longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver" // 88 chars
nodeID := "nodeA"
longID := longName + longName + "abcdefghijklmnopqrstuvwxyz" // 202 chars
successCases := []storage.CSINode{
{
// driver name: dot only
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// driver name: dash only
ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io-kubernetes-storage-csi-driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// driver name: numbers
ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "1io-kubernetes-storage-2-csi-driver3",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// driver name: dot, dash
ObjectMeta: metav1.ObjectMeta{Name: "foo4"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage-csi-driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// driver name: dot, dash, and numbers
ObjectMeta: metav1.ObjectMeta{Name: "foo5"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName2,
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Driver name length 1
ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "a",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// multiple drivers with different node IDs, topology keys
ObjectMeta: metav1.ObjectMeta{Name: "foo6"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"key1", "key2"},
},
{
Name: "driverB",
NodeID: "nodeA",
TopologyKeys: []string{"keyA", "keyB"},
},
},
},
},
{
// multiple drivers with same node IDs, topology keys
ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"key1"},
},
{
Name: "driver2",
NodeID: "node1",
TopologyKeys: []string{"key1"},
},
},
},
},
{
// Volume limits being zero
ObjectMeta: metav1.ObjectMeta{Name: "foo11"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(0)},
},
},
},
},
{
// Volume limits with positive number
ObjectMeta: metav1.ObjectMeta{Name: "foo11"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(1)},
},
},
},
},
{
// topology key names with -, _, and dot .
ObjectMeta: metav1.ObjectMeta{Name: "foo8"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"zone_1", "zone.2"},
},
{
Name: "driver2",
NodeID: "node1",
TopologyKeys: []string{"zone-3", "zone.4"},
},
},
},
},
{
// topology prefix with - and dot.
ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"company-com/zone1", "company.com/zone2"},
},
},
},
},
{
// No topology keys
ObjectMeta: metav1.ObjectMeta{Name: "foo10"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName,
NodeID: nodeID,
},
},
},
},
}
for _, csiNode := range successCases {
if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
nodeIDCase := storage.CSINode{
// node ID length > 128 but < 192
ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName,
NodeID: longID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
}
if errs := ValidateCSINode(&nodeIDCase, longerIDValidateOption); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
errorCases := []storage.CSINode{
{
// Empty driver name
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Invalid start char in driver name
ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "_io.kubernetes.storage.csi.driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Invalid end char in driver name
ObjectMeta: metav1.ObjectMeta{Name: "foo4"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver/",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Invalid separators in driver name
ObjectMeta: metav1.ObjectMeta{Name: "foo5"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io/kubernetes/storage/csi~driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// driver name: underscore only
ObjectMeta: metav1.ObjectMeta{Name: "foo6"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io_kubernetes_storage_csi_driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Driver name length > 63
ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: longName,
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// No driver name
ObjectMeta: metav1.ObjectMeta{Name: "foo8"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Empty individual topology key
ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName,
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", ""},
},
},
},
},
{
// duplicate drivers in driver specs
ObjectMeta: metav1.ObjectMeta{Name: "foo10"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"key1", "key2"},
},
{
Name: "driver1",
NodeID: "nodeX",
TopologyKeys: []string{"keyA", "keyB"},
},
},
},
},
{
// single driver with duplicate topology keys in driver specs
ObjectMeta: metav1.ObjectMeta{Name: "foo11"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"key1", "key1"},
},
},
},
},
{
// multiple drivers with one set of duplicate topology keys in driver specs
ObjectMeta: metav1.ObjectMeta{Name: "foo12"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "driver1",
NodeID: "node1",
TopologyKeys: []string{"key1"},
},
{
Name: "driver2",
NodeID: "nodeX",
TopologyKeys: []string{"keyA", "keyA"},
},
},
},
},
{
// Empty NodeID
ObjectMeta: metav1.ObjectMeta{Name: "foo13"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName,
NodeID: "",
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// Volume limits with negative number
ObjectMeta: metav1.ObjectMeta{Name: "foo11"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(-1)},
},
},
},
},
{
// topology prefix should be lower case
ObjectMeta: metav1.ObjectMeta{Name: "foo14"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: driverName,
NodeID: "node1",
TopologyKeys: []string{"Company.Com/zone1", "company.com/zone2"},
},
},
},
},
nodeIDCase,
}
for _, csiNode := range errorCases {
if errs := ValidateCSINode(&csiNode, shorterIDValidationOption); len(errs) == 0 {
t.Errorf("Expected failure for test: %v", csiNode)
}
}
}
func TestCSINodeUpdateValidation(t *testing.T) {
//driverName := "driver-name"
//driverName2 := "1io.kubernetes-storage-2-csi-driver3"
//longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
nodeID := "nodeA"
old := storage.CSINode{
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
},
},
}
successCases := []storage.CSINode{
{
// no change
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
},
},
},
{
// remove a driver
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
{
// add a driver
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
{
Name: "io.kubernetes.storage.csi.driver-3",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(30)},
},
},
},
},
{
// remove a driver and add a driver
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.new-driver",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(30)},
},
},
},
},
}
for _, csiNode := range successCases {
if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) != 0 {
t.Errorf("expected success: %+v", errs)
}
}
errorCases := []storage.CSINode{
{
// invalid change node id
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: "nodeB",
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
},
},
},
{
// invalid change topology keys
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
},
},
},
{
// invalid change trying to set a previously unset allocatable
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(10)},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
},
},
},
},
{
// invalid change trying to update allocatable with a different volume limit
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(21)},
},
},
},
},
{
// invalid change trying to update allocatable with an empty volume limit
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
Allocatable: &storage.VolumeNodeResources{Count: nil},
},
},
},
},
{
// invalid change trying to remove allocatable
ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
Spec: storage.CSINodeSpec{
Drivers: []storage.CSINodeDriver{
{
Name: "io.kubernetes.storage.csi.driver-1",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
{
Name: "io.kubernetes.storage.csi.driver-2",
NodeID: nodeID,
TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
},
},
},
},
}
for _, csiNode := range errorCases {
if errs := ValidateCSINodeUpdate(&csiNode, &old, shorterIDValidationOption); len(errs) == 0 {
t.Errorf("Expected failure for test: %+v", csiNode)
}
}
}
func TestCSIDriverValidation(t *testing.T) {
driverName := "test-driver"
longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
invalidName := "-invalid-@#$%^&*()-"
attachRequired := true
attachNotRequired := false
podInfoOnMount := true
notPodInfoOnMount := false
notRequiresRepublish := false
storageCapacity := true
notStorageCapacity := false
supportedFSGroupPolicy := storage.FileFSGroupPolicy
invalidFSGroupPolicy := storage.FSGroupPolicy("invalid-mode")
successCases := []storage.CSIDriver{
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
// driver name: dot only
ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi.driver"},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: ¬StorageCapacity,
},
},
{
// driver name: dash only
ObjectMeta: metav1.ObjectMeta{Name: "io-kubernetes-storage-csi-driver"},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
// driver name: numbers
ObjectMeta: metav1.ObjectMeta{Name: "1csi2driver3"},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
// driver name: dot and dash
ObjectMeta: metav1.ObjectMeta{Name: "io.kubernetes.storage.csi-driver"},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent,
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral,
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral,
storage.VolumeLifecyclePersistent,
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral,
storage.VolumeLifecyclePersistent,
storage.VolumeLifecycleEphemeral,
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
StorageCapacity: &storageCapacity,
FSGroupPolicy: &supportedFSGroupPolicy,
},
},
}
for _, csiDriver := range successCases {
if errs := ValidateCSIDriver(&csiDriver); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := []storage.CSIDriver{
{
ObjectMeta: metav1.ObjectMeta{Name: invalidName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
StorageCapacity: &storageCapacity,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: longName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
StorageCapacity: &storageCapacity,
},
},
{
// AttachRequired not set
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: nil,
PodInfoOnMount: &podInfoOnMount,
StorageCapacity: &storageCapacity,
},
},
{
// PodInfoOnMount not set
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: nil,
StorageCapacity: &storageCapacity,
},
},
{
// StorageCapacity not set
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: &podInfoOnMount,
StorageCapacity: nil,
},
},
{
// invalid mode
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
"no-such-mode",
},
},
},
{
// invalid fsGroupPolicy
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
FSGroupPolicy: &invalidFSGroupPolicy,
StorageCapacity: &storageCapacity,
},
},
}
for _, csiDriver := range errorCases {
if errs := ValidateCSIDriver(&csiDriver); len(errs) == 0 {
t.Errorf("Expected failure for test: %v", csiDriver)
}
}
}
func TestCSIDriverValidationUpdate(t *testing.T) {
driverName := "test-driver"
longName := "my-a-b-c-d-c-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-ABCDEFGHIJKLMNOPQRSTUVWXYZ-driver"
invalidName := "-invalid-@#$%^&*()-"
attachRequired := true
attachNotRequired := false
podInfoOnMount := true
storageCapacity := true
notPodInfoOnMount := false
gcp := "gcp"
requiresRepublish := true
notRequiresRepublish := false
notStorageCapacity := false
resourceVersion := "1"
old := storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName, ResourceVersion: resourceVersion},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachNotRequired,
PodInfoOnMount: ¬PodInfoOnMount,
RequiresRepublish: ¬RequiresRepublish,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral,
storage.VolumeLifecyclePersistent,
},
StorageCapacity: &storageCapacity,
},
}
successCases := []struct {
name string
modify func(new *storage.CSIDriver)
}{
{
name: "no change",
modify: func(new *storage.CSIDriver) {},
},
{
name: "change TokenRequests",
modify: func(new *storage.CSIDriver) {
new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}}
},
},
{
name: "change RequiresRepublish",
modify: func(new *storage.CSIDriver) {
new.Spec.RequiresRepublish = &requiresRepublish
},
},
{
name: "StorageCapacity changed",
modify: func(new *storage.CSIDriver) {
new.Spec.StorageCapacity = ¬StorageCapacity
},
},
}
for _, test := range successCases {
t.Run(test.name, func(t *testing.T) {
new := old.DeepCopy()
test.modify(new)
if errs := ValidateCSIDriverUpdate(new, &old); len(errs) != 0 {
t.Errorf("Expected success for %+v: %v", new, errs)
}
})
}
// Each test case changes exactly one field. None of that is valid.
errorCases := []struct {
name string
modify func(new *storage.CSIDriver)
}{
{
name: "invalid name",
modify: func(new *storage.CSIDriver) {
new.Name = invalidName
},
},
{
name: "long name",
modify: func(new *storage.CSIDriver) {
new.Name = longName
},
},
{
name: "AttachRequired not set",
modify: func(new *storage.CSIDriver) {
new.Spec.AttachRequired = nil
},
},
{
name: "AttachRequired changed",
modify: func(new *storage.CSIDriver) {
new.Spec.AttachRequired = &attachRequired
},
},
{
name: "PodInfoOnMount not set",
modify: func(new *storage.CSIDriver) {
new.Spec.PodInfoOnMount = nil
},
},
{
name: "PodInfoOnMount changed",
modify: func(new *storage.CSIDriver) {
new.Spec.PodInfoOnMount = &podInfoOnMount
},
},
{
name: "invalid volume lifecycle mode",
modify: func(new *storage.CSIDriver) {
new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
"no-such-mode",
}
},
},
{
name: "volume lifecycle modes not set",
modify: func(new *storage.CSIDriver) {
new.Spec.VolumeLifecycleModes = nil
},
},
{
name: "VolumeLifecyclePersistent removed",
modify: func(new *storage.CSIDriver) {
new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral,
}
},
},
{
name: "VolumeLifecycleEphemeral removed",
modify: func(new *storage.CSIDriver) {
new.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent,
}
},
},
{
name: "FSGroupPolicy invalidated",
modify: func(new *storage.CSIDriver) {
invalidFSGroupPolicy := storage.FSGroupPolicy("invalid")
new.Spec.FSGroupPolicy = &invalidFSGroupPolicy
},
},
{
name: "FSGroupPolicy changed",
modify: func(new *storage.CSIDriver) {
fileFSGroupPolicy := storage.FileFSGroupPolicy
new.Spec.FSGroupPolicy = &fileFSGroupPolicy
},
},
{
name: "TokenRequests invalidated",
modify: func(new *storage.CSIDriver) {
new.Spec.TokenRequests = []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}}
},
},
{
name: "invalid nil StorageCapacity",
modify: func(new *storage.CSIDriver) {
new.Spec.StorageCapacity = nil
},
},
}
for _, test := range errorCases {
t.Run(test.name, func(t *testing.T) {
new := old.DeepCopy()
test.modify(new)
if errs := ValidateCSIDriverUpdate(new, &old); len(errs) == 0 {
t.Errorf("Expected failure for test: %+v", new)
}
})
}
}
func TestCSIDriverStorageCapacityEnablement(t *testing.T) {
run := func(t *testing.T, withField bool) {
driverName := "test-driver"
attachRequired := true
podInfoOnMount := true
requiresRepublish := true
storageCapacity := true
csiDriver := storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
RequiresRepublish: &requiresRepublish,
},
}
if withField {
csiDriver.Spec.StorageCapacity = &storageCapacity
}
errs := ValidateCSIDriver(&csiDriver)
success := withField
if success && len(errs) != 0 {
t.Errorf("expected success, got: %v", errs)
}
if !success && len(errs) == 0 {
t.Errorf("expected error, got success")
}
}
yesNo := []bool{true, false}
for _, withField := range yesNo {
t.Run(fmt.Sprintf("with-field=%v", withField), func(t *testing.T) {
run(t, withField)
})
}
}
func TestValidateCSIStorageCapacity(t *testing.T) {
storageClassName := "test-sc"
invalidName := "-invalid-@#$%^&*()-"
goodCapacity := storage.CSIStorageCapacity{
ObjectMeta: metav1.ObjectMeta{
Name: "csc-329803da-fdd2-42e4-af6f-7b07e7ccc305",
Namespace: metav1.NamespaceDefault,
},
StorageClassName: storageClassName,
}
goodTopology := metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
}
scenarios := map[string]struct {
isExpectedFailure bool
capacity *storage.CSIStorageCapacity
}{
"good-capacity": {
capacity: &goodCapacity,
},
"missing-storage-class-name": {
isExpectedFailure: true,
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.StorageClassName = ""
return &capacity
}(),
},
"bad-storage-class-name": {
isExpectedFailure: true,
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.StorageClassName = invalidName
return &capacity
}(),
},
"good-capacity-value": {
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.Capacity = resource.NewQuantity(1, resource.BinarySI)
return &capacity
}(),
},
"bad-capacity-value": {
isExpectedFailure: true,
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.Capacity = resource.NewQuantity(-11, resource.BinarySI)
return &capacity
}(),
},
"good-topology": {
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.NodeTopology = &goodTopology
return &capacity
}(),
},
"empty-topology": {
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.NodeTopology = &metav1.LabelSelector{}
return &capacity
}(),
},
"bad-topology-fields": {
isExpectedFailure: true,
capacity: func() *storage.CSIStorageCapacity {
capacity := goodCapacity
capacity.NodeTopology = &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "foo",
Operator: metav1.LabelSelectorOperator("no-such-operator"),
Values: []string{
"bar",
},
},
},
}
return &capacity
}(),
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
errs := ValidateCSIStorageCapacity(scenario.capacity)
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success")
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure: %+v", errs)
}
})
}
}
func TestCSIServiceAccountToken(t *testing.T) {
driverName := "test-driver"
gcp := "gcp"
aws := "aws"
notRequiresRepublish := false
tests := []struct {
desc string
csiDriver *storage.CSIDriver
wantErr bool
}{
{
desc: "invalid - TokenRequests has tokens with the same audience",
csiDriver: &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
TokenRequests: []storage.TokenRequest{{Audience: gcp}, {Audience: gcp}},
RequiresRepublish: ¬RequiresRepublish,
},
},
wantErr: true,
},
{
desc: "invalid - TokenRequests has tokens with ExpirationSeconds less than 10min",
csiDriver: &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64Ptr(10)}},
RequiresRepublish: ¬RequiresRepublish,
},
},
wantErr: true,
},
{
desc: "invalid - TokenRequests has tokens with ExpirationSeconds longer than 1<<32 min",
csiDriver: &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
TokenRequests: []storage.TokenRequest{{Audience: gcp, ExpirationSeconds: utilpointer.Int64Ptr(1<<32 + 1)}},
RequiresRepublish: ¬RequiresRepublish,
},
},
wantErr: true,
},
{
desc: "valid - TokenRequests has at most one token with empty string audience",
csiDriver: &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
TokenRequests: []storage.TokenRequest{{Audience: ""}},
RequiresRepublish: ¬RequiresRepublish,
},
},
},
{
desc: "valid - TokenRequests has tokens with different audience",
csiDriver: &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{Name: driverName},
Spec: storage.CSIDriverSpec{
TokenRequests: []storage.TokenRequest{{}, {Audience: gcp}, {Audience: aws}},
RequiresRepublish: ¬RequiresRepublish,
},
},
},
}
for _, test := range tests {
test.csiDriver.Spec.AttachRequired = new(bool)
test.csiDriver.Spec.PodInfoOnMount = new(bool)
test.csiDriver.Spec.StorageCapacity = new(bool)
if errs := ValidateCSIDriver(test.csiDriver); test.wantErr != (len(errs) != 0) {
t.Errorf("ValidateCSIDriver = %v, want err: %v", errs, test.wantErr)
}
}
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦