k8s.io/kubernetes@v1.29.3/pkg/volume/csi/expander_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package csi 18 19 import ( 20 "context" 21 "os" 22 "reflect" 23 "testing" 24 25 "google.golang.org/grpc/codes" 26 "google.golang.org/grpc/status" 27 api "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/resource" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/kubernetes/pkg/volume" 31 volumetypes "k8s.io/kubernetes/pkg/volume/util/types" 32 ) 33 34 func TestNodeExpand(t *testing.T) { 35 tests := []struct { 36 name string 37 nodeExpansion bool 38 nodeStageSet bool 39 success bool 40 fsVolume bool 41 grpcError error 42 hasVolumeInUseError bool 43 deviceStagePath string 44 enableCSINodeExpandSecret bool 45 secret *api.Secret 46 }{ 47 { 48 name: "when node expansion is not set", 49 success: false, 50 }, 51 { 52 name: "when nodeExpansion=on, nodeStage=on, volumePhase=staged", 53 nodeExpansion: true, 54 nodeStageSet: true, 55 success: true, 56 fsVolume: true, 57 deviceStagePath: "/foo/bar", 58 }, 59 { 60 name: "when nodeExpansion=on, nodeStage=on, volumePhase=published", 61 nodeExpansion: true, 62 nodeStageSet: true, 63 success: true, 64 fsVolume: true, 65 }, 66 { 67 name: "when nodeExpansion=on, nodeStage=off, volumePhase=published", 68 nodeExpansion: true, 69 success: true, 70 fsVolume: true, 71 }, 72 { 73 name: "when nodeExpansion=on, nodeStage=off, volumePhase=published, fsVolume=false", 74 nodeExpansion: true, 75 success: true, 76 fsVolume: false, 77 }, 78 { 79 name: "when nodeExpansion=on, nodeStage=on, volumePhase=published has grpc volume-in-use error", 80 nodeExpansion: true, 81 nodeStageSet: true, 82 success: false, 83 fsVolume: true, 84 grpcError: status.Error(codes.FailedPrecondition, "volume-in-use"), 85 hasVolumeInUseError: true, 86 }, 87 { 88 name: "when nodeExpansion=on, nodeStage=on, volumePhase=published has other grpc error", 89 nodeExpansion: true, 90 nodeStageSet: true, 91 success: false, 92 fsVolume: true, 93 grpcError: status.Error(codes.InvalidArgument, "invalid-argument"), 94 hasVolumeInUseError: false, 95 }, 96 { 97 name: "when nodeExpansion=on, nodeStage=on, volumePhase=staged", 98 nodeExpansion: true, 99 nodeStageSet: true, 100 success: true, 101 fsVolume: true, 102 deviceStagePath: "/foo/bar", 103 enableCSINodeExpandSecret: true, 104 secret: &api.Secret{ 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: "expand-secret", 107 Namespace: "default", 108 }, 109 Data: map[string][]byte{ 110 "apiUsername": []byte("csiusername"), 111 "apiPassword": []byte("csipassword"), 112 }, 113 }, 114 }, 115 } 116 for _, tc := range tests { 117 t.Run(tc.name, func(t *testing.T) { 118 plug, tmpDir := newTestPlugin(t, nil) 119 defer os.RemoveAll(tmpDir) 120 121 spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "expandable", "test-vol"), false) 122 if tc.enableCSINodeExpandSecret { 123 spec.PersistentVolume.Spec.CSI.NodeExpandSecretRef = &api.SecretReference{ 124 Name: tc.secret.Name, 125 Namespace: tc.secret.Namespace, 126 } 127 } 128 129 newSize, _ := resource.ParseQuantity("20Gi") 130 131 resizeOptions := volume.NodeResizeOptions{ 132 VolumeSpec: spec, 133 NewSize: newSize, 134 DeviceMountPath: "/foo/bar", 135 DeviceStagePath: "/foo/bar", 136 DevicePath: "/mnt/foobar", 137 } 138 csiSource, _ := getCSISourceFromSpec(resizeOptions.VolumeSpec) 139 csClient := setupClientWithExpansion(t, tc.nodeStageSet, tc.nodeExpansion) 140 141 fakeCSIClient, _ := csClient.(*fakeCsiDriverClient) 142 fakeNodeClient := fakeCSIClient.nodeClient 143 144 if tc.enableCSINodeExpandSecret { 145 _, err := plug.host.GetKubeClient().CoreV1().Secrets(tc.secret.Namespace).Create(context.TODO(), tc.secret, metav1.CreateOptions{}) 146 if err != nil { 147 t.Fatal(err) 148 } 149 } 150 151 if tc.grpcError != nil { 152 fakeNodeClient.SetNextError(tc.grpcError) 153 } 154 155 ok, err := plug.nodeExpandWithClient(resizeOptions, csiSource, csClient, tc.fsVolume) 156 157 if tc.hasVolumeInUseError { 158 if !volumetypes.IsFailedPreconditionError(err) { 159 t.Errorf("expected failed precondition error got: %v", err) 160 } 161 } 162 163 // verify device staging target path 164 stagingTargetPath := fakeNodeClient.FakeNodeExpansionRequest.GetStagingTargetPath() 165 if tc.deviceStagePath != "" && tc.deviceStagePath != stagingTargetPath { 166 t.Errorf("For %s: expected staging path %s got %s", tc.name, tc.deviceStagePath, stagingTargetPath) 167 } 168 169 if ok != tc.success { 170 if err != nil { 171 t.Errorf("For %s : expected %v got %v with %v", tc.name, tc.success, ok, err) 172 } else { 173 t.Errorf("For %s : expected %v got %v", tc.name, tc.success, ok) 174 } 175 } 176 // verify volume capability received by node expansion request 177 if tc.success { 178 capability := fakeNodeClient.FakeNodeExpansionRequest.GetVolumeCapability() 179 if tc.fsVolume { 180 if capability.GetMount() == nil { 181 t.Errorf("For %s: expected mount accesstype got: %v", tc.name, capability) 182 } 183 } else { 184 if capability.GetBlock() == nil { 185 t.Errorf("For %s: expected block accesstype got: %v", tc.name, capability) 186 } 187 } 188 } 189 }) 190 } 191 } 192 193 func TestNodeExpandNoClientError(t *testing.T) { 194 transientError := volumetypes.NewTransientOperationFailure("") 195 plug, tmpDir := newTestPlugin(t, nil) 196 defer os.RemoveAll(tmpDir) 197 spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "expandable", "test-vol"), false) 198 199 newSize, _ := resource.ParseQuantity("20Gi") 200 201 resizeOptions := volume.NodeResizeOptions{ 202 VolumeSpec: spec, 203 NewSize: newSize, 204 DeviceMountPath: "/foo/bar", 205 DeviceStagePath: "/foo/bar", 206 DevicePath: "/mnt/foobar", 207 } 208 209 _, err := plug.NodeExpand(resizeOptions) 210 211 if err == nil { 212 t.Errorf("test should fail, but no error occurred") 213 } else if reflect.TypeOf(transientError) != reflect.TypeOf(err) { 214 t.Fatalf("expected exitError type: %v got: %v (%v)", reflect.TypeOf(transientError), reflect.TypeOf(err), err) 215 } 216 }