1
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
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
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 }
217
View as plain text